/* Stack Intelligence: overview, the Discovery Room, the inventory, and
   recommendations. Everything reads through ctx.token for ctx.org; the
   server decides what the role may do and the UI shows the refusal. */

function useLojo(ctx, payload, deps) {
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState('');
  const [tick, setTick] = React.useState(0);
  React.useEffect(() => {
    let live = true;
    lojo(ctx.token, { ...payload, org: ctx.org }).then((d) => { if (!live) return; if (d.ok) { setData(d); setError(''); } else setError(d.message || d.error || 'Could not load.'); });
    return () => { live = false; };
  }, [ctx.org, ctx.token, tick, ...(deps || [])]);
  return [data, error, () => setTick((t) => t + 1)];
}

function SubTabs({ tabs, value, onChange }) {
  return <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 20 }}>
    {tabs.map(([id, label]) => <button key={id} className={'tour-tab' + (value === id ? ' on' : '')} onClick={() => onChange(id)} style={{ padding: '7px 13px', fontSize: 13 }}>{label}</button>)}
  </div>;
}

function StackModule({ ctx }) {
  const [tab, setTab] = React.useState(() => new URLSearchParams(window.location.search).get('t') || 'overview');
  return (
    <div>
      <SubTabs value={tab} onChange={setTab} tabs={[['overview', 'Overview'], ['discovery', 'Discovery room'], ['inventory', 'Inventory'], ['recommendations', 'Recommendations']]} />
      {tab === 'overview' && <StackOverview ctx={ctx} go={setTab} />}
      {tab === 'discovery' && <DiscoveryRoom ctx={ctx} />}
      {tab === 'inventory' && <Inventory ctx={ctx} />}
      {tab === 'recommendations' && <Recommendations ctx={ctx} />}
    </div>);
}

/* ---------------------------------------------------------------- overview */
function StackOverview({ ctx, go }) {
  const [d, error] = useLojo(ctx, { module: 'stack', action: 'overview' });
  if (error) return <div className="empty">{error}</div>;
  if (!d) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const inv = d.inventory, recs = d.recommendations, led = d.opportunities;
  const asm = d.assessments[0];
  const empty = inv.subscriptions === 0 && !asm;
  const maxCat = Math.max(1, ...inv.by_category.map((c) => c.annual_usd));
  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', gap: 12, flexWrap: 'wrap', marginBottom: 16 }}>
        <div>
          <div className="overline" style={{ marginBottom: 6 }}>Stack intelligence</div>
          <h1 className="display" style={{ fontSize: 'clamp(24px,3.6vw,34px)', margin: 0 }}>{d.org.name}</h1>
          {d.org.profile && d.org.profile.industry && <div style={{ color: 'var(--fg-muted)', fontSize: 14, marginTop: 4 }}>{d.org.profile.industry}{d.org.profile.size_band ? ` · ${d.org.profile.size_band} people` : ''}</div>}
        </div>
        {!ctx.readOnly && <div style={{ display: 'flex', gap: 8 }}>
          <button className="app-btn" onClick={() => go('discovery')}>Discovery room</button>
          <button className="app-btn primary" onClick={() => go('inventory')}>Upload inventory</button>
        </div>}
      </div>
      {empty &&
      <div className="empty" style={{ marginBottom: 16 }}>
        <b>Start with the inventory.</b> Upload a CSV of your software subscriptions (product, cost, seats, active seats, renewal) and recommendations appear at once.
        The Discovery Room fills in the rest at your pace, and you can hand sections to the people who own them.
      </div>}
      <div className="app-grid cols-4" style={{ marginBottom: 16 }}>
        <Stat label="Annual software spend" value={money(inv.annual_spend_usd)} note={`${inv.subscriptions} active subscriptions`} />
        <Stat label="Idle seat cost" value={money(inv.idle_seat_cost_usd)} note={inv.seats ? `${inv.seats_active} of ${inv.seats} seats active` : 'seat data not provided'} tone={inv.idle_seat_cost_usd > 0 ? 'warn' : undefined} />
        <Stat label="Recommended annual benefit" value={money(recs.annual_net_benefit_usd)} note={`${recs.count} recommendations`} tone="accent" />
        <Stat label="Opportunity NPV" value={money(led.npv_usd, { compact: true })} note={`${led.count} opportunities · ${money(led.approved_npv_usd, { compact: true })} approved`} />
      </div>
      <div className="app-grid cols-2">
        <div className="app-panel">
          <h3>Spend by category</h3>
          {inv.by_category.length === 0 ? <div style={{ color: 'var(--fg-subtle)', fontSize: 13.5 }}>No subscriptions yet.</div> :
          inv.by_category.slice(0, 8).map((c) =>
          <div key={c.category} style={{ display: 'grid', gridTemplateColumns: '150px 1fr 80px', gap: 10, alignItems: 'center', fontSize: 13, padding: '5px 0' }}>
            <span style={{ color: 'var(--fg-muted)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c.label}</span><Bar value={c.annual_usd} max={maxCat} /><span className="mono" style={{ textAlign: 'right' }}>{money(c.annual_usd)}</span>
          </div>)}
        </div>
        <div className="app-panel">
          <h3>Top recommendations</h3>
          {recs.top.length === 0 ? <div style={{ color: 'var(--fg-subtle)', fontSize: 13.5 }}>Run recommendations once the inventory is in.</div> :
          recs.top.map((r) =>
          <div key={r.id} style={{ display: 'flex', justifyContent: 'space-between', gap: 10, padding: '8px 0', borderBottom: '1px solid var(--line)', fontSize: 13.5, cursor: 'pointer' }} onClick={() => go('recommendations')}>
            <span><KindPill kind={r.kind} /> <span style={{ marginLeft: 6 }}>{r.title}</span></span>
            <span className="mono" style={{ color: 'var(--good)', whiteSpace: 'nowrap' }}>{money(r.annual_net_benefit_usd)}/yr</span>
          </div>)}
        </div>
        <div className="app-panel">
          <h3>Discovery</h3>
          {asm ? <div>
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13.5, marginBottom: 6 }}><span>{asm.title}</span><span className="mono">{asm.progress.pct || 0}%</span></div>
            <Bar value={asm.progress.pct || 0} max={100} />
            <div style={{ fontSize: 12.5, color: 'var(--fg-muted)', marginTop: 8 }}>{asm.progress.required_done || 0} of {asm.progress.required || 0} required answers · status {asm.status}</div>
            <button className="app-btn small" style={{ marginTop: 10 }} onClick={() => go('discovery')}>Open the discovery room</button>
          </div> : <div style={{ color: 'var(--fg-subtle)', fontSize: 13.5 }}>No assessment yet. {!ctx.readOnly && <button className="app-btn small" onClick={() => go('discovery')}>Start one</button>}</div>}
          {ctx.view === 'impl' && <div className="mono" style={{ fontSize: 11.5, color: 'var(--fg-subtle)', marginTop: 12 }}>{d.evidence} evidence items · {d.capabilities} capabilities · {d.workflows} workflows</div>}
        </div>
        <div className="app-panel">
          <h3>Next renewals</h3>
          {inv.next_renewals.length === 0 ? <div style={{ color: 'var(--fg-subtle)', fontSize: 13.5 }}>No renewal dates on file.</div> :
          <table className="app-table"><tbody>{inv.next_renewals.map((r) => <tr key={r.id}><td>{r.product_name}</td><td className="mono">{r.renewal_date}</td><td className="num">{money(r.annual_cost)}</td></tr>)}</tbody></table>}
        </div>
      </div>
    </div>);
}

