package wang.yaojia.webterm.components

import android.content.res.Configuration
import android.view.KeyEvent
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import wang.yaojia.webterm.designsystem.LayoutTokens
import wang.yaojia.webterm.designsystem.Opacity
import wang.yaojia.webterm.designsystem.Radius
import wang.yaojia.webterm.designsystem.Spacing
import wang.yaojia.webterm.designsystem.WebTermTheme
import wang.yaojia.webterm.session.KeyByteMap

/**
 * # KeyBar (A17) — the mobile IME-bypass touch key-bar + hardware-chord router.
 *
 * The Android port of `public/keybar.ts`: a horizontal strip of the Claude Code
 * high-frequency keys a phone soft keyboard can't easily produce (Esc / Esc² /
 * ⇧Tab / arrows / Enter / Ctrl-chords / Tab / `/`), most-used first.
 *
 * ### Byte source of truth (plan §6.3, A6)
 * Every label→bytes lookup — buttons AND hardware chords — resolves through
 * [KeyByteMap] (byte-for-byte matched to the web `KEY_MAP`). NO escape sequence is
 * ever hand-written here; hand-writing `ESC[A` etc. is a review finding.
 *
 * ### IME bypass (plan §6.3 source #2, R8)
 * A key-bar tap calls [emitKey] → `onSend(bytes)` DIRECTLY, mirroring the web
 * `ws.send` bypass. It never routes through a `BasicTextField` / the emulator's
 * `InputConnection`, so tapping a key never pops the soft keyboard. `onSend` is
 * wired by A21 to `TerminalSessionController.sendInput`.
 *
 * ### DECCKM split (plan §6.3 source #3)
 * Hardware keys are routed by [HardwareKeyRouter]: Esc / Ctrl-<letter> / ⇧Tab are
 * mapped app-side via [KeyByteMap]; **arrows / Enter / (plain) Tab are LEFT to
 * Termux's `KeyHandler`** (via `RemoteTerminalView.onKeyCommand` returning false)
 * so `cursorKeysApplication` (DECCKM) still emits `ESC O A` — never `ESC [ A` —
 * for vim/htop.
 *
 * Compose layout / IME-inset behaviour / real key events are DEVICE-QA (plan §7);
 * the byte contract + the routing decision are the JVM-tested core.
 */

/** One key-bar button: fixed glyph + Chinese caption + the [KeyByteMap.Key] it emits. */
public data class KeyBarButton(
    val label: String,
    val caption: String,
    val key: KeyByteMap.Key,
    val title: String,
    val isPrimary: Boolean = false,
)

/**
 * Button layout — most-used Claude Code keys first, mirroring the web
 * `KEYBAR_BUTTONS` (public/keybar.ts) order, glyphs and captions 1:1. The 🎤 voice
 * button (web A2) is out of scope for A17.
 */
public val KEYBAR_LAYOUT: List<KeyBarButton> = listOf(
    KeyBarButton("Esc", "中断", KeyByteMap.Key.ESC, "Esc — interrupt Claude / dismiss", isPrimary = true),
    KeyBarButton("Esc²", "回溯", KeyByteMap.Key.ESC_ESC, "Esc Esc — rewind / clear draft"),
    KeyBarButton("⇧Tab", "模式", KeyByteMap.Key.SHIFT_TAB, "Shift+Tab — cycle plan / auto-accept mode"),
    KeyBarButton("↑", "上一个", KeyByteMap.Key.ARROW_UP, "Up — previous option / history"),
    KeyBarButton("↓", "下一个", KeyByteMap.Key.ARROW_DOWN, "Down — next option / history"),
    KeyBarButton("⏎", "确认", KeyByteMap.Key.ENTER, "Enter — confirm"),
    KeyBarButton("^C", "取消", KeyByteMap.Key.CTRL_C, "Ctrl+C — cancel / quit"),
    KeyBarButton("^R", "搜历史", KeyByteMap.Key.CTRL_R, "Ctrl+R — reverse-search command history"),
    KeyBarButton("^O", "详情", KeyByteMap.Key.CTRL_O, "Ctrl+O — expand transcript / tool detail"),
    KeyBarButton("^L", "重绘", KeyByteMap.Key.CTRL_L, "Ctrl+L — redraw screen"),
    KeyBarButton("^T", "任务", KeyByteMap.Key.CTRL_T, "Ctrl+T — toggle task list"),
    KeyBarButton("^B", "后台", KeyByteMap.Key.CTRL_B, "Ctrl+B — background running task"),
    KeyBarButton("^D", "退出", KeyByteMap.Key.CTRL_D, "Ctrl+D — exit session (EOF)"),
    KeyBarButton("Tab", "补全", KeyByteMap.Key.TAB, "Tab — complete / toggle"),
    KeyBarButton("←", "左移", KeyByteMap.Key.ARROW_LEFT, "Left — move cursor left"),
    KeyBarButton("→", "右移", KeyByteMap.Key.ARROW_RIGHT, "Right — move cursor right"),
    KeyBarButton("/", "命令", KeyByteMap.Key.SLASH, "Slash — command launcher"),
)

