// ===== Reusable visual components =====

const { useState, useEffect, useRef, useMemo } = React;

// ----- Donut chart (animated) -----
function Donut({ allocation, total = 100 }) {
  const radius = 50;
  const circumference = 2 * Math.PI * radius;
  let offsetAcc = 0;
  return (
    <div className="donut">
      <svg viewBox="0 0 130 130">
        <circle cx="65" cy="65" r={radius} stroke="#efede8" strokeWidth="18" fill="none" />
        {allocation.map((s, i) => {
          const dash = (s.pct / total) * circumference;
          const offset = offsetAcc;
          offsetAcc += dash;
          return (
            <circle
              key={s.id}
              cx="65" cy="65" r={radius}
              stroke={SLEEVE_COLORS[s.id] || '#999'}
              strokeWidth="18"
              fill="none"
              strokeDasharray={`${dash} ${circumference - dash}`}
              strokeDashoffset={-offset}
            />
          );
        })}
      </svg>
      <div className="center">
        <div>
          <div className="ey">Allocation</div>
          <div className="vl">{allocation.length}<span style={{fontSize:11,color:'var(--ink-3)'}}>&nbsp;sleeves</span></div>
        </div>
      </div>
    </div>
  );
}

// ----- Allocation legend with deltas -----
function AllocationLegend({ current, proposed }) {
  // build a map of current pct
  const currentMap = Object.fromEntries(current.map(s => [s.id, s.pct]));
  return (
    <div className="alloc-legend">
      {proposed.map(s => {
        const wasIn = s.id in currentMap;
        const cur = currentMap[s.id] || 0;
        const delta = s.pct - cur;
        let cls = '';
        if (!wasIn) cls = 'new';
        else if (Math.abs(delta) >= 0.5) cls = delta > 0 ? 'up' : 'down';
        return (
          <div key={s.id} className={`row ${cls}`}>
            <span className="sw" style={{ background: SLEEVE_COLORS[s.id] || '#999' }} />
            <span className="nm">{SLEEVE_LABELS[s.id]}</span>
            <span className="vl">
              {s.pct.toFixed(1)}%
              {wasIn && Math.abs(delta) >= 0.5 && (
                <span style={{marginLeft:4, fontSize:10}}>
                  {delta > 0 ? '↑' : '↓'}{Math.abs(delta).toFixed(1)}
                </span>
              )}
            </span>
          </div>
        );
      })}
    </div>
  );
}

// ----- Live KPI grid -----
function KpiGrid({ kpis, prevKpis }) {
  const items = [
    { k: 'yield',    lbl: 'Yield',    ds: 'projected' },
    { k: 'income',   lbl: 'Income',   ds: 'projected' },
    { k: 'drawdown', lbl: 'Max DD',   ds: 'stress test' },
    { k: 'return',   lbl: 'Return',   ds: 'projected' },
  ];
  return (
    <div className="kpi-grid">
      {items.map(({ k, lbl, ds }) => {
        const changed = prevKpis && prevKpis[k] !== kpis[k];
        return (
          <div key={k} className={`kpi ${changed ? 'changed' : ''}`}>
            <div className="lbl">{lbl}</div>
            <div className="vl">{kpis[k]}</div>
            <div className="ds">{ds}</div>
          </div>
        );
      })}
    </div>
  );
}