/* ---------------------------------------------------------- discovery room */
function DiscoveryRoom({ ctx }) {
  const [list, error, reload] = useLojo(ctx, { module: 'stack', action: 'assessments' });
  const [open, setOpen] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const create = async () => { setBusy(true); const d = await lojo(ctx.token, { module: 'stack', action: 'assessment-create', org: ctx.org, template_id: 'stack-discovery' }); setBusy(false); if (d.ok) { reload(); setOpen(d.assessment.id); } };
  if (error) return <div className="empty">{error}</div>;
  if (!list) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const current = open || (list.assessments[0] || {}).id;
  if (!current) return <div className="empty"><b>The Discovery Room</b> is a guided assessment: what the business runs on, what it pays, where the work happens. Answer what you know and delegate sections to the people who own them.<br />
    {!ctx.readOnly && <button className="app-btn primary" style={{ marginTop: 12 }} onClick={create} disabled={busy}>Start the stack discovery</button>}</div>;
  return <Assessment ctx={ctx} id={current} all={list.assessments} onSwitch={setOpen} onNew={create} />;
}

function Assessment({ ctx, id, all, onSwitch, onNew }) {
  const [d, error, reload] = useLojo(ctx, { module: 'stack', action: 'assessment', assessment_id: id }, [id]);
  const [section, setSection] = React.useState('');
  const [flash, setFlash] = React.useState('');
  const [delegating, setDelegating] = React.useState(false);
  if (error) return <div className="empty">{error}</div>;
  if (!d) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const a = d.assessment;
  const sec = a.sections.find((s) => s.id === section) || a.sections[0];
  const val = (a.validation || []).find((v) => v.section_id === sec.id) || {};
  const say = (m) => { setFlash(m); setTimeout(() => setFlash(''), 1800); };
  const save = async (q, value, evidence_ids) => {
    const r = await lojo(ctx.token, { module: 'stack', action: 'answer', org: ctx.org, assessment_id: a.id, section_id: sec.id, question_id: q.id, value, evidence_ids: evidence_ids || [] });
    if (r.ok) { say('Saved'); reload(); } else say(r.message || 'Not saved');
  };
  const upload = async (q, file) => {
    const b64 = await fileToBase64(file);
    const r = await lojo(ctx.token, { module: 'stack', action: 'evidence-add', org: ctx.org, filename: file.name, content_type: file.type || 'application/octet-stream', content_base64: b64, title: file.name, grade: 'B', source: `Uploaded in the discovery room (${sec.title})` });
    if (!r.ok) { say(r.message || 'Upload failed'); return; }
    await save(q, { filename: file.name }, [r.evidence.id]);
    if (q.id === 'upload' && sec.id === 'inventory' && /csv|text/.test(file.type || file.name)) {
      const imp = await lojo(ctx.token, { module: 'stack', action: 'inventory-import', org: ctx.org, evidence_id: r.evidence.id });
      say(imp.ok ? `Inventory imported: ${imp.created} added, ${imp.updated} updated` : (imp.message || 'Uploaded; could not parse as an inventory'));
    }
  };
  const requestsForSection = d.requests.filter((r) => (r.section_ids || []).includes(sec.id) && r.status === 'open');
  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'flex-end', flexWrap: 'wrap', marginBottom: 14 }}>
        <div>
          <div className="overline" style={{ marginBottom: 6 }}>Discovery room</div>
          <h2 className="display" style={{ fontSize: 26, margin: 0 }}>{a.title}</h2>
        </div>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          {all.length > 1 && <Select value={a.id} onChange={onSwitch} options={all.map((x) => [x.id, `${x.title} · ${shortDate(x.created_at)}`])} style={{ width: 'auto' }} />}
          {!ctx.readOnly && <button className="app-btn small" onClick={() => setDelegating(true)}>Delegate a section</button>}
          {flash && <span style={{ fontSize: 13, color: 'var(--fg-muted)' }}>{flash}</span>}
        </div>
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, marginBottom: 6 }}><span style={{ color: 'var(--fg-muted)' }}>{a.progress.required_done} of {a.progress.required} required answers · {a.progress.answered} of {a.progress.questions} in all</span><span className="mono">{a.progress.pct}%</span></div>
      <Bar value={a.progress.pct} max={100} />
      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(200px, 260px) 1fr', gap: 18, marginTop: 18 }} className="discovery-grid">
        <div className="section-nav">
          {a.sections.map((s) => {
            const p = (a.progress.sections || []).find((x) => x.section_id === s.id) || {};
            const tone = p.status === 'complete' ? 'good' : p.status === 'partial' ? 'accent' : 'fg-subtle';
            return <button key={s.id} className={s.id === sec.id ? 'on' : ''} onClick={() => setSection(s.id)}>
              <span>{s.title}</span><span className="mono" style={{ fontSize: 10.5, color: `var(--${tone})` }}>{p.answered || 0}/{p.questions || 0}</span></button>;
          })}
          <div style={{ fontSize: 12, color: 'var(--fg-subtle)', padding: '10px 10px 0', lineHeight: 1.5 }}>Answers save as you go. Required questions are marked *.</div>
        </div>
        <div>
          <div className="app-panel" style={{ marginBottom: 12 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'flex-start' }}>
              <div><h3 style={{ marginBottom: 4 }}>{sec.title}</h3>{sec.intro && <div style={{ fontSize: 13.5, color: 'var(--fg-muted)', lineHeight: 1.6 }}>{sec.intro}</div>}</div>
              {sec.owner_hint && <span className="chip" style={{ flex: '0 0 auto' }}>Usually: {sec.owner_hint}</span>}
            </div>
            {val.missing && val.missing.length > 0 && <div style={{ marginTop: 10, fontSize: 12.5, color: 'var(--warn)' }}>Still needed: {val.missing.map((m) => m.text).join(' · ')}</div>}
            {requestsForSection.length > 0 && <div style={{ marginTop: 8, fontSize: 12.5, color: 'var(--accent)' }}>Delegated to {requestsForSection.map((r) => r.to_name || r.to_email).join(', ')} (open)</div>}
          </div>
          {sec.questions.map((q) => {
            const r = d.responses[sec.id + '/' + q.id];
            return <div key={q.id}>
              <QuestionInput q={q} initial={r && r.value} disabled={ctx.readOnly} onSave={(v) => save(q, v, r && r.evidence_ids)} onUpload={(file) => upload(q, file)} />
              {r && ctx.view === 'impl' && <div className="mono" style={{ fontSize: 11, color: 'var(--fg-subtle)', margin: '-6px 0 10px 4px' }}>by {r.answered_by || '—'}{r.delegated ? ' (delegated)' : ''} · {shortDate(r.updated_at)}{(r.evidence_ids || []).length ? ` · evidence ${r.evidence_ids.join(', ')}` : ''}</div>}
            </div>;
          })}
        </div>
      </div>
      {d.requests.length > 0 &&
      <div className="app-panel" style={{ marginTop: 18 }}>
        <h3>Data requests</h3>
        <table className="app-table"><thead><tr><th>To</th><th>Sections</th><th>Status</th><th>Sent</th><th></th></tr></thead><tbody>
          {d.requests.map((r) => <tr key={r.id}><td>{r.to_name ? `${r.to_name} · ` : ''}{r.to_email}</td><td>{(r.section_ids || []).map((s) => (a.sections.find((x) => x.id === s) || {}).title || s).join(', ')}</td>
            <td><Pill tone={r.status === 'answered' ? 'good' : r.status === 'open' ? 'accent' : 'fg-subtle'}>{r.status}</Pill></td><td className="mono">{shortDate(r.created_at)}</td>
            <td>{r.status === 'open' && !ctx.readOnly && <button className="app-btn small" onClick={async () => { await lojo(ctx.token, { module: 'stack', action: 'request-revoke', org: ctx.org, request_id: r.id }); reload(); }}>Revoke</button>}</td></tr>)}
        </tbody></table>
      </div>}
      {delegating && <DelegateDrawer ctx={ctx} assessment={a} onClose={() => { setDelegating(false); reload(); }} />}
    </div>);
}

