/* Execute: the opportunity ledger, each opportunity's options and money,
   approvals and actuals, the roadmap, and the assumption set. */

const STATUS_TONE = { identified: 'fg-subtle', proposed: 'accent', approved: 'good', in_progress: 'accent', done: 'good', dropped: 'crit' };
const TRANSITIONS_UI = { identified: ['proposed', 'dropped'], proposed: ['approved', 'identified', 'dropped'], approved: ['in_progress', 'proposed', 'dropped'], in_progress: ['done', 'approved', 'dropped'], done: ['in_progress'], dropped: ['identified'] };
const STATUS_VERB = { proposed: 'Propose', approved: 'Approve', in_progress: 'Start', done: 'Mark done', dropped: 'Drop', identified: 'Back to identified' };

function ExecuteModule({ ctx }) {
  const [tab, setTab] = React.useState('opportunities');
  return <div>
    <SubTabs value={tab} onChange={setTab} tabs={[['opportunities', 'Opportunities'], ['roadmap', 'Roadmap'], ['assumptions', 'Assumptions']]} />
    {tab === 'opportunities' && <Opportunities ctx={ctx} />}
    {tab === 'roadmap' && <Roadmap ctx={ctx} />}
    {tab === 'assumptions' && <Assumptions ctx={ctx} />}
  </div>;
}

function chosen(o) { return ((o.options || []).find((x) => x.id === (o.chosen_option || 'recommended')) || {}).result || {}; }

