import SessionCore
import UIKit

/// T-iOS-11 · Mobile key bar + hardware key mapping, mirroring
/// `public/keybar.ts` — the Claude Code keys a phone keyboard can't produce,
/// most-used first. EVERY label→bytes lookup resolves through
/// `KeyByteMap` (SessionCore) — no byte literal lives in this file.
///
/// The bar is installed as the terminal's `inputAccessoryView` and sends via
/// the ViewModel/engine DIRECTLY (bypassing SwiftTerm's text path so a tap
/// never pops or fights the soft keyboard — same reason the web bar bypasses
/// xterm and calls `ws.send`).

// MARK: - Visibility policy (T-iPad-3)

/// Pure predicate deciding whether the soft-keyboard KeyBar
/// (`inputAccessoryView`) should show — THE single decision point (mirrors
/// `PrivacyShadePolicy` / `LayoutPolicy`, no scattered checks in views):
/// - a hardware keyboard makes the on-screen key bar redundant → default hidden;
/// - no hardware keyboard → shown (unchanged iPhone behavior — zero regression);
/// - an explicit user toggle (`userOverride`) always wins over the auto default.
///
/// Hardware presence is injected (`GCKeyboard.coalesced != nil` at the call
/// site) so this stays a fast, device-agnostic unit (`KeyBarVisibilityTests`).
enum KeyBarVisibility {
    /// - Parameters:
    ///   - hardwareKeyboardPresent: injected `GCKeyboard.coalesced != nil`.
    ///   - userOverride: nil = follow the auto default; true/false = the user
    ///     explicitly forced show/hide (wins over the hardware-driven default).
    static func isVisible(hardwareKeyboardPresent: Bool, userOverride: Bool?) -> Bool {
        if let userOverride { return userOverride }
        return !hardwareKeyboardPresent
    }
}

// MARK: - Layout data (mirror of KEYBAR_BUTTONS)

/// One key-bar button: glyph, short function caption (shown under the glyph,
/// self-documenting on touch) and the full title for accessibility.
struct KeyBarButtonSpec: Equatable, Sendable {
    let key: KeyByteMap.Key
    let label: String
    let caption: String
    let title: String
    let isPrimary: Bool

    init(key: KeyByteMap.Key, label: String, caption: String, title: String,
         isPrimary: Bool = false) {
        self.key = key
        self.label = label
        self.caption = caption
        self.title = title
        self.isPrimary = isPrimary
    }
}

/// Button order/copy transcribed from `public/keybar.ts` `KEYBAR_BUTTONS`
/// (most-used Claude Code keys first). The 🎤 voice button is P2 (T-iOS-31).
enum KeyBarLayout {
    static let buttons: [KeyBarButtonSpec] = [
        KeyBarButtonSpec(key: .esc, label: "Esc", caption: "中断",
                         title: "Esc — interrupt Claude / dismiss", isPrimary: true),
        KeyBarButtonSpec(key: .escEsc, label: "Esc²", caption: "回溯",
                         title: "Esc Esc — rewind / clear draft"),
        KeyBarButtonSpec(key: .shiftTab, label: "⇧Tab", caption: "模式",
                         title: "Shift+Tab — cycle plan / auto-accept mode"),
        KeyBarButtonSpec(key: .arrowUp, label: "↑", caption: "上一个",
                         title: "Up — previous option / history"),
        KeyBarButtonSpec(key: .arrowDown, label: "↓", caption: "下一个",
                         title: "Down — next option / history"),
        KeyBarButtonSpec(key: .enter, label: "⏎", caption: "确认",
                         title: "Enter — confirm"),
        KeyBarButtonSpec(key: .ctrlC, label: "^C", caption: "取消",
                         title: "Ctrl+C — cancel / quit"),
        KeyBarButtonSpec(key: .ctrlR, label: "^R", caption: "搜历史",
                         title: "Ctrl+R — reverse-search command history"),
        KeyBarButtonSpec(key: .ctrlO, label: "^O", caption: "详情",
                         title: "Ctrl+O — expand transcript / tool detail"),
        KeyBarButtonSpec(key: .ctrlL, label: "^L", caption: "重绘",
                         title: "Ctrl+L — redraw screen"),
        KeyBarButtonSpec(key: .ctrlT, label: "^T", caption: "任务",
                         title: "Ctrl+T — toggle task list"),
        KeyBarButtonSpec(key: .ctrlB, label: "^B", caption: "后台",
                         title: "Ctrl+B — background running task"),
        KeyBarButtonSpec(key: .ctrlD, label: "^D", caption: "退出",
                         title: "Ctrl+D — exit session (EOF)"),
        KeyBarButtonSpec(key: .tab, label: "Tab", caption: "补全",
                         title: "Tab — complete / toggle"),
        KeyBarButtonSpec(key: .arrowLeft, label: "←", caption: "左移",
                         title: "Left — move cursor left"),
        KeyBarButtonSpec(key: .arrowRight, label: "→", caption: "右移",
                         title: "Right — move cursor right"),
        KeyBarButtonSpec(key: .slash, label: "/", caption: "命令",
                         title: "Slash — command launcher"),
    ]
}

