import SessionCore
import Testing
import UIKit
@testable import WebTerm

/// T-iOS-11 · KeyBar component + hardware key commands (plan §7).
/// The bar's layout mirrors `public/keybar.ts` `KEYBAR_BUTTONS` (order, glyphs,
/// captions, primary flag) and EVERY label→bytes lookup goes through
/// `KeyByteMap` — no byte literal exists in the App layer.
@MainActor
@Suite("KeyBar")
struct KeyBarTests {
    @MainActor
    private final class KeyRecorder {
        private(set) var keys: [KeyByteMap.Key] = []
        func record(_ key: KeyByteMap.Key) { keys = keys + [key] }
    }

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

    @Test("button order mirrors public/keybar.ts KEYBAR_BUTTONS exactly")
    func layoutOrderMirrorsWebKeybarButtons() {
        let expectedOrder: [KeyByteMap.Key] = [
            .esc, .escEsc, .shiftTab, .arrowUp, .arrowDown, .enter, .ctrlC,
            .ctrlR, .ctrlO, .ctrlL, .ctrlT, .ctrlB, .ctrlD, .tab,
            .arrowLeft, .arrowRight, .slash,
        ]
        #expect(KeyBarLayout.buttons.map(\.key) == expectedOrder)
    }

    @Test("glyph labels mirror the web bar; Esc is the only primary button")
    func labelsAndPrimaryFlagMirrorWeb() {
        let expectedLabels = [
            "Esc", "Esc²", "⇧Tab", "↑", "↓", "⏎", "^C", "^R", "^O", "^L",
            "^T", "^B", "^D", "Tab", "←", "→", "/",
        ]
        #expect(KeyBarLayout.buttons.map(\.label) == expectedLabels)
        #expect(KeyBarLayout.buttons.filter(\.isPrimary).map(\.key) == [.esc])
    }

    @Test("every button spec resolves to real bytes through KeyByteMap (single source of truth)")
    func everySpecResolvesThroughKeyByteMap() {
        for spec in KeyBarLayout.buttons {
            #expect(!KeyByteMap.bytes(for: spec.key).isEmpty,
                    "no bytes for \(spec.key.rawValue)")
        }
    }

    // MARK: - KeyBarView (UIKit input accessory)

    @Test("KeyBarView builds one button per spec with the web title as accessibility label")
    func keyBarViewBuildsOneButtonPerSpec() {
        // Arrange / Act
        let bar = KeyBarView()

        // Assert
        #expect(bar.keyButtons.count == KeyBarLayout.buttons.count)
        #expect(bar.keyButtons.map(\.accessibilityLabel)
            == KeyBarLayout.buttons.map(\.title))
    }

    @Test("tapping button i fires onKey with layout key i (bytes resolved downstream via KeyByteMap)")
    func tappingButtonsFiresOnKeyInLayoutOrder() {
        // Arrange
        let bar = KeyBarView()
        let recorder = KeyRecorder()
        bar.onKey = { recorder.record($0) }

        // Act: tap every button once, in order.
        for button in bar.keyButtons {
            button.sendActions(for: .touchUpInside)
        }

        // Assert
        #expect(recorder.keys == KeyBarLayout.buttons.map(\.key))
    }

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

    @Test("hardware chords: Esc, Shift+Tab and the 7 Ctrl chords map through KeyByteMap")
    func hardwareChordsCoverEscShiftTabAndCtrlChords() {
        let expected: [(KeyByteMap.Key, String, UIKeyModifierFlags)] = [
            (.esc, UIKeyCommand.inputEscape, []),
            (.shiftTab, "\t", .shift),
            (.ctrlC, "c", .control),
            (.ctrlR, "r", .control),
            (.ctrlO, "o", .control),
            (.ctrlL, "l", .control),
            (.ctrlT, "t", .control),
            (.ctrlB, "b", .control),
            (.ctrlD, "d", .control),
        ]
        #expect(HardwareKeyCommands.chords.count == expected.count)
        for (key, input, modifiers) in expected {
            let chord = HardwareKeyCommands.chords.first { $0.key == key }
            #expect(chord?.input == input, "input mismatch for \(key.rawValue)")
            #expect(chord?.modifiers == modifiers, "modifiers mismatch for \(key.rawValue)")
            #expect(!KeyByteMap.bytes(for: key).isEmpty)
        }
    }

    @Test("build() emits one prioritized UIKeyCommand per chord and key(matching:) round-trips")
    func buildEmitsPrioritizedCommandsAndRoundTrips() {
        // Arrange / Act
        let commands = HardwareKeyCommands.build(
            action: #selector(UIResponder.becomeFirstResponder) // any selector; not invoked
        )

        // Assert
        #expect(commands.count == HardwareKeyCommands.chords.count)
        for command in commands {
            #expect(command.wantsPriorityOverSystemBehavior)
            let key = HardwareKeyCommands.key(matching: command)
            #expect(key != nil, "no chord matches \(String(describing: command.input))")
        }
        #expect(Set(commands.compactMap { HardwareKeyCommands.key(matching: $0) }).count
            == HardwareKeyCommands.chords.count)
    }

    @Test("hardware mapping never hijacks plain typing keys or arrows (SwiftTerm owns them — DECCKM)")
    func hardwareMappingExcludesPlainKeysAndArrows() {
        // Arrows must stay SwiftTerm-native: a fixed \u{1B}[A override would
        // break application-cursor-keys mode (vim/htop send \u{1B}OA there).
        // Enter/Tab/"/" are ordinary typing; Esc·Esc is two Esc presses.
        let excluded: Set<KeyByteMap.Key> = [
            .arrowUp, .arrowDown, .arrowLeft, .arrowRight,
            .enter, .tab, .slash, .escEsc,
        ]
        let chordKeys = Set(HardwareKeyCommands.chords.map(\.key))
        #expect(chordKeys.isDisjoint(with: excluded))
    }
}
