import json
import subprocess
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
MENTIONS = ROOT / "frontend" / "mention-composer.js"


def run_node(script: str) -> dict:
    completed = subprocess.run(
        ["node", "-e", script],
        check=True,
        capture_output=True,
        text=True,
    )
    return json.loads(completed.stdout)


def test_active_mention_replaces_only_the_token_at_the_caret():
    script = f"""
const mentions=require({json.dumps(str(MENTIONS))});
const text='Thanks @al for this; keep @casey informed';
const caret=text.indexOf(' for');
const active=mentions.activeMention(text, caret);
const inserted=mentions.insertMention(text, active, 'alex');
process.stdout.write(JSON.stringify({{active,inserted}}));
"""

    result = run_node(script)

    assert result["active"] == {"start": 7, "end": 10, "query": "al"}
    assert result["inserted"] == {
        "value": "Thanks @alex for this; keep @casey informed",
        "caret": 13,
    }


def test_controller_ignores_stale_results_and_keyboard_selects_a_teammate():
    script = f"""
const mentions=require({json.dumps(str(MENTIONS))});
class Element {{
  constructor() {{ this.listeners={{}}; this.children=[]; this.attrs={{}}; this.dataset={{}}; this.hidden=true; this.value=''; this.selectionStart=0; this.selectionEnd=0; this.textContent=''; }}
  addEventListener(type, fn) {{ (this.listeners[type] ||= []).push(fn); }}
  dispatch(type, extra={{}}) {{ const event={{preventDefault(){{ this.prevented=true; }}, ...extra}}; (this.listeners[type] || []).forEach(fn=>fn(event)); return event; }}
  replaceChildren(...children) {{ this.children=children; }}
  appendChild(child) {{ this.children.push(child); }}
  setAttribute(name, value) {{ this.attrs[name]=String(value); }}
  removeAttribute(name) {{ delete this.attrs[name]; }}
  setSelectionRange(start, end) {{ this.selectionStart=start; this.selectionEnd=end; }}
  focus() {{ this.focused=true; }}
}}
const textarea=new Element(), listbox=new Element(), status=new Element();
const pending=[];
const controller=mentions.create({{
  textarea,listbox,status,getRepository:()=> 'stackchain/api',
  loadCandidates:(repository, query)=>new Promise(resolve=>pending.push({{repository,query,resolve}})),
  setTimer:fn=>{{ fn(); return 1; }}, clearTimer:()=>{{}},
  createOption:()=>new Element(),
}});
controller.start();
(async()=>{{
  textarea.value='Ping @al'; textarea.selectionStart=textarea.value.length; textarea.dispatch('input');
  textarea.value='Ping @alex'; textarea.selectionStart=textarea.value.length; textarea.dispatch('input');
  pending[1].resolve([{{login:'alex',name:'Alexander'}}]); await Promise.resolve(); await Promise.resolve();
  pending[0].resolve([{{login:'alice',name:'Alice'}}]); await Promise.resolve(); await Promise.resolve();
  const before={{queries:pending.map(item=>item.query), options:listbox.children.map(item=>item.dataset.login), expanded:textarea.attrs['aria-expanded']}};
  textarea.dispatch('keydown', {{key:'ArrowDown'}});
  const enter=textarea.dispatch('keydown', {{key:'Enter'}});
  process.stdout.write(JSON.stringify({{before,value:textarea.value,caret:textarea.selectionStart,focused:textarea.focused,prevented:enter.prevented,hidden:listbox.hidden}}));
}})().catch(error=>{{ console.error(error); process.exit(1); }});
"""

    result = run_node(script)

    assert result["before"] == {
        "queries": ["al", "alex"],
        "options": ["alex"],
        "expanded": "true",
    }
    assert result["value"] == "Ping @alex "
    assert result["caret"] == 11
    assert result["focused"] is True
    assert result["prevented"] is True
    assert result["hidden"] is True