// ----- Plan tree (left rail) — clickable; parent decides reachability -----
// Each `step` here is a "view-model" with state already computed:
//   state:     'done' | 'active' | 'future' | 'unlocked' | 'locked' | 'skipped' | 'auto'
//   reachable: whether the user is allowed to click on it
//   badge:     short label appended next to the title (e.g. "optional", "just unlocked")
function PlanTree({ steps, onNavigate }) {
  return (
    <nav className="plan">
      {steps.map(step => {
        const cls = [
          'step',
          step.state,
          step.reachable ? 'reachable' : 'unreachable',
          step.modified ? 'modified' : '',
        ].filter(Boolean).join(' ');
        const handleClick = step.reachable ? () => onNavigate(step.id) : undefined;
        return (
          <button
            key={step.id}
            type="button"
            className={cls}
            onClick={handleClick}
            disabled={!step.reachable}
            title={
              step.modified ? `Just modified: ${step.modifiedSummary}` :
              step.reachable
                ? (step.state === 'done' ? 'Revisit' : step.state === 'active' ? 'You are here' : 'Open')
                : (step.state === 'locked' ? 'Locked — answer earlier steps to unlock' : 'Not navigable')
            }
          >
            <span className="marker" />
            <span className="label">
              <span className="label-row">
                <span>{step.label}</span>
                {step.modified && <span className="step-badge modified-tag">just modified</span>}
                {!step.modified && step.badge && <span className={`step-badge ${step.badgeKind || ''}`}>{step.badge}</span>}
              </span>
              <small>{step.modified ? step.modifiedSummary : step.sub}</small>
            </span>
          </button>
        );
      })}
    </nav>
  );
}

// ----- Generative card frame -----
function GenCard({ q, children, footer, whyOpen, onToggleWhy }) {
  return (
    <div className="gencard fade-in" key={q.title}>
      <div className="gencard-head">
        <div className="ey">
          <span className="pulse" />
          {q.eyebrow}
        </div>
        <h3>{q.title}</h3>
        <div className="why-line">
          {q.why}
          {q.sources && q.sources.map((s, i) => (
            <span key={i} className="src-pin" title={s}>[{i+1}]</span>
          ))}
        </div>
      </div>
      <div className="gencard-body">{children}</div>
      {whyOpen && (
        <div className="why-tray">
          <strong>Why this widget, not a form?</strong>
          {q.explain}
        </div>
      )}
      <div className="gencard-foot">
        <button className="why-toggle" onClick={onToggleWhy}>
          {whyOpen ? 'Hide reasoning' : 'Why this widget?'}
        </button>
        <span className="gap" />
        {footer}
      </div>
    </div>
  );
}

// ----- Risk slider widget -----
function RiskSlider({ q, value, onChange }) {
  const opt = q.options.find(o => o.v === value);
  const knobX = opt ? opt.position : 38;
  const readout = q.readouts[value];

  return (
    <div className="risk-w">
      <div className="risk-anchor">
        {q.anchors.map((a, i) => (
          <div className="ra" key={i}>
            <div className="lbl">{a.lbl}</div>
            <div className="vl">{a.vl}</div>
            <div className="ds">{a.ds}</div>
          </div>
        ))}
      </div>
      <div className="slider-w">
        <div className="slider-track">
          <div className="anchor-mark" style={{ left: '38%' }}>2023 baseline</div>
          <div className="knob" style={{ left: `${knobX}%` }} />
        </div>
        <div className="slider-labels">
          <span>Capital preservation</span>
          <span>Balanced</span>
          <span>Growth</span>
        </div>
      </div>
      <div className="chips" style={{ marginTop: 14 }}>
        {q.options.map(o => (
          <button
            key={o.v}
            className={`chip ${value === o.v ? 'sel' : ''} ${o.recommended ? 'recommended' : ''}`}
            onClick={() => onChange(o.v)}
          >
            {o.label}
          </button>
        ))}
      </div>
      {readout && (
        <div className="slider-readout">
          {readout}
        </div>
      )}
    </div>
  );
}