function Opportunities({ ctx }) {
  const [d, error, reload] = useLojo(ctx, { module: 'execute', action: 'opportunities' });
  const [open, setOpen] = React.useState('');
  const [status, setStatus] = React.useState('');
  const [creating, setCreating] = React.useState(false);
  if (error) return <div className="empty">{error}</div>;
  if (!d) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const led = d.ledger;
  const rows = d.opportunities.filter((o) => !status || o.status === status).sort((a, b) => b.priority_score - a.priority_score);
  return (
    <div>
      <div className="app-grid cols-4" style={{ marginBottom: 16 }}>
        <Stat label="Modelled net benefit / yr" value={money(led.annual_net_benefit_usd)} note={`${led.count} opportunities`} tone="good" />
        <Stat label="Implementation" value={money(led.implementation_cost_usd)} note="one-time, modelled" />
        <Stat label="Approved NPV" value={money(led.approved_npv_usd, { compact: true })} note={`of ${money(led.npv_usd, { compact: true })} in total`} tone="accent" />
        <Stat label="Actual benefit recorded" value={money(led.actual_benefit_usd)} note="from actuals entered" />
      </div>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', marginBottom: 12 }}>
        <button className={'app-btn small' + (!status ? ' primary' : '')} onClick={() => setStatus('')}>All</button>
        {Object.keys(STATUS_TONE).map((s) => <button key={s} className={'app-btn small' + (status === s ? ' primary' : '')} onClick={() => setStatus(s)}>{titleCase(s)} {led.by_status[s] ? <span className="mono" style={{ opacity: .7 }}>{led.by_status[s]}</span> : null}</button>)}
        <div style={{ flex: 1 }}></div>
        {!ctx.readOnly && <button className="app-btn primary" onClick={() => setCreating(true)}>New opportunity</button>}
      </div>
      {rows.length === 0 && <div className="empty"><b>No opportunities yet.</b> Convert a recommendation under Stack, or create one by hand with its own inputs. Every figure is computed from the inputs under the organization's assumption set.</div>}
      {rows.length > 0 && <div className="app-table-wrap"><table className="app-table">
        <thead><tr><th>Opportunity</th><th>Status</th><th>Owner</th><th className="num">Net / yr</th><th className="num">Implement</th><th className="num">Payback</th><th className="num">NPV</th><th>Conf.</th><th className="num">Priority</th></tr></thead>
        <tbody>{rows.map((o) => { const r = chosen(o); return <tr key={o.id} className="clickable" onClick={() => setOpen(o.id)}>
          <td style={{ fontWeight: 600, maxWidth: 360 }}>{o.title}{o.deviation && o.deviation.benefit_delta_pct !== null && o.deviation.benefit_delta_pct !== undefined && <span className="mono" style={{ marginLeft: 8, fontSize: 11, color: o.deviation.benefit_delta_pct < 0 ? 'var(--warn)' : 'var(--good)' }}>actual {o.deviation.benefit_delta_pct > 0 ? '+' : ''}{o.deviation.benefit_delta_pct}%</span>}</td>
          <td><Pill tone={STATUS_TONE[o.status]}>{titleCase(o.status)}</Pill></td><td style={{ color: 'var(--fg-muted)' }}>{o.owner_name || '—'}</td>
          <td className="num" style={{ color: (r.annual_net_benefit_usd || 0) >= 0 ? 'var(--good)' : 'var(--crit)' }}>{money(r.annual_net_benefit_usd)}</td><td className="num">{money(r.implementation_cost_usd)}</td>
          <td className="num">{r.payback_month ? `m${r.payback_month}` : '—'}</td><td className="num">{money(r.npv_usd, { compact: true })}</td><td><Confidence c={o.confidence} /></td><td className="num">{o.priority_score}</td></tr>; })}</tbody></table></div>}
      {open && <OpportunityDrawer ctx={ctx} id={open} onClose={() => { setOpen(''); reload(); }} />}
      {creating && <NewOpportunity ctx={ctx} onClose={() => { setCreating(false); reload(); }} />}
    </div>);
}

const INPUT_FIELDS = [['recurring_saving_usd', 'Recurring saving / yr (USD)', 'number'], ['one_time_saving_usd', 'One-time saving (USD)', 'number'],
  ['hours_saved_per_month', 'Hours freed / month', 'number'], ['labour_band', 'Whose hours', 'select', [['ops', 'Operations'], ['specialist', 'Specialist'], ['manager', 'Manager'], ['executive', 'Executive']]],
  ['recurring_new_cost_usd', 'New recurring cost / yr (USD)', 'number'], ['implementation_days', 'Implementation days', 'number'], ['implementation_fixed_usd', 'Fixed implementation cost (USD)', 'number'],
  ['ramp_months', 'Months to full benefit', 'number'], ['evidence_grade', 'Evidence grade', 'select', ['A', 'B', 'C']]];

function InputsForm({ value, onChange, disabled }) {
  return <div className="app-grid cols-2">{INPUT_FIELDS.map(([k, label, type, options]) => <Field key={k} label={label}>
    {type === 'select' ? <Select value={value[k] || (k === 'evidence_grade' ? 'B' : 'ops')} onChange={(v) => onChange({ ...value, [k]: v })} options={options} disabled={disabled} />
      : <Input type="number" value={value[k] === undefined || value[k] === null ? '' : value[k]} onChange={(e) => onChange({ ...value, [k]: e.target.value === '' ? null : Number(e.target.value) })} disabled={disabled} />}
  </Field>)}</div>;
}

function NewOpportunity({ ctx, onClose }) {
  const [f, setF] = React.useState({ title: '', description: '' });
  const [inputs, setInputs] = React.useState({ evidence_grade: 'B', labour_band: 'ops', ramp_months: 3 });
  const [error, setError] = React.useState('');
  const save = async () => {
    const d = await lojo(ctx.token, { module: 'execute', action: 'opportunity-save', org: ctx.org, title: f.title, description: f.description,
      options: [{ id: 'do-nothing', title: 'Do nothing', kind: 'keep', inputs: {} }, { id: 'recommended', title: f.title || 'Proposed change', kind: 'other', inputs }] });
    if (!d.ok) { setError(d.message || 'Not saved'); return; }
    onClose();
  };
  return <Drawer title="New opportunity" onClose={onClose} width={620}>
    <Field label="Title"><Input value={f.title} onChange={(e) => setF({ ...f, title: e.target.value })} /></Field>
    <Field label="Description and evidence"><textarea className="app-input" rows={3} value={f.description} onChange={(e) => setF({ ...f, description: e.target.value })} /></Field>
    <div className="overline" style={{ margin: '8px 0 8px' }}>The proposed option's inputs</div>
    <InputsForm value={inputs} onChange={setInputs} />
    {error && <div className="flash warn">{error}</div>}
    <button className="app-btn primary" onClick={save} disabled={!f.title}>Create and compute</button>
  </Drawer>;
}

function OpportunityDrawer({ ctx, id, onClose }) {
  const [d, error, reload] = useLojo(ctx, { module: 'execute', action: 'opportunity', id }, [id]);
  const [flash, setFlash] = React.useState('');
  const [busy, setBusy] = React.useState('');
  const [editing, setEditing] = React.useState(false);
  const [inputs, setInputs] = React.useState(null);
  const [actuals, setActuals] = React.useState({ benefit_usd: '', cost_usd: '', as_of: '', notes: '' });
  if (error) return <Drawer title="Opportunity" onClose={onClose}><div className="empty">{error}</div></Drawer>;
  if (!d) return <Drawer title="Opportunity" onClose={onClose}><div style={{ color: 'var(--fg-subtle)' }}>Loading…</div></Drawer>;
  const o = d.opportunity;
  const rec = (o.options || []).find((x) => x.id === (o.chosen_option || 'recommended')) || {};
  const res = rec.result || {};
  const act = async (payload, label) => {
    setBusy(label); setFlash('');
    const x = await lojo(ctx.token, { ...payload, org: ctx.org, id: o.id });
    setBusy('');
    if (!x.ok) { setFlash(x.message || 'That did not work.'); return x; }
    reload(); return x;
  };
  const move = async (status) => {
    const needs = ['approved', 'dropped', 'done', 'in_progress'].includes(status);
    const reason = needs ? window.prompt(`${STATUS_VERB[status]}: reason (recorded in the audit log)`) : '';
    if (needs && reason === null) return;
    await act({ module: 'execute', action: 'opportunity-status', status, reason: reason || '' }, status);
  };
  const saveInputs = async () => {
    const options = o.options.map((x) => x.id === rec.id ? { ...x, inputs } : x);
    const x = await act({ module: 'execute', action: 'opportunity-save', options }, 'inputs');
    if (x && x.ok) setEditing(false);
  };
  const saveActuals = async () => { const x = await act({ module: 'execute', action: 'opportunity-actuals', ...actuals }, 'actuals'); if (x && x.ok) setFlash('Actuals recorded; deviation computed.'); };
  const canApprove = ['owner', 'admin', 'operator'].includes(ctx.role);
  return (
    <Drawer title={o.title} onClose={onClose} width={820}>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', marginBottom: 14 }}>
        <Pill tone={STATUS_TONE[o.status]}>{titleCase(o.status)}</Pill><span style={{ fontSize: 13 }}>Confidence <Confidence c={o.confidence} /></span>
        <span style={{ fontSize: 13, color: 'var(--fg-muted)' }}>Owner {o.owner_name || '—'}</span><span className="mono" style={{ fontSize: 11.5, color: 'var(--fg-subtle)' }}>assumptions v{o.assumption_set_version} · priority {o.priority_score}</span>
      </div>
      <div className="app-grid cols-4" style={{ marginBottom: 16 }}>
        <Stat label="Net benefit / yr" value={money(res.annual_net_benefit_usd)} tone={(res.annual_net_benefit_usd || 0) >= 0 ? 'good' : 'crit'} />
        <Stat label="To implement" value={money(res.implementation_cost_usd)} />
        <Stat label="Payback" value={res.payback_month ? `month ${res.payback_month}` : '—'} />
        <Stat label={`NPV (${res.horizon_years || 3} yr)`} value={money(res.npv_usd, { compact: true })} note={res.roi_pct !== null && res.roi_pct !== undefined ? `ROI ${res.roi_pct}%` : ''} />
      </div>
      {o.description && <p style={{ fontSize: 14.5, lineHeight: 1.65, margin: '0 0 16px' }}>{o.description}</p>}
      {o.ai_notes && o.ai_notes.text && <div className="app-panel" style={{ marginBottom: 14, borderColor: 'color-mix(in oklab, var(--accent) 35%, transparent)' }}>
        <div className="overline" style={{ marginBottom: 6 }}>Explanation · AI, cites evidence only</div><div style={{ fontSize: 14, lineHeight: 1.6 }}>{o.ai_notes.text}</div></div>}

      <div className="overline" style={{ marginBottom: 6 }}>Options compared</div>
      <div className="app-table-wrap" style={{ marginBottom: 16 }}><table className="app-table">
        <thead><tr><th>Option</th><th className="num">Net / yr</th><th className="num">Implement</th><th className="num">Payback</th><th className="num">NPV</th><th></th></tr></thead>
        <tbody>{(o.options || []).map((x) => { const r = x.result || {}; return <tr key={x.id} style={x.id === rec.id ? { background: 'color-mix(in oklab, var(--accent) 8%, transparent)' } : {}}>
          <td><div style={{ fontWeight: 600 }}>{x.title}</div><div style={{ fontSize: 12, color: 'var(--fg-subtle)' }}><KindPill kind={x.kind} /></div></td>
          <td className="num">{money(r.annual_net_benefit_usd)}</td><td className="num">{money(r.implementation_cost_usd)}</td><td className="num">{r.payback_month ? `m${r.payback_month}` : '—'}</td><td className="num">{money(r.npv_usd, { compact: true })}</td>
          <td>{x.recommended && <Pill tone="good">best NPV</Pill>}{x.id === o.chosen_option && <Pill tone="accent">chosen</Pill>}</td></tr>; })}</tbody></table></div>

      <div className="app-grid cols-2" style={{ marginBottom: 16 }}>
        <div><div className="overline" style={{ marginBottom: 6 }}>How the money is computed</div><Lines lines={res.lines} />
          <div style={{ marginTop: 8, display: 'flex', gap: 8 }}>
            {!ctx.readOnly && !editing && <button className="app-btn small" onClick={() => { setInputs(rec.inputs || {}); setEditing(true); }}>Edit inputs</button>}
            {!ctx.readOnly && o.assumption_set_version !== undefined && <button className="app-btn small" disabled={!!busy} onClick={() => act({ module: 'execute', action: 'opportunity-repin' }, 'repin')} title="Recompute with the current assumption set">Re-pin assumptions</button>}
          </div>
          {editing && <div style={{ marginTop: 10 }}><InputsForm value={inputs} onChange={setInputs} /><div style={{ display: 'flex', gap: 8 }}><button className="app-btn primary small" onClick={saveInputs}>Recompute</button><button className="app-btn small" onClick={() => setEditing(false)}>Cancel</button></div></div>}
        </div>
        <div>
          <div className="overline" style={{ marginBottom: 6 }}>Risks</div>
          <ul style={{ margin: '0 0 12px', paddingLeft: 18, fontSize: 13.5, lineHeight: 1.6 }}>{(o.risks || []).map((r) => <li key={r}>{r}</li>)}</ul>
          <div className="overline" style={{ marginBottom: 6 }}>Dependencies</div>
          <ul style={{ margin: 0, paddingLeft: 18, fontSize: 13.5, lineHeight: 1.6 }}>{(o.dependencies || []).map((r) => <li key={r}>{r}</li>)}</ul>
          {ctx.view === 'impl' && <div style={{ marginTop: 12 }}><div className="overline" style={{ marginBottom: 6 }}>Assumptions used</div>
            <div className="mono" style={{ fontSize: 11, color: 'var(--fg-muted)', lineHeight: 1.7 }}>discount {Math.round((d.assumptions.discount_rate || 0) * 100)}% · horizon {d.assumptions.horizon_years} yr · day rate {money(d.assumptions.day_rate_usd)} · hourly {Object.entries(d.assumptions.hourly_cost || {}).map(([k, v]) => `${k} ${money(v)}`).join(', ')} · haircut A/B/C {Object.values(d.assumptions.haircut || {}).map((v) => Math.round(v * 100) + '%').join('/')}</div></div>}
        </div>
      </div>

      {(o.status === 'approved' || o.status === 'in_progress' || o.status === 'done') &&
      <div className="app-panel" style={{ marginBottom: 16 }}>
        <h3>Actuals vs forecast</h3>
        {o.deviation && o.deviation.as_of ? <div className="app-grid cols-4" style={{ marginBottom: 10 }}>
          <Stat label="Benefit actual" value={money(o.deviation.benefit_actual_usd)} note={`forecast ${money(o.deviation.benefit_forecast_usd)}`} />
          <Stat label="Benefit deviation" value={o.deviation.benefit_delta_pct === null ? '—' : `${o.deviation.benefit_delta_pct > 0 ? '+' : ''}${o.deviation.benefit_delta_pct}%`} tone={o.deviation.benefit_delta_usd < 0 ? 'warn' : 'good'} />
          <Stat label="Cost actual" value={money(o.deviation.cost_actual_usd)} note={`forecast ${money(o.deviation.cost_forecast_usd)}`} />
          <Stat label="Cost deviation" value={o.deviation.cost_delta_pct === null ? '—' : `${o.deviation.cost_delta_pct > 0 ? '+' : ''}${o.deviation.cost_delta_pct}%`} tone={o.deviation.cost_delta_usd > 0 ? 'warn' : 'good'} />
        </div> : <div style={{ fontSize: 13, color: 'var(--fg-subtle)', marginBottom: 8 }}>No actuals recorded yet.</div>}
        {o.actuals && o.actuals.notes && <div style={{ fontSize: 13, color: 'var(--fg-muted)', marginBottom: 8 }}>{o.actuals.notes} <span className="mono" style={{ fontSize: 11 }}>(as of {o.actuals.as_of})</span></div>}
        {!ctx.readOnly && <div className="app-grid cols-4">
          <Field label="Annual benefit realised (USD)"><Input type="number" value={actuals.benefit_usd} onChange={(e) => setActuals({ ...actuals, benefit_usd: e.target.value })} /></Field>
          <Field label="Cost to date (USD)"><Input type="number" value={actuals.cost_usd} onChange={(e) => setActuals({ ...actuals, cost_usd: e.target.value })} /></Field>
          <Field label="As of"><Input type="date" value={actuals.as_of} onChange={(e) => setActuals({ ...actuals, as_of: e.target.value })} /></Field>
          <Field label="Notes"><Input value={actuals.notes} onChange={(e) => setActuals({ ...actuals, notes: e.target.value })} /></Field>
        </div>}
        {!ctx.readOnly && <button className="app-btn small" disabled={!!busy || actuals.benefit_usd === ''} onClick={saveActuals}>Record actuals</button>}
      </div>}

      {flash && <div className="flash good">{flash}</div>}
      {!ctx.readOnly && <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 14 }}>
        {(TRANSITIONS_UI[o.status] || []).map((s) => {
          const consequential = ['approved', 'in_progress', 'done', 'dropped'].includes(s);
          return <button key={s} className={'app-btn' + (s === 'approved' ? ' primary' : s === 'dropped' ? ' danger' : '')} disabled={!!busy || (consequential && !canApprove)} title={consequential && !canApprove ? 'Needs an admin or owner' : ''} onClick={() => move(s)}>{STATUS_VERB[s]}</button>;
        })}
        {ctx.orgData && ctx.orgData.org.settings && ctx.orgData.org.settings.ai_assist !== false && <button className="app-btn" disabled={!!busy} onClick={() => act({ module: 'execute', action: 'explain' }, 'explain')}>{busy === 'explain' ? 'Writing…' : 'Explain with AI'}</button>}
      </div>}
      {(o.approvals || []).length > 0 && <div><div className="overline" style={{ marginBottom: 6 }}>Decisions</div>
        {o.approvals.map((a, i) => <div key={i} style={{ fontSize: 13, padding: '5px 0', borderBottom: '1px solid var(--line)' }}><Pill tone={a.status === 'approved' ? 'good' : 'crit'}>{a.status}</Pill> <span style={{ marginLeft: 6 }}>{a.by_name || a.by}</span> <span className="mono" style={{ color: 'var(--fg-subtle)', fontSize: 11 }}>{shortDate(a.at)}</span>{a.reason && <div style={{ color: 'var(--fg-muted)', marginTop: 2 }}>{a.reason}</div>}</div>)}</div>}
      {ctx.view === 'impl' && <div className="mono" style={{ fontSize: 11, color: 'var(--fg-subtle)', marginTop: 16 }}>id {o.id} · version {o.version} · {(d.versions || []).length} prior versions · evidence {(o.evidence_ids || []).join(', ') || 'none'} · from {(o.recommendation_ids || []).join(', ') || 'manual'}</div>}
    </Drawer>);
}