function DelegateDrawer({ ctx, assessment, onClose }) {
  const [f, setF] = React.useState({ to_email: '', to_name: '', message: '', section_ids: [] });
  const [result, setResult] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const toggle = (id) => setF({ ...f, section_ids: f.section_ids.includes(id) ? f.section_ids.filter((x) => x !== id) : [...f.section_ids, id] });
  const send = async () => {
    setBusy(true);
    const d = await lojo(ctx.token, { module: 'stack', action: 'request-create', org: ctx.org, assessment_id: assessment.id, ...f, origin: window.location.origin });
    setBusy(false); setResult(d);
  };
  return (
    <Drawer title="Delegate sections" onClose={onClose} width={560}>
      {result && result.ok ? <div>
        <div className="flash good">Request created{result.emailed ? ' and emailed' : ''}.</div>
        <p style={{ fontSize: 13.5, color: 'var(--fg-muted)', lineHeight: 1.6 }}>{result.emailed ? 'They also get this link by email.' : 'Email is not configured on this deployment, so send them this link yourself.'} It opens only the chosen sections, needs no account, and works for 14 days.</p>
        <div className="mono" style={{ fontSize: 12, wordBreak: 'break-all', padding: '10px 12px', background: 'var(--navy-2)', borderRadius: 'var(--r-md)', border: '1px solid var(--line)' }}>{result.link}</div>
        <div style={{ marginTop: 10 }}><CopyButton text={result.link} label="Copy link" /></div>
      </div> : <div>
        <Group label="Sections to hand over">
          {assessment.sections.map((s) => <label key={s.id} style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 13.5, padding: '5px 0' }}>
            <input type="checkbox" checked={f.section_ids.includes(s.id)} onChange={() => toggle(s.id)} /> {s.title} <span style={{ color: 'var(--fg-subtle)', fontSize: 12 }}>{s.owner_hint}</span></label>)}
        </Group>
        <div className="app-grid cols-2">
          <Field label="Their email"><Input type="email" value={f.to_email} onChange={(e) => setF({ ...f, to_email: e.target.value })} /></Field>
          <Field label="Their name"><Input value={f.to_name} onChange={(e) => setF({ ...f, to_name: e.target.value })} /></Field>
        </div>
        <Field label="A note from you"><textarea className="app-input" rows={3} value={f.message} onChange={(e) => setF({ ...f, message: e.target.value })} placeholder="What you need and by when." /></Field>
        {result && !result.ok && <div className="flash warn">{result.message || 'Could not create the request.'}</div>}
        <button className="btn btn-primary" disabled={busy || !f.to_email || !f.section_ids.length} onClick={send}>{busy ? 'Creating…' : 'Create the request'}</button>
      </div>}
    </Drawer>);
}