// ----- Liquidity widget -----
function LiquidityWidget({ items, onToggle, onAdd }) {
  return (
    <div className="liq-w">
      <div className="liq-list">
        {items.map((it, i) => (
          <div key={it.id} className={`liq-item ${it.detected ? 'detected' : ''}`}>
            <div>
              <div className="nm">{it.name}</div>
              <small style={{ display:'block', fontSize: 11.5, color: 'var(--ink-3)', fontFamily: 'var(--mono)', marginTop: 2 }}>
                {it.window} · {it.purpose}
              </small>
            </div>
            <div style={{display:'flex', alignItems:'center', gap: 14}}>
              <div className="amt">{it.amt}</div>
              <div className={`toggle ${it.on ? 'on' : ''}`} onClick={() => onToggle(it.id)} />
            </div>
          </div>
        ))}
      </div>
      <button
        className="chip"
        style={{ width:'auto' }}
        onClick={onAdd}
      >
        + Add another reserve
      </button>
    </div>
  );
}

// ----- Currency widget -----
function CurrencyWidget({ q, value, onChange }) {
  return (
    <div>
      <div style={{
        background: 'var(--bg)',
        border: '1px solid var(--line)',
        borderRadius: 10,
        padding: '14px 16px',
        marginBottom: 14,
        display: 'grid',
        gridTemplateColumns: '1fr 1fr 1fr',
        gap: 8,
      }}>
        <div>
          <div style={{fontFamily:'var(--mono)', fontSize:10, letterSpacing:'.12em', textTransform:'uppercase', color:'var(--ink-3)', marginBottom:4}}>Family income</div>
          <div style={{fontFamily:'var(--serif)', fontSize:18, fontWeight:500}}>70 / 30</div>
          <div style={{fontSize:11.5, color:'var(--ink-3)'}}>SGD / USD</div>
        </div>
        <div>
          <div style={{fontFamily:'var(--mono)', fontSize:10, letterSpacing:'.12em', textTransform:'uppercase', color:'var(--ink-3)', marginBottom:4}}>Today's portfolio</div>
          <div style={{fontFamily:'var(--serif)', fontSize:18, fontWeight:500, color: 'var(--warn)'}}>42 / 58</div>
          <div style={{fontSize:11.5, color:'var(--ink-3)'}}>SGD / USD</div>
        </div>
        <div>
          <div style={{fontFamily:'var(--mono)', fontSize:10, letterSpacing:'.12em', textTransform:'uppercase', color:'var(--ink-3)', marginBottom:4}}>Recommendation</div>
          <div style={{fontFamily:'var(--serif)', fontSize:18, fontWeight:500, color:'var(--accent)'}}>30%</div>
          <div style={{fontSize:11.5, color:'var(--ink-3)'}}>USD ceiling</div>
        </div>
      </div>
      <div className="chips">
        {q.options.map(o => (
          <button
            key={o.v}
            className={`chip ${value === o.v ? 'sel' : ''} ${o.recommended ? 'recommended' : ''}`}
            onClick={() => onChange(o.v)}
          >
            {o.label}
          </button>
        ))}
      </div>
    </div>
  );
}

