package wang.yaojia.webterm.session

/**
 * Key-name → terminal byte-string table (A6; ports iOS `SessionCore.KeyByteMap`;
 * pure data, no I/O).
 *
 * Byte-for-byte mirror of the web client's `public/keybar.ts` `KEY_MAP` — the
 * SINGLE source of truth for every label→bytes lookup: the KeyBar buttons AND the
 * hardware-key mapping both resolve through here (plan A17/A25). Hand-writing an
 * escape sequence anywhere in the App layer is a review finding.
 *
 * Control bytes are built from explicit hex code points (`Char(0x1B)` etc.) that
 * map 1:1 to the web `\xNN` escapes — never a raw control byte in source. Gotcha
 * carried over from the web client: Enter is `\r` (0x0D, carriage return), NOT
 * `\n` — synthesized input with a linefeed breaks TUIs (CLAUDE.md gotcha).
 */
public object KeyByteMap {
    private const val ESC_CODE = 0x1B

    /**
     * One key the bar / hardware mapping can send. [webName] mirrors the web
     * `KEY_MAP` property name (iOS's `enum Key: String` raw value); declaration
     * order mirrors the web table.
     */
    public enum class Key(public val webName: String) {
        ESC("esc"),
        ESC_ESC("escEsc"),
        SHIFT_TAB("shiftTab"),
        ARROW_UP("arrowUp"),
        ARROW_DOWN("arrowDown"),
        ARROW_LEFT("arrowLeft"),
        ARROW_RIGHT("arrowRight"),
        ENTER("enter"),
        CTRL_C("ctrlC"),
        CTRL_R("ctrlR"),
        CTRL_O("ctrlO"),
        CTRL_L("ctrlL"),
        CTRL_T("ctrlT"),
        CTRL_B("ctrlB"),
        CTRL_D("ctrlD"),
        TAB("tab"),
        SLASH("slash"),
    }

    /**
     * The exact byte string to send as `ClientMessage.Input(data)` for [key].
     * Values are verbatim from public/keybar.ts KEY_MAP (see `web '\xNN'` notes);
     * never edit one side alone.
     */
    public fun bytes(key: Key): String = when (key) {
        Key.ESC -> esc()                          // web '\x1b'
        Key.ESC_ESC -> esc() + esc()              // web '\x1b\x1b'
        Key.SHIFT_TAB -> esc() + "[Z"             // web '\x1b[Z'
        Key.ARROW_UP -> esc() + "[A"              // web '\x1b[A'
        Key.ARROW_DOWN -> esc() + "[B"            // web '\x1b[B'
        Key.ARROW_LEFT -> esc() + "[D"            // web '\x1b[D'
        Key.ARROW_RIGHT -> esc() + "[C"           // web '\x1b[C'
        Key.ENTER -> "\r"                         // web '\r' — NOT "\n" (CLAUDE.md gotcha)
        Key.CTRL_C -> ctrl(0x03)                  // web '\x03'
        Key.CTRL_R -> ctrl(0x12)                  // web '\x12'
        Key.CTRL_O -> ctrl(0x0F)                  // web '\x0f'
        Key.CTRL_L -> ctrl(0x0C)                  // web '\x0c'
        Key.CTRL_T -> ctrl(0x14)                  // web '\x14'
        Key.CTRL_B -> ctrl(0x02)                  // web '\x02'
        Key.CTRL_D -> ctrl(0x04)                  // web '\x04'
        Key.TAB -> "\t"                           // web '\t'
        Key.SLASH -> "/"                          // web '/'
    }

    private fun esc(): String = ctrl(ESC_CODE)

    /** A single control byte as a 1-char String (matches the web `\xNN` escape). */
    private fun ctrl(code: Int): String = Char(code).toString()
}