/**
 * The single tap→wire funnel: resolve [key]'s bytes through [KeyByteMap] and hand
 * them VERBATIM to [sink] (invariant #9 — no content filtering). This is exactly
 * what a button's `onClick` invokes, and what the JVM byte-contract test drives.
 */
internal fun emitKey(key: KeyByteMap.Key, sink: (String) -> Unit) {
    sink(KeyByteMap.bytes(key))
}

/** Web parity: the key-bar is a mobile affordance, hidden on wide screens (web hides it >768px). */
internal const val WIDE_SCREEN_MAX_DP: Int = 768

/**
 * Whether the key-bar should render: shown on narrow (phone-width) screens only,
 * AND hidden when a hardware keyboard is present (the physical keys make the
 * touch bar redundant — the iPad-equivalent auto-hide, plan §"iPad-equivalent").
 */
internal fun shouldShowKeyBar(screenWidthDp: Int, hardwareKeyboardPresent: Boolean): Boolean =
    screenWidthDp <= WIDE_SCREEN_MAX_DP && !hardwareKeyboardPresent

/**
 * Best-effort hardware-keyboard heuristic from the current [Configuration]
 * (accepted-less-reliable, plan §"iPad-equivalent" — no `GCKeyboard` equivalent):
 * an alphabetic keyboard that is currently exposed (lid open / dock attached).
 */
internal fun isHardwareKeyboardPresent(keyboard: Int, hardKeyboardHidden: Int): Boolean =
    keyboard == Configuration.KEYBOARD_QWERTY && hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO

/**
 * The mobile key-bar overlay. Pinned ABOVE the IME via [WindowInsets.ime] (falling
 * back to the navigation-bar inset when the keyboard is hidden), horizontally
 * scrollable so every key stays reachable on a narrow phone. Renders nothing on
 * wide screens or when a hardware keyboard is present ([shouldShowKeyBar]).
 *
 * @param onSend the IME-bypass sink — A21 wires it to `TerminalSessionController.sendInput`.
 */
@Composable
public fun KeyBar(
    onSend: (String) -> Unit,
    modifier: Modifier = Modifier,
) {
    val config = LocalConfiguration.current
    val hardwareKeyboard = isHardwareKeyboardPresent(config.keyboard, config.hardKeyboardHidden)
    if (!shouldShowKeyBar(config.screenWidthDp, hardwareKeyboard)) return

    Row(
        modifier = modifier
            .fillMaxWidth()
            .windowInsetsPadding(WindowInsets.ime.union(WindowInsets.navigationBars))
            .horizontalScroll(rememberScrollState())
            .padding(horizontal = Spacing.sm8, vertical = Spacing.xs4),
        horizontalArrangement = Arrangement.spacedBy(Spacing.xs4),
    ) {
        for (button in KEYBAR_LAYOUT) {
            KeyBarKey(button = button, onSend = onSend)
        }
    }
}

/** One inert key button. A tap emits bytes directly via [emitKey] — never through a text field. */
@Composable
private fun KeyBarKey(button: KeyBarButton, onSend: (String) -> Unit) {
    val container =
        if (button.isPrimary) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant
    val content =
        if (button.isPrimary) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface

    Surface(
        onClick = { emitKey(button.key, onSend) },
        shape = RoundedCornerShape(Radius.sm8),
        color = container,
        contentColor = content,
        modifier = Modifier
            .defaultMinSize(minWidth = LayoutTokens.minHitTarget, minHeight = LayoutTokens.minHitTarget)
            .semantics { contentDescription = button.title },
    ) {
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
            modifier = Modifier.padding(horizontal = Spacing.sm8, vertical = Spacing.xs4),
        ) {
            Text(text = button.label, style = MaterialTheme.typography.labelLarge)
            Text(
                text = button.caption,
                style = MaterialTheme.typography.labelSmall,
                color = content.copy(alpha = Opacity.stale),
            )
        }
    }
}