// ----- Discovery animated reveal -----
function DiscoveryView({ rows, ready, adjustments = {}, onAdjust, onContinue, ctaLabel }) {
  const [revealCount, setRevealCount] = useState(0);
  const [tickerLine, setTickerLine] = useState('');
  const [openIdx, setOpenIdx] = useState(null);

  useEffect(() => {
    if (!ready) return;
    let cancelled = false;
    let i = 0;
    function tick() {
      if (cancelled) return;
      if (i < rows.length) {
        setRevealCount(i + 1);
        i++;
        setTimeout(tick, 110);
      }
    }
    tick();
    return () => { cancelled = true; };
  }, [ready, rows.length]);

  // ticker
  useEffect(() => {
    if (!ready) return;
    let cancelled = false;
    let idx = 0;
    function tickT() {
      if (cancelled) return;
      if (idx < DISCOVERY_TIMELINE.length) {
        setTickerLine(DISCOVERY_TIMELINE[idx][1]);
        idx++;
        setTimeout(tickT, 280);
      }
    }
    tickT();
    return () => { cancelled = true; };
  }, [ready]);

  const visible = rows.slice(0, revealCount);
  const knownCount = visible.filter(r => r.src !== 'miss').length;
  const askCount = visible.filter(r => r.src === 'miss').length;
  const allDone = revealCount === rows.length;
  const adjustedCount = Object.keys(adjustments).length;

  return (
    <div className="discover-wrap">
      <div className="discover-counter">
        <div>
          <div className="ey">Discovery — what I already know</div>
          <h3>I pulled what I could before I asked you anything.</h3>
          <p>Of the 31 fields a portfolio proposal needs, I have 27 from systems of record and your brief. Click any row below to override the value or flag it for follow-up — your changes flow into the proposal.</p>
        </div>
        <div className="num">
          {knownCount}<span style={{color:'rgba(255,255,255,.4)'}}>/31</span>
          <small>{tickerLine || 'fields ready'}</small>
        </div>
      </div>

      <div className="discover-bars">
        <div className="discover-bar">
          <div className="lbl good">Already known</div>
          <div className="vl">{visible.filter(r => r.src !== 'miss' && r.src !== 'brief').length}</div>
          <div className="ds">From CRM, custodian feed, document vault. Click to override.</div>
        </div>
        <div className="discover-bar">
          <div className="lbl accent">Inferred from your brief</div>
          <div className="vl">{visible.filter(r => r.src === 'brief').length}</div>
          <div className="ds">Read straight from your one-paragraph instruction. Click to correct.</div>
        </div>
        <div className="discover-bar">
          <div className="lbl warn">I need to ask</div>
          <div className="vl">{askCount}</div>
          <div className="ds">Material fields with no defensible default. Three quick decisions follow.</div>
        </div>
      </div>

      {adjustedCount > 0 && (
        <div className="adjust-summary">
          <span className="dot" />
          <b>{adjustedCount} adjustment{adjustedCount > 1 ? 's' : ''}</b> queued — they'll be picked up when modeling runs.
          <button className="lnk" onClick={() => visible.forEach((_, i) => onAdjust && onAdjust(i, null))}>Clear all</button>
        </div>
      )}

      <div className="discover-list">
        {visible.map((r, i) => {
          const adj = adjustments[i];
          const isOpen = openIdx === i;
          const askable = r.src !== 'miss';
          return (
            <div key={i} className={`discover-row ${adj ? 'adjusted ' + adj.status : ''} ${isOpen ? 'open' : ''} ${askable ? 'clickable' : ''}`}>
              <button
                className="row-main"
                disabled={!askable}
                onClick={() => askable && setOpenIdx(isOpen ? null : i)}
              >
                <span className={`src ${r.src}`}>{r.srcLabel}</span>
                <span className="field">
                  {r.field}
                  {adj && adj.value && <small className="ovr">→ overridden: "{adj.value}"</small>}
                  {adj && adj.status === 'flag' && <small className="ovr">flagged for follow-up</small>}
                </span>
                <span className={`conf ${r.conf}`}>{adj ? (adj.status === 'flag' ? 'flagged' : 'overridden') : r.confLabel}</span>
                {askable && <span className="caret">{isOpen ? '−' : 'edit'}</span>}
              </button>
              {isOpen && askable && (
                <RowEditor
                  row={r}
                  current={adj}
                  onSave={(payload) => { onAdjust && onAdjust(i, payload); setOpenIdx(null); }}
                  onClear={() => { onAdjust && onAdjust(i, null); setOpenIdx(null); }}
                  onCancel={() => setOpenIdx(null)}
                />
              )}
            </div>
          );
        })}
      </div>

      {allDone && (
        <div className="discover-cta fade-in">
          <div className="txt">
            {adjustedCount > 0
              ? <><b>{adjustedCount} adjustment{adjustedCount > 1 ? 's' : ''} on file.</b> The decisions branch off these — let's keep going.</>
              : <><b>Looks right?</b> Three quick decisions are next. The plan tree on the left shows where they slot in.</>
            }
          </div>
          <button
            className="next"
            onClick={onContinue}
            style={{ background: 'var(--accent)', color: 'white', border: 0, padding: '10px 18px', borderRadius: 8, fontSize: 13, fontWeight: 600, cursor: 'pointer' }}
          >
            {ctaLabel || 'Continue →'}
          </button>
        </div>
      )}
    </div>
  );
}

