import json
import subprocess
from pathlib import Path


FRONTEND = Path(__file__).parents[1] / "frontend"
HYDRATOR = FRONTEND / "conversation-action-hydrator.js"


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


def test_conversation_paints_before_optional_actions_load_and_hydrates_once():
    script = f"""
const createConversationActionHydrator = require({json.dumps(str(HYDRATOR))});
const events = [];
let finishLoad;
const hydrator = createConversationActionHydrator({{
  load: () => {{
    events.push('load');
    return new Promise(resolve => {{ finishLoad = resolve; }});
  }},
  activate: () => ({{name:'actions'}}),
}});
const root = {{}};
const retry = {{hidden:false}};
const status = {{textContent:'2 messages loaded.'}};
const state = {{comments:[1,2]}};
const pending = hydrator.show({{
  root, state, retry, status,
  paint: (_state, actions) => events.push(actions ? 'paint-actions' : 'paint-core'),
  wire: () => events.push('wire'),
}});
events.push('returned');
finishLoad();
pending.then(() => {{
  hydrator.show({{
    root, state, retry, status,
    paint: (_state, actions) => events.push(actions ? 'paint-actions-again' : 'paint-core-again'),
    wire: () => events.push('wire-again'),
  }}).then(() => process.stdout.write(JSON.stringify({{events,retry,status:status.textContent}})));
}});
"""
    output = run_node(script)

    assert output == {
        "events": [
            "paint-core", "load", "returned", "wire", "paint-actions",
            "paint-actions-again",
        ],
        "retry": {"hidden": True},
        "status": "2 messages loaded.",
    }


def test_failed_comment_actions_keep_conversation_usable_and_retry_in_place():
    script = f"""
const createConversationActionHydrator = require({json.dumps(str(HYDRATOR))});
const events = [];
let attempt = 0;
const hydrator = createConversationActionHydrator({{
  load: async () => {{
    attempt += 1;
    if (attempt === 1) throw new Error('chunk unavailable');
  }},
  activate: () => ({{name:'actions'}}),
}});
const root = {{}};
const retry = {{hidden:true}};
const status = {{textContent:'2 messages loaded.'}};
const options = {{
  root, retry, status, state:{{comments:[1,2]}},
  paint: (_state, actions) => events.push(actions ? 'actions' : 'core'),
  wire: () => events.push('wire'),
}};
(async () => {{
  const failed = await hydrator.show(options);
  events.push('reply-still-usable');
  const recovered = await hydrator.show(options);
  process.stdout.write(JSON.stringify({{
    failed, recovered, attempt, events, retry, status:status.textContent,
  }}));
}})();
"""
    output = run_node(script)

    assert output == {
        "failed": False,
        "recovered": True,
        "attempt": 2,
        "events": ["core", "reply-still-usable", "core", "wire", "actions"],
        "retry": {"hidden": True},
        "status": "2 messages loaded.",
    }


def test_other_focused_surfaces_can_request_the_same_lazy_action_controller():
    script = f"""
const createConversationActionHydrator = require({json.dumps(str(HYDRATOR))});
let loads=0; const controller={{name:'shared-actions'}};
const hydrator=createConversationActionHydrator({{
  load:async()=>{{loads++;}}, activate:()=>controller,
}});
(async()=>{{
  const [first,second]=await Promise.all([hydrator.get(),hydrator.get()]);
  process.stdout.write(JSON.stringify({{loads,same:first===second,name:first.name}}));
}})().catch(error=>{{console.error(error);process.exit(1);}});
"""
    assert run_node(script) == {"loads": 1, "same": True, "name": "shared-actions"}


def test_issue_pull_and_update_conversations_trigger_optional_actions_not_startup():
    html = (FRONTEND / "index.html").read_text()
    javascript = (FRONTEND / "dashboard.js").read_text()

    assert '<script src="static/conversation-action-hydrator.js"></script>' in html
    assert html.index("static/conversation-action-hydrator.js") < html.index("static/dashboard.js")
    assert "await commentActionFeatures.run('comment-actions'" not in javascript
    assert "load: () => commentActionFeatures.load('comment-actions')" in javascript
    assert "createConversationActionHydrator" in javascript
    for kind in ("issue", "pull", "update"):
        assert f"retry-{kind}-comment-actions" in html
        assert f"showConversationWithActions('{kind}'" in javascript
        assert f"qs('#retry-{kind}-comment-actions').addEventListener" in javascript


def test_comment_action_retry_is_a_phone_sized_inline_control():
    css = (FRONTEND / "dashboard.css").read_text()

    assert ".conversation-actions-retry" in css
    rule = css.split(".conversation-actions-retry", 1)[1].split("}", 1)[0]
    assert "min-height:44px" in rule
    assert "max-width:100%" in rule