/* -------------------------------------------------------------- inventory */
const SUB_FIELDS = [['product_name', 'Product', 'text'], ['vendor_name', 'Vendor', 'text'], ['category', 'Category', 'text'], ['annual_cost', 'Annual cost (USD)', 'number'],
  ['seats', 'Seats', 'number'], ['seats_active', 'Active seats', 'number'], ['renewal_date', 'Renewal date', 'date'], ['contract_term_months', 'Term (months)', 'number'],
  ['owner', 'Owner', 'text'], ['department', 'Department', 'text'], ['notes', 'Notes', 'text']];

function Inventory({ ctx }) {
  const [part, setPart] = React.useState('subscriptions');
  return <div>
    <SubTabs value={part} onChange={setPart} tabs={[['subscriptions', 'Subscriptions'], ['capabilities', 'Capabilities'], ['workflows', 'Workflows'], ['evidence', 'Evidence']]} />
    {part === 'subscriptions' && <Subscriptions ctx={ctx} />}
    {part === 'capabilities' && <Capabilities ctx={ctx} />}
    {part === 'workflows' && <Workflows ctx={ctx} />}
    {part === 'evidence' && <EvidenceList ctx={ctx} />}
  </div>;
}

function Subscriptions({ ctx }) {
  const [d, error, reload] = useLojo(ctx, { module: 'stack', action: 'subscriptions' });
  const [edit, setEdit] = React.useState(null);
  const [flash, setFlash] = React.useState('');
  const [sort, setSort] = React.useState('annual_cost');
  const upload = async (file) => {
    const b64 = await fileToBase64(file);
    setFlash('Importing…');
    const r = await lojo(ctx.token, { module: 'stack', action: 'inventory-import', org: ctx.org, filename: file.name, content_type: file.type || 'text/csv', content_base64: b64 });
    setFlash(r.ok ? `Imported ${r.created} new and ${r.updated} updated subscriptions from ${file.name}.${r.errors.length ? ' Skipped: ' + r.errors.join(' ') : ''}` : (r.message || (r.errors || []).join(' ') || 'Import failed'));
    reload();
  };
  if (error) return <div className="empty">{error}</div>;
  if (!d) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const rows = [...d.subscriptions].sort((a, b) => sort === 'annual_cost' ? (b.annual_cost || 0) - (a.annual_cost || 0) : String(a[sort] || '').localeCompare(String(b[sort] || '')));
  const util = (s) => s.seats && s.seats_active !== null && s.seats_active !== undefined ? Math.round(s.seats_active / s.seats * 100) : null;
  return (
    <div>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 12 }}>
        <Stat label="Annual spend" value={money(d.summary.annual_spend_usd)} note={`${d.summary.subscriptions} active`} />
        <Stat label="Idle seat cost" value={money(d.summary.idle_seat_cost_usd)} note="seats paid for and unused" />
        <div style={{ flex: 1 }}></div>
        {!ctx.readOnly && <React.Fragment>
          <label className="app-btn primary" style={{ cursor: 'pointer' }}>Upload CSV<input type="file" accept=".csv,.tsv,.txt,text/csv" style={{ display: 'none' }} onChange={(e) => { const f = e.target.files && e.target.files[0]; if (f) upload(f); e.target.value = ''; }} /></label>
          <button className="app-btn" onClick={() => setEdit({})}>Add one</button>
        </React.Fragment>}
      </div>
      {flash && <div className="flash good">{flash}</div>}
      {rows.length === 0 && <div className="empty"><b>No subscriptions yet.</b> Upload a CSV with columns like product, vendor, annual cost, seats, active seats, renewal date, owner, department. Column names are matched loosely; a row needs a product and a cost.</div>}
      {rows.length > 0 && <div className="app-table-wrap"><table className="app-table">
        <thead><tr>{[['product_name', 'Product'], ['category', 'Category'], ['annual_cost', 'Annual cost'], ['seats', 'Seats'], ['renewal_date', 'Renewal'], ['owner', 'Owner']].map(([k, l]) =>
          <th key={k} className={k === 'annual_cost' ? 'num' : ''} style={{ cursor: 'pointer' }} onClick={() => setSort(k)}>{l}{sort === k ? ' ↓' : ''}</th>)}{ctx.view === 'impl' && <th>Evidence</th>}</tr></thead>
        <tbody>{rows.map((s) => <tr key={s.id} className="clickable" onClick={() => setEdit(s)}>
          <td><div style={{ fontWeight: 600 }}>{s.product_name}</div><div style={{ fontSize: 12, color: 'var(--fg-subtle)' }}>{s.vendor_name}{s.product_id ? '' : ' · not in catalog'}</div></td>
          <td style={{ color: 'var(--fg-muted)' }}>{titleCase(s.category) || '—'}</td>
          <td className="num">{money(s.annual_cost)}</td>
          <td>{s.seats ? <span>{s.seats_active !== null && s.seats_active !== undefined ? `${s.seats_active}/${s.seats}` : s.seats}{util(s) !== null && <span className="mono" style={{ color: util(s) < 70 ? 'var(--warn)' : 'var(--fg-subtle)', fontSize: 11, marginLeft: 6 }}>{util(s)}%</span>}</span> : '—'}</td>
          <td className="mono" style={{ fontSize: 12 }}>{s.renewal_date || '—'}</td><td style={{ color: 'var(--fg-muted)' }}>{s.owner || '—'}</td>
          {ctx.view === 'impl' && <td className="mono" style={{ fontSize: 11, color: 'var(--fg-subtle)' }}>{(s.evidence_ids || []).length ? s.evidence_ids.join(', ') : 'none'}</td>}
        </tr>)}</tbody></table></div>}
      {edit && <RecordDrawer ctx={ctx} title={edit.id ? edit.product_name : 'New subscription'} record={edit} fields={SUB_FIELDS} saveAction="subscription-save" deleteAction="subscription-delete" onClose={() => { setEdit(null); reload(); }} />}
    </div>);
}