// ----- Inline editor for a discovery row -----
function RowEditor({ row, current, onSave, onClear, onCancel }) {
  const [mode, setMode] = useState(current?.status || 'override');
  const [value, setValue] = useState(current?.value || '');
  const [note, setNote] = useState(current?.note || '');
  return (
    <div className="row-editor">
      <div className="re-tabs">
        <button className={mode === 'override' ? 'sel' : ''} onClick={() => setMode('override')}>Override value</button>
        <button className={mode === 'flag' ? 'sel' : ''} onClick={() => setMode('flag')}>Flag for follow-up</button>
      </div>
      {mode === 'override' && (
        <>
          <label className="re-lbl">Replacement value</label>
          <input
            className="re-input"
            value={value}
            onChange={e => setValue(e.target.value)}
            placeholder={`e.g. correct the field "${row.field.slice(0, 40)}${row.field.length > 40 ? '…' : ''}"`}
            autoFocus
          />
          <label className="re-lbl">Why? <span style={{color:'var(--ink-4)'}}>(optional — goes into the audit log)</span></label>
          <input
            className="re-input"
            value={note}
            onChange={e => setNote(e.target.value)}
            placeholder="e.g. CRM is stale; client confirmed by phone"
          />
        </>
      )}
      {mode === 'flag' && (
        <>
          <label className="re-lbl">Note for the relationship manager</label>
          <input
            className="re-input"
            value={note}
            onChange={e => setNote(e.target.value)}
            placeholder="e.g. confirm with Sarah on next call"
            autoFocus
          />
          <small style={{ color: 'var(--ink-3)', fontSize: 11.5, fontFamily: 'var(--mono)' }}>
            The proposal will ship with this field flagged · supervisor sign-off required.
          </small>
        </>
      )}
      <div className="re-foot">
        {current && <button className="lnk" onClick={onClear}>Clear adjustment</button>}
        <span className="gap" />
        <button className="ghost-sm" onClick={onCancel}>Cancel</button>
        <button
          className="primary-sm"
          disabled={mode === 'override' ? !value.trim() : !note.trim()}
          onClick={() => onSave({ status: mode, value: mode === 'override' ? value : undefined, note })}
        >
          Save adjustment
        </button>
      </div>
    </div>
  );
}

// ----- Confirmed-answer pills (shown above current card) -----
function AnswersTrail({ answers, onJumpTo, modified = {} }) {
  if (!answers.length) return null;
  return (
    <div className="answers-trail">
      {answers.map(a => {
        const isMod = !!modified[a.key];
        return (
          <div key={a.key} className={`answer-pill ${isMod ? 'modified' : ''}`}>
            {isMod && <span className="mod-bar" />}
            <div>
              <div className="qq">{a.label}{isMod && <span className="mod-tag">just modified</span>}</div>
              <div className="av">
                {a.value}
                {(isMod ? <small className="mod-detail">{modified[a.key].summary}</small> : (a.detail && <small>{a.detail}</small>))}
              </div>
            </div>
            <button className="edit" onClick={() => onJumpTo(a.key)}>change</button>
          </div>
        );
      })}
    </div>
  );
}

// ----- Agent thinking bubble -----
function ThinkingCard({ lines }) {
  return (
    <div className="thinking-card">
      <div className="agent-glyph">A</div>
      <div className="lns">
        {lines.map((l, i) => (
          <div key={i} className={`ln ${l.state}`}>{l.text}</div>
        ))}
      </div>
    </div>
  );
}