/**
 * The DECCKM split router (plan §6.3 source #3): decides whether a hardware key is
 * handled app-side (mapped through [KeyByteMap]) or LEFT to Termux's `KeyHandler`.
 *
 * Pure decision logic (primitive keyCode + modifier booleans, mirroring the Android
 * `KeyEvent` constants) so it is JVM-unit-testable with no device / Robolectric.
 * The Android glue lives in [handle].
 */
public object HardwareKeyRouter {

    /** The ONLY Ctrl-<letter> chords the app claims — exactly the web key-bar's control keys. */
    private val CTRL_LETTER_KEYS: Map<Int, KeyByteMap.Key> = mapOf(
        KeyEvent.KEYCODE_C to KeyByteMap.Key.CTRL_C,
        KeyEvent.KEYCODE_R to KeyByteMap.Key.CTRL_R,
        KeyEvent.KEYCODE_O to KeyByteMap.Key.CTRL_O,
        KeyEvent.KEYCODE_L to KeyByteMap.Key.CTRL_L,
        KeyEvent.KEYCODE_T to KeyByteMap.Key.CTRL_T,
        KeyEvent.KEYCODE_B to KeyByteMap.Key.CTRL_B,
        KeyEvent.KEYCODE_D to KeyByteMap.Key.CTRL_D,
    )

    /**
     * Resolve a hardware key to a routing decision.
     *
     * - **⇧Tab** → app-handled `ESC[Z`; **plain Tab** defers (completion / DECCKM nav).
     * - **Esc** (unmodified) → app-handled `ESC`.
     * - **Ctrl+{C,R,O,L,T,B,D}** → app-handled control byte via [KeyByteMap].
     * - **Everything else** (arrows, Enter, plain Tab, plain letters, unmapped Ctrl
     *   chords) → [HardwareKeyResult.DeferToTerminal] so Termux's `KeyHandler`
     *   emits the DECCKM-correct sequence (`ESC O A` under application-cursor mode).
     */
    public fun resolve(keyCode: Int, ctrl: Boolean, shift: Boolean, alt: Boolean): HardwareKeyResult {
        // ⇧Tab is app-handled; plain Tab is left to Termux (do this BEFORE the generic defer).
        if (keyCode == KeyEvent.KEYCODE_TAB) {
            return if (shift && !ctrl && !alt) app(KeyByteMap.Key.SHIFT_TAB) else HardwareKeyResult.DeferToTerminal
        }
        // Esc → interrupt Claude; a modified Escape is not our chord.
        if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
            return if (!ctrl && !shift && !alt) app(KeyByteMap.Key.ESC) else HardwareKeyResult.DeferToTerminal
        }
        // The mapped Ctrl-<letter> chords only; unmapped Ctrl letters defer to Termux.
        if (ctrl && !alt) {
            CTRL_LETTER_KEYS[keyCode]?.let { return app(it) }
        }
        // Arrows / Enter / plain Tab / plain keys → Termux KeyHandler (DECCKM-correct, §6.3).
        return HardwareKeyResult.DeferToTerminal
    }

    /**
     * Android glue for `RemoteTerminalView.onKeyCommand`: extract modifiers from
     * [event], route via [resolve], and on an app-handled DOWN emit the bytes to
     * [onSend], returning true to CONSUME the event. Returns false otherwise so the
     * stock Termux path (`KeyHandler`) handles arrows/Enter/Tab. Device-QA'd
     * (touches `KeyEvent` accessor methods).
     */
    public fun handle(keyCode: Int, event: KeyEvent, onSend: (String) -> Unit): Boolean {
        if (event.action != KeyEvent.ACTION_DOWN) return false
        return when (val result = resolve(keyCode, event.isCtrlPressed, event.isShiftPressed, event.isAltPressed)) {
            is HardwareKeyResult.AppHandled -> {
                onSend(result.bytes)
                true
            }

            HardwareKeyResult.DeferToTerminal -> false
        }
    }

    private fun app(key: KeyByteMap.Key): HardwareKeyResult =
        HardwareKeyResult.AppHandled(KeyByteMap.bytes(key))
}

/** The routing decision for one hardware key (the DECCKM split, plan §6.3). */
public sealed interface HardwareKeyResult {
    /** The app consumes the key and sends these exact [bytes] (resolved via [KeyByteMap]). */
    public data class AppHandled(val bytes: String) : HardwareKeyResult

    /** The key is left to Termux's `KeyHandler` so DECCKM stays correct (arrows/Enter/Tab). */
    public data object DeferToTerminal : HardwareKeyResult
}

// ── Preview ───────────────────────────────────────────────────────────────────

@Preview(name = "KeyBar")
@Composable
private fun KeyBarPreview() {
    WebTermTheme {
        KeyBar(onSend = {})
    }
}