/* Generic add/edit drawer for the simple collections. */
function RecordDrawer({ ctx, title, record, fields, saveAction, deleteAction, onClose, extra }) {
  const [f, setF] = React.useState(() => Object.fromEntries(fields.map(([k]) => [k, record[k] === undefined || record[k] === null ? '' : Array.isArray(record[k]) ? record[k].join(', ') : record[k]])));
  const [error, setError] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const save = async () => {
    setBusy(true); setError('');
    const data = {};
    fields.forEach(([k, , type]) => { const v = f[k]; data[k] = type === 'list' ? String(v || '').split(',').map((x) => x.trim()).filter(Boolean) : v === '' ? null : v; });
    const d = await lojo(ctx.token, { module: 'stack', action: saveAction, org: ctx.org, id: record.id, ...data });
    setBusy(false);
    if (!d.ok) { setError(d.message || Object.values(d.errors || {}).join(', ') || 'Not saved'); return; }
    onClose();
  };
  const del = async () => { if (!window.confirm('Delete this record?')) return; await lojo(ctx.token, { module: 'stack', action: deleteAction, org: ctx.org, id: record.id }); onClose(); };
  return (
    <Drawer title={title} onClose={onClose} width={560}>
      {ctx.readOnly && <div className="flash warn">Read-only organization.</div>}
      <div className="app-grid cols-2">
        {fields.map(([k, label, type, options]) => <Field key={k} label={label}>
          {type === 'select' ? <Select value={f[k]} onChange={(v) => setF({ ...f, [k]: v })} options={options} disabled={ctx.readOnly} />
            : type === 'textarea' ? <textarea className="app-input" rows={3} value={f[k]} onChange={(e) => setF({ ...f, [k]: e.target.value })} disabled={ctx.readOnly} />
            : <Input type={type === 'number' ? 'number' : type === 'date' ? 'date' : 'text'} value={f[k]} onChange={(e) => setF({ ...f, [k]: e.target.value })} disabled={ctx.readOnly} placeholder={type === 'list' ? 'comma separated' : ''} />}
        </Field>)}
      </div>
      {extra}
      {error && <div className="flash warn">{error}</div>}
      {!ctx.readOnly && <div style={{ display: 'flex', gap: 10, marginTop: 8 }}>
        <button className="app-btn primary" onClick={save} disabled={busy}>{busy ? 'Saving…' : 'Save'}</button>
        {record.id && deleteAction && <button className="app-btn danger" onClick={del}>Delete</button>}
      </div>}
      {ctx.view === 'impl' && record.id && <div className="mono" style={{ fontSize: 11, color: 'var(--fg-subtle)', marginTop: 16 }}>id {record.id} · updated {shortDate(record.updated_at)} · evidence {(record.evidence_ids || []).join(', ') || 'none'}</div>}
    </Drawer>);
}

