import json
import subprocess
from pathlib import Path


BOOTSTRAP = Path(__file__).parents[1] / "frontend" / "workspace-bootstrap.js"
INDEX = Path(__file__).parents[1] / "frontend" / "index.html"
CSS = Path(__file__).parents[1] / "frontend" / "dashboard.css"


def run_bootstrap(scenario: str) -> dict:
    harness = f"""
const loadWorkspace = require({json.dumps(str(BOOTSTRAP))});
(async()=>{{ {scenario} }})().catch(error=>{{console.error(error);process.exit(1);}});
"""
    completed = subprocess.run(
        ["node", "-e", harness], check=True, capture_output=True, text=True
    )
    return json.loads(completed.stdout)


def test_workspace_bootstrap_loads_content_addressed_feature_before_startup():
    result = run_bootstrap("""
const status={textContent:''};
const document={
  querySelector(selector) {
    if (selector === 'meta[name="stackchain-feature-today-timer"]') return {content:'feature-workspace-abc.js'};
    if (selector === 'meta[name="stackchain-feature-planning"]') return {content:'feature-planning-def.js'};
    if (selector === '#my-work-action-status') return status;
    return null;
  }
};
const requested=[];
const createLoader=options=>({load:async name=>{requested.push(name + ':' + options.urls[name]);}});
await loadWorkspace({document,createLoader});
console.log(JSON.stringify({requested,status:status.textContent}));
""")
    assert result == {
        "requested": [
            "today-timer:feature-workspace-abc.js",
            "planning:feature-planning-def.js",
        ],
        "status": "",
    }


def test_workspace_bootstrap_recovers_one_transient_failure_in_place():
    result = run_bootstrap("""
const status={textContent:''}; const retry={hidden:true,disabled:false}; let attempts=0;
const document={querySelector(selector) {
  if (selector.startsWith('meta[')) return {content:'feature-workspace-abc.js'};
  if (selector === '#my-work-action-status') return status;
  if (selector === '#retry-workspace') return retry;
  return null;
}};
const createLoader=()=>({load:async()=>{attempts++; if (attempts === 1) throw new Error('brief outage');}});
const schedule=callback=>{callback();};
await loadWorkspace({document,createLoader,schedule});
console.log(JSON.stringify({attempts,status:status.textContent,retryHidden:retry.hidden}));
""")
    assert result == {"attempts": 4, "status": "", "retryHidden": True}


def test_workspace_bootstrap_offers_single_flight_manual_retry_without_reloading():
    result = run_bootstrap("""
const status={textContent:''}; const listeners={}; let reloads=0; let attempts=0;
const retry={hidden:true,disabled:false,addEventListener(name,callback){listeners[name]=callback;}};
const document={querySelector(selector) {
  if (selector.startsWith('meta[')) return {content:'feature-workspace-abc.js'};
  if (selector === '#my-work-action-status') return status;
  if (selector === '#retry-workspace') return retry;
  return null;
}};
const window={location:{reload(){reloads++;}},addEventListener(){},removeEventListener(){}};
const createLoader=()=>({load:async()=>{attempts++; if (attempts < 5) throw new Error('offline');}});
const loading=loadWorkspace({document,window,createLoader,schedule:callback=>callback()});
await new Promise(resolve=>setImmediate(resolve));
const offered={hidden:retry.hidden,disabled:retry.disabled,status:status.textContent};
const first=listeners.click(); const second=listeners.click();
await Promise.all([first,second,loading]);
console.log(JSON.stringify({attempts,reloads,offered,status:status.textContent,retryHidden:retry.hidden}));
""")
    assert result == {
        "attempts": 6,
        "reloads": 0,
        "offered": {
            "hidden": False,
            "disabled": False,
            "status": "Workspace unavailable. Reconnect or retry.",
        },
        "status": "",
        "retryHidden": True,
    }


def test_workspace_retry_control_is_accessible_and_touch_safe():
    index = INDEX.read_text()
    css = CSS.read_text()

    assert '<button class="retry-work-route" id="retry-workspace" type="button" hidden>Retry workspace</button>' in index
    assert ".retry-work-route { min-height:44px;" in css


def test_workspace_bootstrap_reconnect_retries_in_place_without_reloading():
    result = run_bootstrap("""
const status={textContent:''}; const listeners={}; let reloads=0; let attempts=0;
const retry={hidden:true,disabled:false,addEventListener(){},removeEventListener(){}};
const document={querySelector(selector) {
  if (selector.startsWith('meta[')) return {content:'feature-workspace-abc.js'};
  if (selector === '#my-work-action-status') return status;
  if (selector === '#retry-workspace') return retry;
  return null;
}};
const window={
  location:{reload(){reloads++;}},
  addEventListener(name,callback) { listeners[name]=callback; },
  removeEventListener(name,callback) { if (listeners[name] === callback) delete listeners[name]; },
};
const createLoader=()=>({load:async()=>{attempts++; if (attempts < 5) throw new Error('offline');}});
const loading=loadWorkspace({document,window,createLoader,schedule:callback=>callback()});
await new Promise(resolve=>setImmediate(resolve));
const waiting=Boolean(listeners.online);
if (listeners.online) listeners.online();
await loading;
console.log(JSON.stringify({attempts,waiting,reloads,status:status.textContent}));
""")
    assert result == {
        "attempts": 6,
        "waiting": True,
        "reloads": 0,
        "status": "",
    }


def test_workspace_bootstrap_replays_online_event_after_dashboard_registers_handlers():
    result = run_bootstrap("""
const status={textContent:''}; const listeners={};
const document={querySelector(selector) {
  if (selector.startsWith('meta[')) return {content:'feature-workspace-abc.js'};
  if (selector === '#my-work-action-status') return status;
  return null;
}};
const window={
  addEventListener(name,callback) { listeners[name]=callback; },
  removeEventListener(name,callback) { if (listeners[name] === callback) delete listeners[name]; },
};
const createLoader=()=>({load:async()=>{listeners.online();}});
const lifecycle=await loadWorkspace({document,window,createLoader});
let reconnects=0;
lifecycle.replayOnline(()=>{reconnects++;});
lifecycle.replayOnline(()=>{reconnects++;});
console.log(JSON.stringify({reconnects,listening:Boolean(listeners.online)}));
""")
    assert result == {"reconnects": 1, "listening": False}