import json
import subprocess
from pathlib import Path


SOURCE = Path(__file__).parents[1] / "frontend" / "mobile-today-command-bar.js"
FRONTEND = SOURCE.parent


def run_node(body: str) -> dict:
    script = f"const createCommandBar = require({json.dumps(str(SOURCE))});\n" + body
    completed = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
    return json.loads(completed.stdout)


def test_more_sheet_opens_modally_and_restores_focus_after_close():
    result = run_node(r"""
function element() {
  const listeners = {};
  return {
    listeners, open:false, hidden:false, attributes:{}, focusCount:0,
    addEventListener:(type, listener) => { (listeners[type] ||= []).push(listener); },
    dispatch(type, event={}) { (listeners[type] || []).forEach(listener => listener({preventDefault(){}, target:this, ...event})); },
    setAttribute(name, value) { this.attributes[name] = value; },
    showModal() { this.open = true; },
    close() { this.open = false; this.dispatch('close'); },
    focus() { this.focusCount += 1; },
  };
}
const more = element();
const sheet = element();
const close = element();
const firstAction = element();
createCommandBar({more, sheet, close, firstAction});
more.dispatch('click');
const opened = {open:sheet.open, expanded:more.attributes['aria-expanded'], firstFocused:firstAction.focusCount};
close.dispatch('click');
process.stdout.write(JSON.stringify({opened, closed:!sheet.open, restored:more.focusCount}));
""")

    assert result == {
        "opened": {"open": True, "expanded": "true", "firstFocused": 1},
        "closed": True,
        "restored": 1,
    }


def test_end_session_action_reuses_existing_session_control_and_closes_sheet():
    result = run_node(r"""
function element() {
  const listeners = {};
  return {
    open:false, clicks:0, attributes:{},
    addEventListener:(type, listener) => { (listeners[type] ||= []).push(listener); },
    dispatch(type, event={}) { (listeners[type] || []).forEach(listener => listener({preventDefault(){}, target:this, ...event})); },
    setAttribute(name, value) { this.attributes[name] = value; },
    showModal() { this.open = true; },
    close() { this.open = false; this.dispatch('close'); },
    click() { this.clicks += 1; this.dispatch('click'); },
    focus() {},
  };
}
const more = element();
const sheet = element();
const endAction = element();
const existingEndControl = element();
createCommandBar({more, sheet, endAction, existingEndControl});
more.dispatch('click');
endAction.dispatch('click');
process.stdout.write(JSON.stringify({open:sheet.open, endClicks:existingEndControl.clicks}));
""")

    assert result == {"open": False, "endClicks": 1}


def test_secondary_action_closes_command_sheet_before_existing_handler_runs():
    result = run_node(r"""
function element() {
  const listeners = {};
  return {
    open:false, attributes:{}, observedOpen:null,
    addEventListener:(type, listener) => { (listeners[type] ||= []).push(listener); },
    dispatch(type) { (listeners[type] || []).forEach(listener => listener({preventDefault(){}, target:this})); },
    setAttribute(name, value) { this.attributes[name] = value; },
    showModal() { this.open = true; }, close() { this.open = false; this.dispatch('close'); }, focus() {},
  };
}
const more = element();
const sheet = element();
const update = element();
createCommandBar({more, sheet, actionButtons:[update]});
update.addEventListener('click', () => { update.observedOpen = sheet.open; });
more.dispatch('click');
update.dispatch('click');
process.stdout.write(JSON.stringify({open:sheet.open, handlerSawOpen:update.observedOpen}));
""")

    assert result == {"open": False, "handlerSawOpen": False}


def test_rendered_hud_and_dock_height_define_scroll_clearance():
    result = run_node(r"""
const listeners = {};
const element = () => ({
  open:false, attributes:{},
  addEventListener:(type, listener) => { (listeners[type] ||= []).push(listener); },
  setAttribute(name, value) { this.attributes[name] = value; },
  focus() {}, showModal() { this.open = true; }, close() { this.open = false; },
});
const values = {};
let resize;
const ResizeObserverImpl = class {
  constructor(callback) { resize = callback; }
  observe() {}
  disconnect() {}
};
const more = element();
const sheet = element();
createCommandBar({
  more, sheet,
  hud:{offsetHeight:96}, dock:{offsetHeight:56},
  style:{setProperty:(name, value) => { values[name] = value; }},
  ResizeObserverImpl,
});
resize();
process.stdout.write(JSON.stringify(values));
""")

    assert result == {"--mobile-today-clearance": "168px"}


def test_mobile_command_bar_keeps_primary_actions_visible_and_secondary_actions_modal():
    html = (FRONTEND / "index.html").read_text()
    css = (FRONTEND / "dashboard.css").read_text()
    dashboard = (FRONTEND / "dashboard.js").read_text()

    hud = html[html.index('<section class="mobile-today-hud"'):html.index('</section>', html.index('<section class="mobile-today-hud"'))]
    sheet = html[html.index('<dialog class="mobile-today-actions"'):html.index('</dialog>', html.index('<dialog class="mobile-today-actions"'))]

    assert "data-mobile-today-open" in hud
    assert "data-mobile-today-complete" in hud
    assert "data-mobile-today-toggle" in hud
    assert "data-mobile-today-more" in hud
    for secondary in (
        "data-work-session-previous",
        "data-work-session-next",
        "data-mobile-today-update",
        "data-mobile-today-blocked",
        "data-today-break-open",
        "data-work-session-adjust-plan",
        "data-mobile-today-end",
    ):
        assert secondary not in hud
        assert secondary in sheet
    assert 'aria-modal="true"' in sheet
    assert "body.mobile-today-active { padding-bottom:calc(var(--mobile-today-clearance" in css
    assert "createMobileTodayCommandBar({" in dashboard
    assert '<script src="static/mobile-today-command-bar.js"></script>' in html