function Capabilities({ ctx }) {
  const [d, error, reload] = useLojo(ctx, { module: 'stack', action: 'capabilities' });
  const [subs] = useLojo(ctx, { module: 'stack', action: 'subscriptions' });
  const [edit, setEdit] = React.useState(null);
  if (error) return <div className="empty">{error}</div>;
  if (!d || !subs) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const subName = (id) => ((subs.subscriptions || []).find((s) => s.id === id) || {}).product_name || id;
  const fields = [['name', 'Capability', 'text'], ['domain', 'Domain', 'text'], ['criticality', 'Criticality', 'select', ['low', 'medium', 'high', 'critical']], ['description', 'Description', 'textarea'], ['served_by', 'Served by (subscription ids)', 'list']];
  return <div>
    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
      <div style={{ fontSize: 13.5, color: 'var(--fg-muted)', maxWidth: 640, lineHeight: 1.6 }}>The business capabilities that matter, how critical each is, and which subscriptions serve it. Keep recommendations rest on this; overlap and automation use it to find candidates.</div>
      {!ctx.readOnly && <button className="app-btn primary" onClick={() => setEdit({})}>Add capability</button>}
    </div>
    {d.capabilities.length === 0 ? <div className="empty">No capabilities yet.</div> :
    <table className="app-table"><thead><tr><th>Capability</th><th>Domain</th><th>Criticality</th><th>Served by</th></tr></thead><tbody>
      {d.capabilities.map((c) => <tr key={c.id} className="clickable" onClick={() => setEdit(c)}><td style={{ fontWeight: 600 }}>{c.name}</td><td style={{ color: 'var(--fg-muted)' }}>{c.domain}</td>
        <td><Pill tone={c.criticality === 'critical' ? 'crit' : c.criticality === 'high' ? 'warn' : 'fg-subtle'}>{c.criticality}</Pill></td><td style={{ color: 'var(--fg-muted)' }}>{(c.served_by || []).map(subName).join(', ') || '—'}</td></tr>)}
    </tbody></table>}
    {edit && <RecordDrawer ctx={ctx} title={edit.id ? edit.name : 'New capability'} record={edit} fields={fields} saveAction="capability-save" deleteAction="capability-delete" onClose={() => { setEdit(null); reload(); }}
      extra={<div style={{ fontSize: 12, color: 'var(--fg-subtle)', marginBottom: 10 }}>Subscription ids: {(subs.subscriptions || []).map((s) => `${s.product_name} = ${s.id}`).join(' · ') || 'none yet'}</div>} />}
  </div>;
}

function Workflows({ ctx }) {
  const [d, error, reload] = useLojo(ctx, { module: 'stack', action: 'workflows' });
  const [caps] = useLojo(ctx, { module: 'stack', action: 'capabilities' });
  const [edit, setEdit] = React.useState(null);
  if (error) return <div className="empty">{error}</div>;
  if (!d || !caps) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const capOptions = [['', 'None'], ...caps.capabilities.map((c) => [c.id, c.name])];
  const fields = [['name', 'Workflow', 'text'], ['capability_id', 'Capability', 'select', capOptions], ['department', 'Department', 'text'], ['description', 'What happens', 'textarea'],
    ['volume_per_month', 'Items per month', 'number'], ['minutes_per_item', 'Minutes per item', 'number'], ['people_involved', 'People involved', 'number'],
    ['labour_band', 'Who does it', 'select', [['ops', 'Operations'], ['specialist', 'Specialist'], ['manager', 'Manager'], ['executive', 'Executive']]],
    ['systems', 'Systems touched', 'list'], ['pain_points', 'Pain points', 'list'], ['outcome', 'Outcome it produces', 'text'],
    ['automatable', 'Automatable?', 'select', [['unknown', 'Not assessed'], ['yes', 'Yes'], ['partly', 'Partly'], ['no', 'No']]],
    ['value_to_customer', 'Value to the customer', 'select', [['unknown', 'Not assessed'], ['direct', 'Direct'], ['indirect', 'Indirect'], ['none', 'None']]]];
  const hours = (w) => Math.round((w.volume_per_month || 0) * (w.minutes_per_item || 0) / 60);
  return <div>
    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
      <div style={{ fontSize: 13.5, color: 'var(--fg-muted)', maxWidth: 640, lineHeight: 1.6 }}>The repetitive work: volume, minutes per item, who does it and which systems it touches. This is what decides whether an agent, a consolidation or a redesign pays.</div>
      {!ctx.readOnly && <button className="app-btn primary" onClick={() => setEdit({})}>Add workflow</button>}
    </div>
    {d.workflows.length === 0 ? <div className="empty">No workflows yet.</div> :
    <table className="app-table"><thead><tr><th>Workflow</th><th>Department</th><th className="num">Hours / month</th><th>Automatable</th><th>Systems</th></tr></thead><tbody>
      {d.workflows.map((w) => <tr key={w.id} className="clickable" onClick={() => setEdit(w)}><td style={{ fontWeight: 600 }}>{w.name}</td><td style={{ color: 'var(--fg-muted)' }}>{w.department}</td>
        <td className="num">{hours(w)}</td><td><Pill tone={w.automatable === 'yes' ? 'good' : w.automatable === 'partly' ? 'accent' : 'fg-subtle'}>{w.automatable}</Pill></td><td style={{ color: 'var(--fg-muted)', fontSize: 12.5 }}>{(w.systems || []).join(', ')}</td></tr>)}
    </tbody></table>}
    {edit && <RecordDrawer ctx={ctx} title={edit.id ? edit.name : 'New workflow'} record={edit} fields={fields} saveAction="workflow-save" deleteAction="workflow-delete" onClose={() => { setEdit(null); reload(); }} />}
  </div>;
}

