import AppKit import Carbon

extension KeyCombo {

/// All system key combos
///
/// - returns: array of key combos
public static func systemKeyCombos() -> [KeyCombo] {
    var unmanagedGlobalHotkeys: Unmanaged<CFArray>?
    guard CopySymbolicHotKeys(&unmanagedGlobalHotkeys) == noErr,
        let globalHotkeys = unmanagedGlobalHotkeys?.takeRetainedValue() else
    {
        assertionFailure("Unable to get system-wide hotkeys")
        return []
    }

    return (0..<CFArrayGetCount(globalHotkeys)).compactMap { i in
        let hotKeyInfo = unsafeBitCast(CFArrayGetValueAtIndex(globalHotkeys, i), to: NSDictionary.self)
        guard (hotKeyInfo[kHISymbolicHotKeyEnabled] as? NSNumber)?.boolValue == true,
            let keyCode = (hotKeyInfo[kHISymbolicHotKeyCode] as? NSNumber)?.uint32Value,
            let modifiers = (hotKeyInfo[kHISymbolicHotKeyModifiers] as? NSNumber)?.uint32Value else
        {
                return nil
        }

        let keyCombo = KeyCombo(carbonKeyCode: keyCode, carbonModifiers: modifiers)

        // Several of these aren’t valid key combos. Filter them out so consumers don’t have to think about this.
        return keyCombo.isValid ? keyCombo : nil
    }
}

/// All key combos in the application’s main window
///
/// - returns: array of key combos
public static func mainMenuKeyCombos() -> [KeyCombo] {
    guard let menu = NSApp.mainMenu else {
        return []
    }

    return keyCombos(in: menu)
}

/// Recursively find key combos in a menu
///
/// - parameter menu: menu to search
///
/// - returns: array of key combos
public static func keyCombos(in menu: NSMenu) -> [KeyCombo] {
    var keyCombos = [KeyCombo]()

    for item in menu.items {
        if let key = Key(string: item.keyEquivalent) {
            keyCombos.append(KeyCombo(key: key, modifiers: item.keyEquivalentModifierMask))
        }

        if let submenu = item.submenu {
            keyCombos += self.keyCombos(in: submenu)
        }
    }

    return keyCombos
}

/// Standard application key combos
///
/// - returns: array of key combos
public static func standardKeyCombos() -> [KeyCombo] {
    return [
        // Application
        KeyCombo(key: .comma, modifiers: .command),
        KeyCombo(key: .h, modifiers: .command),
        KeyCombo(key: .h, modifiers: [.command, .option]),
        KeyCombo(key: .q, modifiers: .command),

        // File
        KeyCombo(key: .n, modifiers: .command),
        KeyCombo(key: .o, modifiers: .command),
        KeyCombo(key: .w, modifiers: .command),
        KeyCombo(key: .s, modifiers: .command),
        KeyCombo(key: .s, modifiers: [.command, .shift]),
        KeyCombo(key: .r, modifiers: .command),
        KeyCombo(key: .p, modifiers: [.command, .shift]),
        KeyCombo(key: .p, modifiers: .command),

        // Edit
        KeyCombo(key: .z, modifiers: .command),
        KeyCombo(key: .z, modifiers: [.command, .shift]),
        KeyCombo(key: .x, modifiers: .command),
        KeyCombo(key: .c, modifiers: .command),
        KeyCombo(key: .v, modifiers: .command),
        KeyCombo(key: .v, modifiers: [.command, .option, .shift]),
        KeyCombo(key: .a, modifiers: .command),
        KeyCombo(key: .f, modifiers: .command),
        KeyCombo(key: .f, modifiers: [.command, .option]),
        KeyCombo(key: .g, modifiers: .command),
        KeyCombo(key: .g, modifiers: [.command, .shift]),
        KeyCombo(key: .e, modifiers: .command),
        KeyCombo(key: .j, modifiers: .command),
        KeyCombo(key: .semicolon, modifiers: [.command, .shift]),
        KeyCombo(key: .semicolon, modifiers: .command),

        // Format
        KeyCombo(key: .t, modifiers: .command),
        KeyCombo(key: .b, modifiers: .command),
        KeyCombo(key: .i, modifiers: .command),
        KeyCombo(key: .u, modifiers: .command),
        KeyCombo(key: .equal, modifiers: [.command, .shift]),
        KeyCombo(key: .minus, modifiers: .command),
        KeyCombo(key: .c, modifiers: [.command, .shift]),
        KeyCombo(key: .c, modifiers: [.command, .option]),
        KeyCombo(key: .v, modifiers: [.command, .option]),
        KeyCombo(key: .leftBracket, modifiers: [.command, .shift]),
        KeyCombo(key: .backslash, modifiers: [.command, .shift]),
        KeyCombo(key: .rightBracket, modifiers: [.command, .shift]),
        KeyCombo(key: .c, modifiers: [.command, .control]),
        KeyCombo(key: .v, modifiers: [.command, .control]),

        // View
        KeyCombo(key: .t, modifiers: [.command, .option]),
        KeyCombo(key: .s, modifiers: [.command, .control]),
        KeyCombo(key: .f, modifiers: [.command, .control]),

        // Window
        KeyCombo(key: .m, modifiers: .command),

        // Help
        KeyCombo(key: .slash, modifiers: [.command, .shift])
    ]
}

}