/// Layout constants — all composed from the frozen `DS` scale (no literals).
/// UIKit reads the same CGFloat tokens the SwiftUI chrome uses, so the key bar
/// stays on-grid with the rest of the app.
private enum KeyBarMetrics {
    /// A hit-target-tall row plus a little breathing room (44 + 8).
    static let barHeight: CGFloat = DS.Layout.minHitTarget + DS.Space.sm8
    static let buttonSpacing: CGFloat = DS.Space.xs4
    static let contentInset: CGFloat = DS.Space.sm8
    static let buttonInsets = NSDirectionalEdgeInsets(
        top: DS.Space.xs4, leading: DS.Space.md12,
        bottom: DS.Space.xs4, trailing: DS.Space.md12
    )
}

// MARK: - KeyBarView (inputAccessoryView)

/// Horizontally scrolling key bar, installed as the terminal's
/// `inputAccessoryView`. Taps call `onKey` — the screen routes them to
/// `TerminalViewModel.send(key:)`, which resolves bytes via `KeyByteMap`.
final class KeyBarView: UIInputView {
    /// Tap outlet. `@MainActor`-typed so the handler can drive the ViewModel.
    var onKey: (@MainActor (KeyByteMap.Key) -> Void)?
    /// Built buttons in layout order (test-visible).
    private(set) var keyButtons: [UIButton] = []

    init() {
        super.init(
            frame: CGRect(x: 0, y: 0, width: 0, height: KeyBarMetrics.barHeight),
            inputViewStyle: .keyboard
        )
        allowsSelfSizing = true
        buildBar()
    }

    @available(*, unavailable, message: "KeyBarView is code-built only")
    required init?(coder: NSCoder) {
        return nil
    }

    override var intrinsicContentSize: CGSize {
        CGSize(width: UIView.noIntrinsicMetric, height: KeyBarMetrics.barHeight)
    }

    private func buildBar() {
        let stack = UIStackView()
        stack.axis = .horizontal
        stack.spacing = KeyBarMetrics.buttonSpacing
        stack.alignment = .center
        stack.translatesAutoresizingMaskIntoConstraints = false

        for spec in KeyBarLayout.buttons {
            let button = makeButton(for: spec)
            keyButtons = keyButtons + [button]
            stack.addArrangedSubview(button)
        }

        let scroll = UIScrollView()
        scroll.showsHorizontalScrollIndicator = false
        scroll.translatesAutoresizingMaskIntoConstraints = false
        scroll.addSubview(stack)
        addSubview(scroll)

        NSLayoutConstraint.activate([
            scroll.leadingAnchor.constraint(equalTo: leadingAnchor),
            scroll.trailingAnchor.constraint(equalTo: trailingAnchor),
            scroll.topAnchor.constraint(equalTo: topAnchor),
            scroll.bottomAnchor.constraint(equalTo: bottomAnchor),
            stack.leadingAnchor.constraint(
                equalTo: scroll.contentLayoutGuide.leadingAnchor,
                constant: KeyBarMetrics.contentInset
            ),
            stack.trailingAnchor.constraint(
                equalTo: scroll.contentLayoutGuide.trailingAnchor,
                constant: -KeyBarMetrics.contentInset
            ),
            stack.topAnchor.constraint(equalTo: scroll.contentLayoutGuide.topAnchor),
            stack.bottomAnchor.constraint(equalTo: scroll.contentLayoutGuide.bottomAnchor),
            stack.heightAnchor.constraint(equalTo: scroll.frameLayoutGuide.heightAnchor),
        ])
    }

