from pathlib import Path
import re


REPO = Path(__file__).resolve().parent.parent


def read(rel: str) -> str:
    return (REPO / rel).read_text(encoding="utf-8")


def _locale_blocks(src: str) -> dict[str, str]:
    matches = list(
        re.finditer(
            r"\n  (?:(['\"])([A-Za-z][A-Za-z0-9-]*)\1|([A-Za-z][A-Za-z0-9-]*)): \{",
            src,
        )
    )
    blocks: dict[str, str] = {}
    for idx, match in enumerate(matches):
        start = match.end()
        end = matches[idx + 1].start() if idx + 1 < len(matches) else src.rfind("\n};")
        blocks[match.group(2) or match.group(3)] = src[start:end]
    return blocks


def test_selected_text_reply_button_is_selection_scoped_and_frontend_only():
    js = read("static/messages.js")

    assert "window.getSelection" in js
    assert "selection.isCollapsed" in js
    assert "range.getBoundingClientRect" in js
    assert "_selectedTextReplyRoot" in js
    assert "$('messages')||$('msgInner')" in js
    assert "root.contains(el)" in js
    assert "document.addEventListener('selectionchange', _updateSelectedTextReplyButton)" in js

    assert "id='selectedTextReplyBtn'" in js
    assert "selected-text-reply-btn" in js
    assert "data-i18n', 'selected_text_reply'" in js
    assert "data-i18n-title', 'selected_text_reply_title'" in js
    assert "data-i18n-aria-label', 'selected_text_reply_title'" in js

    # MVP contract: selected text reply is entirely static/frontend; do not add
    # backend endpoints or change send payload routing.
    assert "/api/selected" not in js
    assert "selected_text" not in js.replace("selected_text_reply", "")


def test_selected_text_reply_appends_blockquote_and_preserves_draft_flow():
    js = read("static/messages.js")

    assert "function _formatSelectedTextReplyQuote" in js
    assert "replace(/\\r\\n?/g,'\\n')" in js
    assert "replace(/\\n{3,}/g,'\\n\\n')" in js
    assert "map(line=>`> ${line}`).join('\\n')" in js

    assert "function _appendSelectedTextReplyToComposer" in js
    assert "$('msg')" in js
    assert "current.trim()?" in js
    assert "${quote}\\n\\n" in js
    assert "composer.dispatchEvent(new Event('input', {bubbles:true}))" in js
    assert "if(typeof autoResize==='function') autoResize()" in js


def test_selected_text_reply_styles_and_i18n_exist_for_all_locales():
    css = read("static/style.css")
    i18n = read("static/i18n.js")

    assert ".selected-text-reply-btn" in css
    assert ".selected-text-reply-btn.visible" in css
    assert "position:fixed" in css
    assert "pointer-events:none" in css
    assert "pointer-events:auto" in css
    assert "border:2px solid var(--accent)" in css
    assert "background:var(--bg)" in css
    assert "color:var(--text)" in css
    assert "outline:2px solid var(--focus-ring)" in css

    blocks = _locale_blocks(i18n)
    assert blocks, "No locale blocks found"
    assert "zh-Hant" in blocks, "Locale parser must include quoted script locales"
    required = {
        "selected_text_reply",
        "selected_text_reply_title",
        "selected_text_reply_appended",
    }
    key_pattern = re.compile(r"^\s{4}([a-zA-Z0-9_]+):", re.MULTILINE)
    for locale, block in blocks.items():
        keys = set(key_pattern.findall(block))
        missing = sorted(required - keys)
        assert not missing, f"{locale} missing selected-text reply keys: {missing}"


def test_selected_text_reply_button_has_user_select_none():
    css = read("static/style.css")
    # The base rule must carry user-select:none so browser selection never
    # renders on or through the button, regardless of hover background opacity.
    assert "user-select:none" in css
    # Confirm it lives inside the .selected-text-reply-btn rule, not elsewhere.
    rule_match = re.search(
        r'\.selected-text-reply-btn\{[^}]*user-select:none[^}]*\}', css
    )
    assert rule_match, ".selected-text-reply-btn base rule must include user-select:none"