// ----- Final proposal doc -----
function ProposalDoc({ baseline, proposed, kpis }) {
  const currentMap = Object.fromEntries(baseline.map(s => [s.id, s.pct]));

  return (
    <div className="doc-paper fade-in">
      <h1 className="dh">Investment Proposal — Tan Family Annual Review</h1>
      <div className="dmeta">v1 draft · 2026-05-09 · advisor sign-off pending</div>

      <h2 className="dh">Executive summary</h2>
      <p>
        Sarah has revised her retirement target to <b>age 63</b><span className="pin" title="Brief 04:12">[1]</span> to fund Maya's MBA program (US$220k over 24 months)<span className="pin" title="Brief 12:38">[2]</span>. The current allocation is overweight US equity by 6.4% relative to IPS v3<span className="pin" title="IPS v3 §4.2">[3]</span>, increasing drawdown risk in the very window the tuition will be drawn.
      </p>
      <p>
        We recommend a moderate rebalance: trim US equity, carve a dedicated short-term reserve for the tuition, and tighten the USD ceiling on liquid assets to 30%. Risk tolerance is unchanged from 2023<span className="pin" title="Confirmed by advisor 11:34">[4]</span>; suitability is preserved.
      </p>

      <h2 className="dh">Recommended allocation</h2>
      <table className="alloc">
        <thead>
          <tr>
            <th>Sleeve</th>
            <th className="right">Current</th>
            <th className="right">Proposed</th>
            <th className="right">Δ</th>
          </tr>
        </thead>
        <tbody>
          {proposed.map(s => {
            const cur = currentMap[s.id];
            const isNew = cur === undefined;
            const delta = isNew ? 0 : s.pct - cur;
            const deltaCls = isNew ? 'delta-new' : (delta > 0.05 ? 'delta-up' : delta < -0.05 ? 'delta-dn' : '');
            const deltaTxt = isNew ? 'new' : (Math.abs(delta) < 0.05 ? '—' : `${delta > 0 ? '+' : ''}${delta.toFixed(1)}`);
            return (
              <tr key={s.id}>
                <td>{SLEEVE_LABELS[s.id]}</td>
                <td className="right">{cur !== undefined ? `${cur.toFixed(1)}%` : '—'}</td>
                <td className="right"><b>{s.pct.toFixed(1)}%</b></td>
                <td className={`right ${deltaCls}`}>{deltaTxt}</td>
              </tr>
            );
          })}
        </tbody>
      </table>

      <h2 className="dh">Why these moves</h2>
      <p>
        <b>Trim US equity</b> from 42.0% → 34.0% to address the IPS-relative overweight and reduce drawdown exposure during the tuition draw window.
      </p>
      <p>
        <b>Carve a 9% reserve sleeve</b> sized to the MBA need (S$300k SGD-equivalent) so the funding does not require equity liquidation in adverse markets<span className="pin" title="Liquidity card · 11:36">[5]</span>.
      </p>
      <p>
        <b>Bring USD exposure inside a 30% ceiling</b><span className="pin" title="Currency card · 11:38">[6]</span> — closer to the family's natural 70/30 SGD/USD income mix and lower concentration risk.
      </p>

      <h2 className="dh">Stress test (simulated)</h2>
      <p style={{fontFamily:'var(--sans)', fontSize: 13, color:'var(--ink-2)'}}>
        Projected yield <b>{kpis.yield}</b> · Income ~<b>{kpis.income}</b> · Max simulated drawdown <b>{kpis.drawdown}</b> over a 12-month horizon (vs −22% on the current allocation).
      </p>
    </div>
  );
}

Object.assign(window, {
  Donut, AllocationLegend, KpiGrid, PlanTree,
  GenCard, RiskSlider, LiquidityWidget, CurrencyWidget,
  DiscoveryView, AnswersTrail, ThinkingCard, ProposalDoc,
});