    private func makeButton(for spec: KeyBarButtonSpec) -> UIButton {
        // Keycap look: primary (Esc) wears the accent tint, the rest a subtle
        // gray fill; both get an sm8 rounded corner. (`.secondaryLabel` is the
        // UIKit twin of DS.Palette.textSecondary — the DS UIColor surface only
        // vends the accent, so native system labels stand in at this boundary.)
        var config: UIButton.Configuration = spec.isPrimary ? .tinted() : .gray()
        config.cornerStyle = .fixed
        config.background.cornerRadius = DS.Radius.sm8
        var label = AttributedString(spec.label)
        label.font = UIFont.monospacedSystemFont(
            ofSize: UIFont.preferredFont(forTextStyle: .footnote).pointSize,
            weight: .semibold
        )
        config.attributedTitle = label
        var caption = AttributedString(spec.caption)
        caption.font = UIFont.preferredFont(forTextStyle: .caption2)
        caption.foregroundColor = .secondaryLabel
        config.attributedSubtitle = caption
        config.titleAlignment = .center
        config.contentInsets = KeyBarMetrics.buttonInsets

        let button = UIButton(configuration: config)
        if spec.isPrimary {
            button.tintColor = DS.Palette.accentUIColor()
        }
        button.accessibilityLabel = spec.title
        // HIG minimum touch target regardless of glyph width.
        button.heightAnchor
            .constraint(greaterThanOrEqualToConstant: DS.Layout.minHitTarget)
            .isActive = true
        button.addAction(
            UIAction { [weak self] _ in self?.onKey?(spec.key) },
            for: .touchUpInside
        )
        return button
    }
}

// MARK: - Hardware keyboard (UIKeyCommand, same KeyByteMap mapping)

/// Hardware-keyboard chords routed through the SAME `KeyByteMap` mapping
/// (plan §7 T-iOS-11). Scope decision (documented deviation): only Esc,
/// Shift+Tab and the Ctrl chords are registered —
/// - arrows stay SwiftTerm-native: a fixed CSI override would break
///   application-cursor-keys mode (DECCKM: vim/htop expect `ESC O A`);
/// - Enter/Tab/"/" are ordinary typing SwiftTerm already delivers;
/// - Esc·Esc is simply two Esc presses.
/// UIKeyCommand interception replaces (never duplicates) the responder's own
/// press handling, so each chord is sent exactly once.
enum HardwareKeyCommands {
    struct Chord: Equatable {
        let key: KeyByteMap.Key
        let input: String
        let modifiers: UIKeyModifierFlags
    }

    static let chords: [Chord] = [
        Chord(key: .esc, input: UIKeyCommand.inputEscape, modifiers: []),
        Chord(key: .shiftTab, input: "\t", modifiers: .shift),
        Chord(key: .ctrlC, input: "c", modifiers: .control),
        Chord(key: .ctrlR, input: "r", modifiers: .control),
        Chord(key: .ctrlO, input: "o", modifiers: .control),
        Chord(key: .ctrlL, input: "l", modifiers: .control),
        Chord(key: .ctrlT, input: "t", modifiers: .control),
        Chord(key: .ctrlB, input: "b", modifiers: .control),
        Chord(key: .ctrlD, input: "d", modifiers: .control),
    ]

    /// One prioritized `UIKeyCommand` per chord, all firing `action`.
    /// (`UIKeyCommand` is `@MainActor` in the SDK, hence the isolation.)
    @MainActor
    static func build(action: Selector) -> [UIKeyCommand] {
        chords.map { chord in
            let command = UIKeyCommand(
                action: action, input: chord.input, modifierFlags: chord.modifiers
            )
            command.wantsPriorityOverSystemBehavior = true
            return command
        }
    }

    /// Reverse lookup for the command handler: which chord fired.
    @MainActor
    static func key(matching command: UIKeyCommand) -> KeyByteMap.Key? {
        chords.first {
            $0.input == command.input && $0.modifiers == command.modifierFlags
        }?.key
    }
}