/* ------------------------------------------------------------------ roadmap */
function Roadmap({ ctx }) {
  const [d, error, reload] = useLojo(ctx, { module: 'execute', action: 'roadmap' });
  const [opps] = useLojo(ctx, { module: 'execute', action: 'opportunities' });
  const [edit, setEdit] = React.useState(null);
  const [busy, setBusy] = React.useState('');
  if (error) return <div className="empty">{error}</div>;
  if (!d) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const rm = d.roadmap, months = rm.horizon_months || 12;
  const ms = [...(rm.milestones || [])].sort((a, b) => (a.start_month - b.start_month) || (a.end_month - b.end_month));
  const oppName = (id) => (((opps || {}).opportunities || []).find((o) => o.id === id) || {}).title || id;
  const run = async (action, extra) => { setBusy(action); const x = await lojo(ctx.token, { module: 'execute', action, org: ctx.org, ...(extra || {}) }); setBusy(''); if (x.ok && action === 'export') downloadText(x.filename, x.content, x.format === 'csv' ? 'text/csv' : 'text/markdown'); reload(); };
  return (
    <div>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 14 }}>
        <Stat label="Milestones" value={d.progress.milestones} note={`${d.progress.done} done${d.progress.blocked ? ` · ${d.progress.blocked} blocked` : ''}`} />
        <Stat label="Overall progress" value={`${d.progress.pct}%`} />
        <div style={{ flex: 1 }}></div>
        <button className="app-btn" disabled={!!busy} onClick={() => run('export', { format: 'md' })}>Export executive summary</button>
        <button className="app-btn" disabled={!!busy} onClick={() => run('export', { format: 'csv' })}>Export CSV</button>
        {!ctx.readOnly && <button className="app-btn" disabled={!!busy} onClick={() => run('roadmap-auto')} title="Add milestones for proposed and approved opportunities, by priority">Plan from the ledger</button>}
        {!ctx.readOnly && <button className="app-btn primary" onClick={() => setEdit({})}>Add milestone</button>}
      </div>
      {ms.length === 0 ? <div className="empty"><b>No milestones yet.</b> Plan from the ledger to place approved and proposed opportunities by priority, or add milestones by hand.</div> :
      <div className="app-panel" style={{ overflowX: 'auto' }}>
        <div className="timeline" style={{ '--months': months }}>
          <div className="head" style={{ textAlign: 'left' }}>Milestone</div>{Array.from({ length: months }, (_, i) => <div key={i} className="head">M{i + 1}</div>)}
          {ms.map((m) => <React.Fragment key={m.id}>
            <div className="row-label" style={{ cursor: ctx.readOnly ? 'default' : 'pointer' }} onClick={() => !ctx.readOnly && setEdit(m)} title={m.title}>{m.title}<div style={{ fontSize: 11, color: 'var(--fg-subtle)' }}>{m.owner_name || '—'} · {titleCase(m.status)} · {m.progress_pct || 0}%</div></div>
            {Array.from({ length: months }, (_, i) => { const on = i + 1 >= m.start_month && i + 1 <= m.end_month; return <div key={i} className={'cell' + (on ? (m.status === 'done' ? ' done' : m.status === 'blocked' ? ' blocked' : ' on') : '')} title={on ? m.title : ''}></div>; })}
          </React.Fragment>)}
        </div>
      </div>}
      {ctx.view === 'impl' && ms.length > 0 && <div className="app-panel" style={{ marginTop: 14 }}><h3>Milestone detail</h3>
        <table className="app-table"><thead><tr><th>Milestone</th><th>Months</th><th>Owner</th><th>Status</th><th>Opportunities</th><th>Notes</th></tr></thead><tbody>
          {ms.map((m) => <tr key={m.id} className="clickable" onClick={() => !ctx.readOnly && setEdit(m)}><td>{m.title}</td><td className="mono">{m.start_month}–{m.end_month}</td><td>{m.owner_name || '—'}</td><td><Pill tone={m.status === 'done' ? 'good' : m.status === 'blocked' ? 'crit' : 'accent'}>{titleCase(m.status)}</Pill></td><td style={{ fontSize: 12.5, color: 'var(--fg-muted)' }}>{(m.opportunity_ids || []).map(oppName).join('; ')}</td><td style={{ fontSize: 12.5, color: 'var(--fg-muted)' }}>{m.notes}</td></tr>)}
        </tbody></table></div>}
      {edit && <MilestoneDrawer ctx={ctx} milestone={edit} months={months} opportunities={(opps || {}).opportunities || []} onClose={() => { setEdit(null); reload(); }} />}
    </div>);
}