function EvidenceList({ ctx }) {
  const [d, error, reload] = useLojo(ctx, { module: 'stack', action: 'evidence' });
  const [flash, setFlash] = React.useState('');
  const upload = async (file) => {
    const b64 = await fileToBase64(file);
    const r = await lojo(ctx.token, { module: 'stack', action: 'evidence-add', org: ctx.org, filename: file.name, content_type: file.type || 'application/octet-stream', content_base64: b64, title: file.name, grade: 'B', source: 'Uploaded to the evidence list' });
    setFlash(r.ok ? `Added ${file.name}` : (r.message || 'Upload failed')); reload();
  };
  if (error) return <div className="empty">{error}</div>;
  if (!d) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  return <div>
    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, gap: 10, flexWrap: 'wrap' }}>
      <div style={{ fontSize: 13.5, color: 'var(--fg-muted)', maxWidth: 640, lineHeight: 1.6 }}>Everything a finding rests on: uploads (hashed), interview notes, catalog references. Grade A is primary evidence, B is stated by the organization, C is indicative.</div>
      {!ctx.readOnly && <label className="app-btn" style={{ cursor: 'pointer' }}>Add a file<input type="file" style={{ display: 'none' }} onChange={(e) => { const f = e.target.files && e.target.files[0]; if (f) upload(f); e.target.value = ''; }} /></label>}
    </div>
    {flash && <div className="flash good">{flash}</div>}
    <table className="app-table"><thead><tr><th>Evidence</th><th>Kind</th><th>Grade</th><th>Source</th><th>Added</th>{ctx.view === 'impl' && <th>Hash</th>}</tr></thead><tbody>
      {d.evidence.map((e) => <tr key={e.id}><td><div style={{ fontWeight: 600 }}>{e.title}</div>{e.summary && <div style={{ fontSize: 12.5, color: 'var(--fg-muted)' }}>{e.summary}</div>}</td><td>{e.kind}</td><td><Pill tone={e.grade === 'A' ? 'good' : e.grade === 'B' ? 'accent' : 'warn'}>{e.grade}</Pill></td>
        <td style={{ color: 'var(--fg-muted)', fontSize: 12.5 }}>{e.source}</td><td className="mono" style={{ fontSize: 12 }}>{shortDate(e.created_at)}</td>{ctx.view === 'impl' && <td className="mono" style={{ fontSize: 10.5, color: 'var(--fg-subtle)' }}>{(e.sha256 || '').slice(0, 12)}</td>}</tr>)}
    </tbody></table>
  </div>;
}

