"""Contract tests for the composer's pure JS logic (Enter-to-send, IME, option
building). No browser: the functions between the TESTABLE markers in web.py are
extracted verbatim and run under Node, the smallest thing that actually executes
JavaScript the way the browser will.
"""

import re
import subprocess

from dual_agent.web import INDEX_HTML


def _testable_js() -> str:
    match = re.search(r"// TESTABLE:BEGIN(.*?)// TESTABLE:END", INDEX_HTML, re.S)
    assert match, "composer test hooks are missing from web.py"
    return match.group(1)


def _run_node(js_body: str) -> None:
    script = _testable_js() + "\n" + js_body
    result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
    assert result.returncode == 0, result.stderr or result.stdout


def test_plain_enter_sends():
    _run_node(
        "const assert = require('assert');\n"
        "assert.strictEqual(\n"
        "  shouldSendOnEnter({key: 'Enter', shiftKey: false, isComposing: false, keyCode: 13}),\n"
        "  true\n"
        ");\n"
    )


def test_shift_enter_is_a_newline_not_a_send():
    _run_node(
        "const assert = require('assert');\n"
        "assert.strictEqual(\n"
        "  shouldSendOnEnter({key: 'Enter', shiftKey: true, isComposing: false, keyCode: 13}),\n"
        "  false\n"
        ");\n"
    )


def test_ime_composing_enter_does_not_send():
    _run_node(
        "const assert = require('assert');\n"
        "assert.strictEqual(\n"
        "  shouldSendOnEnter({key: 'Enter', shiftKey: false, isComposing: true, keyCode: 13}),\n"
        "  false\n"
        ");\n"
    )


def test_ime_legacy_keycode_229_does_not_send():
    _run_node(
        "const assert = require('assert');\n"
        "assert.strictEqual(\n"
        "  shouldSendOnEnter({key: 'Enter', shiftKey: false, isComposing: false, keyCode: 229}),\n"
        "  false\n"
        ");\n"
    )


def test_non_enter_key_does_not_send():
    _run_node(
        "const assert = require('assert');\n"
        "assert.strictEqual(\n"
        "  shouldSendOnEnter({key: 'a', shiftKey: false, isComposing: false, keyCode: 65}),\n"
        "  false\n"
        ");\n"
    )


def test_select_options_html_comes_only_from_the_values_given():
    _run_node(
        "const assert = require('assert');\n"
        "const html = selectOptionsHtml(['', 'opus', 'sonnet'], 'opus');\n"
        "assert.strictEqual((html.match(/<option/g) || []).length, 3);\n"
        "assert.ok(html.includes('>默认</option>'));\n"
        "assert.ok(/value=\"opus\"[^>]*selected/.test(html));\n"
    )


def test_tuning_from_selections_omits_empty_roles_keeps_chosen_ones():
    _run_node(
        "const assert = require('assert');\n"
        "const t = tuningFromSelections({\n"
        "  planner: {model: '', effort: ''},\n"
        "  implementer: {model: 'opus', effort: 'high'},\n"
        "});\n"
        "assert.deepStrictEqual(Object.keys(t), ['implementer']);\n"
        "assert.deepStrictEqual(t.implementer, {model: 'opus', effort: 'high'});\n"
    )


def test_composer_placeholder_updated_and_existing_guards_untouched():
    """shouldSendOnEnter is new; the empty-message and busy guards it relies on already
    existed and must still be there for it to be safe to wire straight to send.
    """
    assert "Enter 发送，Shift+Enter 换行" in INDEX_HTML
    assert re.search(r"if\s*\(\s*busy\s*\)", INDEX_HTML)
    assert re.search(r"if\s*\(\s*!text\s*\)\s*return;", INDEX_HTML)
    assert "shouldSendOnEnter(e)" in INDEX_HTML