function MilestoneDrawer({ ctx, milestone, months, opportunities, onClose }) {
  const [f, setF] = React.useState({ title: '', owner_name: '', start_month: 1, end_month: 1, status: 'planned', progress_pct: 0, notes: '', opportunity_ids: [], ...milestone });
  const [error, setError] = React.useState('');
  const save = async () => { const d = await lojo(ctx.token, { module: 'execute', action: 'milestone-save', org: ctx.org, ...f }); if (!d.ok) { setError(d.message || 'Not saved'); return; } onClose(); };
  const remove = async () => { if (!window.confirm('Remove this milestone?')) return; await lojo(ctx.token, { module: 'execute', action: 'milestone-remove', org: ctx.org, milestone_id: f.id }); onClose(); };
  const toggle = (id) => setF({ ...f, opportunity_ids: f.opportunity_ids.includes(id) ? f.opportunity_ids.filter((x) => x !== id) : [...f.opportunity_ids, id] });
  return <Drawer title={f.id ? f.title : 'New milestone'} onClose={onClose} width={600}>
    <Field label="Title"><Input value={f.title} onChange={(e) => setF({ ...f, title: e.target.value })} /></Field>
    <div className="app-grid cols-2">
      <Field label="Owner"><Input value={f.owner_name} onChange={(e) => setF({ ...f, owner_name: e.target.value })} /></Field>
      <Field label="Status"><Select value={f.status} onChange={(v) => setF({ ...f, status: v })} options={[['planned', 'Planned'], ['in_progress', 'In progress'], ['done', 'Done'], ['blocked', 'Blocked'], ['dropped', 'Dropped']]} /></Field>
      <Field label="Start month"><Input type="number" min={1} max={months} value={f.start_month} onChange={(e) => setF({ ...f, start_month: Number(e.target.value) })} /></Field>
      <Field label="End month"><Input type="number" min={1} max={months} value={f.end_month} onChange={(e) => setF({ ...f, end_month: Number(e.target.value) })} /></Field>
      <Field label="Progress %"><Input type="number" min={0} max={100} value={f.progress_pct} onChange={(e) => setF({ ...f, progress_pct: Number(e.target.value) })} /></Field>
    </div>
    <Field label="Notes"><textarea className="app-input" rows={2} value={f.notes} onChange={(e) => setF({ ...f, notes: e.target.value })} /></Field>
    <Group label="Opportunities delivered">{opportunities.length === 0 ? <div style={{ fontSize: 13, color: 'var(--fg-subtle)' }}>None yet.</div> : opportunities.map((o) => <label key={o.id} style={{ display: 'flex', gap: 8, fontSize: 13, padding: '3px 0' }}><input type="checkbox" checked={f.opportunity_ids.includes(o.id)} onChange={() => toggle(o.id)} />{o.title}</label>)}</Group>
    {error && <div className="flash warn">{error}</div>}
    <div style={{ display: 'flex', gap: 8 }}><button className="app-btn primary" onClick={save} disabled={!f.title}>Save</button>{f.id && <button className="app-btn danger" onClick={remove}>Remove</button>}</div>
  </Drawer>;
}