/* --------------------------------------------------------- recommendations */
function Recommendations({ ctx }) {
  const [d, error, reload] = useLojo(ctx, { module: 'stack', action: 'recommendations' });
  const [kind, setKind] = React.useState('');
  const [open, setOpen] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [flash, setFlash] = React.useState('');
  const run = async () => { setBusy(true); const r = await lojo(ctx.token, { module: 'stack', action: 'recommend-run', org: ctx.org }); setBusy(false); setFlash(r.ok ? `${r.count} recommendations from the current inventory, capabilities and workflows.` : (r.message || 'Could not run')); reload(); };
  if (error) return <div className="empty">{error}</div>;
  if (!d) return <div style={{ color: 'var(--fg-subtle)' }}>Loading…</div>;
  const recs = d.recommendations.filter((r) => r.status !== 'dismissed' && (!kind || r.kind === kind))
    .sort((a, b) => (a.kind === 'keep') - (b.kind === 'keep') || ((b.economics || {}).npv_usd || 0) - ((a.economics || {}).npv_usd || 0));
  const kinds = ['consolidate', 'optimize', 'replace', 'automate', 'eliminate', 'keep'];
  return (
    <div>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', marginBottom: 12 }}>
        <button className={'app-btn small' + (!kind ? ' primary' : '')} onClick={() => setKind('')}>All</button>
        {kinds.map((k) => <button key={k} className={'app-btn small' + (kind === k ? ' primary' : '')} onClick={() => setKind(k)}>{KIND_LABEL[k]}</button>)}
        <div style={{ flex: 1 }}></div>
        {!ctx.readOnly && <button className="app-btn primary" onClick={run} disabled={busy}>{busy ? 'Running…' : 'Run recommendations'}</button>}
      </div>
      {flash && <div className="flash good">{flash}</div>}
      {recs.length === 0 && <div className="empty"><b>Nothing yet.</b> Recommendations come from deterministic rules over your subscriptions, capabilities and workflows: overlap, idle seats, cheaper equivalents, automatable work, work with no outcome, renewals worth negotiating. Upload the inventory and run them.</div>}
      <div className="app-grid cols-2">
        {recs.map((r) => {
          const e = r.economics || {};
          return <div key={r.id} className="rec-card" onClick={() => setOpen(r)}>
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', marginBottom: 8 }}>
              <KindPill kind={r.kind} />{r.status !== 'new' && <Pill tone={r.status === 'converted' ? 'good' : 'accent'}>{r.status}</Pill>}
            </div>
            <div style={{ fontSize: 15.5, fontWeight: 600, lineHeight: 1.35, marginBottom: 8 }}>{r.title}</div>
            {ctx.view === 'exec' ? <div style={{ fontSize: 13, color: 'var(--fg-muted)', lineHeight: 1.55, marginBottom: 10 }}>{String(r.rationale || '').split('. ')[0]}.</div> : null}
            <div style={{ display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap', fontSize: 13 }}>
              {r.kind !== 'keep' && <span><span style={{ color: 'var(--fg-subtle)' }}>Net/yr </span><b style={{ color: (e.annual_net_benefit_usd || 0) >= 0 ? 'var(--good)' : 'var(--crit)' }}>{money(e.annual_net_benefit_usd)}</b></span>}
              {r.kind !== 'keep' && <span><span style={{ color: 'var(--fg-subtle)' }}>NPV </span><b>{money(e.npv_usd, { compact: true })}</b></span>}
              <span><span style={{ color: 'var(--fg-subtle)' }}>Confidence </span><Confidence c={r.confidence} /></span>
              <span><span style={{ color: 'var(--fg-subtle)' }}>Effort </span><Effort e={r.effort} /></span>
            </div>
          </div>;
        })}
      </div>
      {open && <RecommendationDrawer ctx={ctx} id={open.id} onClose={() => { setOpen(null); reload(); }} />}
    </div>);
}

function RecommendationDrawer({ ctx, id, onClose }) {
  const [d, error, reload] = useLojo(ctx, { module: 'stack', action: 'recommendation', id }, [id]);
  const [flash, setFlash] = React.useState('');
  const [busy, setBusy] = React.useState('');
  if (error) return <Drawer title="Recommendation" onClose={onClose}><div className="empty">{error}</div></Drawer>;
  if (!d) return <Drawer title="Recommendation" onClose={onClose}><div style={{ color: 'var(--fg-subtle)' }}>Loading…</div></Drawer>;
  const r = d.recommendation, e = r.economics || {};
  const act = async (payload, label) => {
    setBusy(label);
    const x = await lojo(ctx.token, { ...payload, org: ctx.org });
    setBusy('');
    if (!x.ok) { setFlash(x.message || 'That did not work.'); return x; }
    reload(); return x;
  };
  const convert = async () => {
    const x = await act({ module: 'execute', action: 'opportunity-from-recommendation', recommendation_id: r.id }, 'convert');
    if (x && x.ok) setFlash(x.existing ? 'Already an opportunity. Open it under Execute.' : 'Opportunity created. Find it under Execute.');
  };
  return (
    <Drawer title={r.title} onClose={onClose}>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', marginBottom: 14 }}>
        <KindPill kind={r.kind} /><Pill tone="fg-subtle">rule {r.rule_id}</Pill><Pill tone={r.status === 'converted' ? 'good' : 'accent'}>{r.status}</Pill>
        <span style={{ fontSize: 13 }}>Confidence <Confidence c={r.confidence} /></span><span style={{ fontSize: 13 }}>Effort <Effort e={r.effort} /></span>
      </div>
      {r.kind !== 'keep' &&
      <div className="app-grid cols-4" style={{ marginBottom: 16 }}>
        <Stat label="Net benefit / yr" value={money(e.annual_net_benefit_usd)} tone={(e.annual_net_benefit_usd || 0) >= 0 ? 'good' : 'crit'} />
        <Stat label="To implement" value={money(e.implementation_cost_usd)} />
        <Stat label="Payback" value={e.payback_month ? `month ${e.payback_month}` : '—'} />
        <Stat label={`NPV (${e.horizon_years || 3} yr)`} value={money(e.npv_usd, { compact: true })} />
      </div>}
      <p style={{ fontSize: 14.5, lineHeight: 1.65, color: 'var(--fg)', margin: '0 0 16px' }}>{r.rationale}</p>
      {r.ai_notes && r.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 }}>{r.ai_notes.text}</div>
        <div className="mono" style={{ fontSize: 10.5, color: 'var(--fg-subtle)', marginTop: 6 }}>{r.ai_notes.model} · cited {(r.ai_notes.cited || []).join(', ') || 'nothing'} · never changes a number</div>
      </div>}
      {(r.targets || []).length > 0 && <div style={{ marginBottom: 14 }}><div className="overline" style={{ marginBottom: 6 }}>Involves</div>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}><span className="chip">{r.subject.type}: {r.subject.name}</span>{r.targets.map((t, i) => <span key={i} className="chip">{t.type}: {t.name}</span>)}</div></div>}
      {r.kind !== 'keep' && <div style={{ marginBottom: 14 }}><div className="overline" style={{ marginBottom: 6 }}>How the money is computed</div><Lines lines={e.lines} />
        <div className="mono" style={{ fontSize: 11, color: 'var(--fg-subtle)', marginTop: 6 }}>Inputs: {Object.entries(e.inputs || {}).map(([k, v]) => `${k}=${v}`).join(' · ')}</div></div>}
      <div style={{ marginBottom: 14 }}><div className="overline" style={{ marginBottom: 6 }}>Evidence</div>
        {(d.evidence || []).filter(Boolean).map((ev) => <div key={ev.id} style={{ fontSize: 13, padding: '6px 0', borderBottom: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'space-between' }}><span>{ev.title}<span style={{ color: 'var(--fg-subtle)' }}> · {ev.kind}</span></span><Pill tone={ev.grade === 'A' ? 'good' : ev.grade === 'B' ? 'accent' : 'warn'}>{ev.grade}</Pill></div>)}
        {(d.evidence || []).filter(Boolean).length === 0 && <div style={{ fontSize: 13, color: 'var(--fg-subtle)' }}>No evidence attached: this one is weak by design.</div>}</div>
      {(r.confidence && r.confidence.basis || []).length > 0 && <div style={{ fontSize: 12.5, color: 'var(--fg-muted)', marginBottom: 14 }}>Confidence basis: {r.confidence.basis.join(' · ')}</div>}
      {flash && <div className="flash good">{flash}</div>}
      {!ctx.readOnly && <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        {r.kind !== 'keep' && r.status !== 'converted' && <button className="app-btn primary" disabled={!!busy} onClick={convert}>Make it an opportunity</button>}
        {r.status === 'converted' && <button className="app-btn" onClick={() => ctx.setModule('execute')}>Open in Execute</button>}
        {r.status === 'new' && <button className="app-btn" disabled={!!busy} onClick={() => act({ module: 'stack', action: 'recommendation-status', id: r.id, status: 'accepted' }, 'accept')}>Accept</button>}
        {r.status !== 'converted' && <button className="app-btn" disabled={!!busy} onClick={() => act({ module: 'stack', action: 'recommendation-status', id: r.id, status: 'dismissed' }, 'dismiss')}>Dismiss</button>}
        {ctx.orgData && ctx.orgData.org.settings && ctx.orgData.org.settings.ai_assist !== false && <button className="app-btn" disabled={!!busy} onClick={() => act({ module: 'stack', action: 'explain', id: r.id }, 'explain')}>{busy === 'explain' ? 'Writing…' : 'Explain with AI'}</button>}
      </div>}
      {ctx.view === 'impl' && <div className="mono" style={{ fontSize: 11, color: 'var(--fg-subtle)', marginTop: 16 }}>id {r.id} · version {r.version} · {(d.versions || []).length} prior · updated {shortDate(r.updated_at)}</div>}
    </Drawer>);
}

Object.assign(window, { useLojo, SubTabs, StackModule, RecordDrawer });