/* -------------------------------------------------------------- assumptions */
function Assumptions({ ctx }) {
  const [d, error, reload] = useLojo(ctx, { module: 'execute', action: 'assumptions' });
  const [v, setV] = React.useState(null);
  const [flash, setFlash] = React.useState('');
  if (error) return <div className="empty">{error}</div>;
  if (!d) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const values = v || { ...d.defaults, ...(d.assumption_set.values || {}), hourly_cost: { ...d.defaults.hourly_cost, ...((d.assumption_set.values || {}).hourly_cost || {}) }, haircut: { ...d.defaults.haircut, ...((d.assumption_set.values || {}).haircut || {}) } };
  const set = (k, val) => setV({ ...values, [k]: val });
  const setNested = (k, kk, val) => setV({ ...values, [k]: { ...values[k], [kk]: val } });
  const save = async () => { const x = await lojo(ctx.token, { module: 'execute', action: 'assumptions-update', org: ctx.org, values }); setFlash(x.ok ? `Saved as version ${x.assumption_set.version}. Existing opportunities keep the version they were costed with until re-pinned.` : (x.message || 'Not saved')); setV(null); reload(); };
  const canEdit = !ctx.readOnly && ['owner', 'admin', 'operator'].includes(ctx.role);
  return (
    <div style={{ maxWidth: 760 }}>
      <div style={{ fontSize: 13.5, color: 'var(--fg-muted)', lineHeight: 1.6, marginBottom: 14 }}>The numbers every opportunity is costed with. Versioned: each save is a new version, and an opportunity records the version it used, so a figure can always be traced. {d.assumption_set.version ? `Current version ${d.assumption_set.version}.` : ''}</div>
      <div className="app-grid cols-3">
        <Field label="Discount rate"><Input type="number" step="0.01" value={values.discount_rate} onChange={(e) => set('discount_rate', Number(e.target.value))} disabled={!canEdit} /></Field>
        <Field label="Horizon (years)"><Input type="number" value={values.horizon_years} onChange={(e) => set('horizon_years', Number(e.target.value))} disabled={!canEdit} /></Field>
        <Field label="Implementation day rate (USD)"><Input type="number" value={values.day_rate_usd} onChange={(e) => set('day_rate_usd', Number(e.target.value))} disabled={!canEdit} /></Field>
        <Field label="Default ramp (months)"><Input type="number" value={values.ramp_months} onChange={(e) => set('ramp_months', Number(e.target.value))} disabled={!canEdit} /></Field>
        <Field label="Labour realisation" hint="share of freed hours that becomes benefit"><Input type="number" step="0.05" value={values.labour_realisation} onChange={(e) => set('labour_realisation', Number(e.target.value))} disabled={!canEdit} /></Field>
      </div>
      <div className="overline" style={{ margin: '8px 0 8px' }}>Hourly cost by band (USD)</div>
      <div className="app-grid cols-4">{Object.entries(values.hourly_cost).map(([k, val]) => <Field key={k} label={titleCase(k)}><Input type="number" value={val} onChange={(e) => setNested('hourly_cost', k, Number(e.target.value))} disabled={!canEdit} /></Field>)}</div>
      <div className="overline" style={{ margin: '8px 0 8px' }}>Evidence haircut by grade</div>
      <div className="app-grid cols-3">{Object.entries(values.haircut).map(([k, val]) => <Field key={k} label={`Grade ${k}`}><Input type="number" step="0.05" value={val} onChange={(e) => setNested('haircut', k, Number(e.target.value))} disabled={!canEdit} /></Field>)}</div>
      {flash && <div className="flash good">{flash}</div>}
      {canEdit && <button className="app-btn primary" onClick={save} disabled={!v}>Save as a new version</button>}
      {(d.versions || []).length > 0 && ctx.view === 'impl' && <div className="mono" style={{ fontSize: 11, color: 'var(--fg-subtle)', marginTop: 14 }}>{d.versions.length} prior version(s): {d.versions.map((x) => `v${x.version} ${shortDate(x.updated_at)}`).join(' · ')}</div>}
    </div>);
}

Object.assign(window, { ExecuteModule });
