/* Cascade.jsx — NZ cascade-scenario simulator
   Pick a scenario, set severity → see correlated portfolio impact across sectors.
*/

const SCENARIOS = [
  {
    id: 'hormuz',
    label: 'Hormuz fuel disruption',
    short: 'Hormuz',
    desc: 'Strait closure event. Marine cargo, war-risk, BI at terminal level. The April 2026 case.',
    weights: { transport: 0.95, fuel: 1, agri: 0.55, banks: 0.4, infra: 0.45, marae: 0.6, retail: 0.7 },
  },
  {
    id: 'wlg',
    label: 'Wellington fault rupture',
    short: 'Wellington',
    desc: 'Major rupture, central-North-Island corridor compromised for 6–12 weeks.',
    weights: { transport: 0.85, fuel: 0.5, agri: 0.4, banks: 0.7, infra: 1, marae: 0.85, retail: 0.6 },
  },
  {
    id: 'avf',
    label: 'Auckland Volcanic Field',
    short: 'AVF',
    desc: 'New vent activation in metro Auckland. Insurers know the exposure exists; defensible portfolio modelling is rare.',
    weights: { transport: 1, fuel: 0.8, agri: 0.3, banks: 0.95, infra: 0.95, marae: 0.7, retail: 1 },
  },
  {
    id: 'mpi',
    label: 'MPI biosecurity breach',
    short: 'Biosecurity',
    desc: 'Single MPI event hits dairy, regional bank loan books, and rural insurance lines simultaneously.',
    weights: { transport: 0.3, fuel: 0.15, agri: 1, banks: 0.85, infra: 0.25, marae: 0.45, retail: 0.4 },
  },
  {
    id: 'cyber',
    label: 'Cloud / supply-chain cyber',
    short: 'Cyber',
    desc: 'Single cloud-service compromise hits hundreds of NZ firms simultaneously.',
    weights: { transport: 0.6, fuel: 0.45, agri: 0.4, banks: 0.95, infra: 0.7, marae: 0.3, retail: 0.85 },
  },
  {
    id: 'hb',
    label: 'Hawkes Bay flood',
    short: 'Hawkes Bay',
    desc: 'Major rainfall event, pastoral and horticultural exposure, infrastructure damage.',
    weights: { transport: 0.55, fuel: 0.2, agri: 0.95, banks: 0.65, infra: 0.7, marae: 0.55, retail: 0.4 },
  },
];

const SECTORS = [
  { id: 'fuel',      label: 'Fuel & logistics',         baseExpo: 1.4,  carrier: 'Vero, IAG, Lloyd\'s' },
  { id: 'transport', label: 'Transport & airlines',     baseExpo: 1.2,  carrier: 'IAG, Suncorp' },
  { id: 'agri',      label: 'Dairy & primary',          baseExpo: 2.6,  carrier: 'FMG, NZI' },
  { id: 'banks',     label: 'Regional bank loan books', baseExpo: 4.1,  carrier: 'Munich Re, Hannover Re' },
  { id: 'infra',     label: 'Infrastructure & utilities', baseExpo: 2.2, carrier: 'Vero, Marsh-placed' },
  { id: 'retail',    label: 'Household & SME',          baseExpo: 1.8,  carrier: 'IAG, Tower, AA' },
  { id: 'marae',     label: 'Iwi assets / marae',       baseExpo: 0.9,  carrier: 'self / parametric' },
];

function Cascade() {
  const tw = React.useContext(window.TweakCtx);
  const NS = tw.nodeScale, LS = tw.labelScale;
  const [scen, setScen] = React.useState('hormuz');
  const [severity, setSeverity] = React.useState(0.7);
  const [hasModel, setHasModel] = React.useState(false);

  const cur = SCENARIOS.find(s => s.id === scen);

  // expected loss per sector ($B)
  const losses = SECTORS.map(s => {
    const w = cur.weights[s.id] || 0.3;
    const raw = s.baseExpo * w * severity;
    // a "vendor model" view truncates correlated exposure
    const vendor = raw * 0.55;
    return { ...s, raw, vendor, weight: w };
  });
  const totalRaw = losses.reduce((a,b)=>a+b.raw, 0);
  const totalVendor = losses.reduce((a,b)=>a+b.vendor, 0);
  const visibleLoss = hasModel ? totalRaw : totalVendor;
  const gap = totalRaw - totalVendor;

  // sector bar max
  const maxLoss = Math.max(...losses.map(l => l.raw));

  return (
    <section id="cascade">
      <div className="container">
        <SectionHeader
          num="05 / 06"
          tag="A canonical NZ cascade-scenario library"
          title="The correlations vendor models do not capture."
          lede="Pick a scenario, set severity, and watch the loss surface ripple across sectors. The grey bars are what existing global cat models show. The red overlay is what NZ-specific cascade modelling reveals."
        />

        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 24 }}>
          {SCENARIOS.map(s => (
            <button key={s.id}
                    className={`chip ${scen === s.id ? 'on' : ''}`}
                    onClick={() => setScen(s.id)}>
              <span className="swatch" style={{ background: scen === s.id ? '#fff' : 'var(--red)' }} />
              {s.label}
            </button>
          ))}
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 360px', gap: 32, alignItems: 'start' }}>
          {/* main visualisation */}
          <div className="card flat" style={{ padding: '24px 28px' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 14 }}>
              <div>
                <div style={{ fontSize: 13, fontWeight: 600 }}>{cur.label}</div>
                <div style={{ fontSize: 11.5, color: 'var(--muted)', marginTop: 2 }}>{cur.desc}</div>
              </div>
              <div className="mono" style={{ fontSize: 11, color: 'var(--muted)' }}>
                severity {(severity*100).toFixed(0)}%
              </div>
            </div>

            {/* severity slider */}
            <div style={{ marginBottom: 24 }}>
              <input type="range" min="0.1" max="1" step="0.01" value={severity}
                     onChange={(e) => setSeverity(parseFloat(e.target.value))}
                     className="slider red" />
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10.5, color: 'var(--muted-2)', marginTop: 4 }}>
                <span>tail event (10th pct)</span>
                <span>median</span>
                <span>1-in-100 worst</span>
              </div>
            </div>

            {/* sector bars */}
            <div className="flex-col" style={{ gap: 10 }}>
              {losses.map(l => {
                const wRaw = (l.raw / maxLoss) * 100;
                const wVendor = (l.vendor / maxLoss) * 100;
                return (
                  <div key={l.id}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 }}>
                      <div>
                        <span style={{ fontWeight: 600 }}>{l.label}</span>
                        <span style={{ color: 'var(--muted-2)', marginLeft: 8, fontSize: 11 }}>
                          {l.carrier}
                        </span>
                      </div>
                      <div className="mono">
                        <span style={{ color: 'var(--muted-2)' }}>vendor ${l.vendor.toFixed(2)}B</span>
                        <span style={{ marginLeft: 12, color: 'var(--red)', fontWeight: 600 }}>
                          true ${l.raw.toFixed(2)}B
                        </span>
                      </div>
                    </div>
                    <div style={{ position: 'relative', height: 14, background: 'var(--rule)', borderRadius: 2, overflow: 'hidden' }}>
                      <div style={{ width: `${wVendor}%`, height: '100%', background: '#bdb6a3' }} />
                      {hasModel && (
                        <div style={{
                          position: 'absolute', left: `${wVendor}%`, width: `${wRaw - wVendor}%`,
                          height: '100%', background: 'var(--red)',
                          transition: 'width .25s',
                        }} />
                      )}
                    </div>
                  </div>
                );
              })}
            </div>

            {/* totals */}
            <div style={{
              marginTop: 24, paddingTop: 18, borderTop: '1px solid var(--rule)',
              display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 24,
            }}>
              <div>
                <div style={{ fontSize: 10.5, letterSpacing: '.1em', textTransform: 'uppercase', color: 'var(--muted-2)', fontWeight: 600 }}>
                  Vendor model
                </div>
                <div style={{ fontFamily: 'Source Serif 4', fontSize: 36, color: 'var(--ink-soft)', letterSpacing: '-.02em', lineHeight: 1.05, marginTop: 4 }}>
                  ${totalVendor.toFixed(1)}B
                </div>
              </div>
              <div>
                <div style={{ fontSize: 10.5, letterSpacing: '.1em', textTransform: 'uppercase', color: 'var(--red)', fontWeight: 600 }}>
                  NZ cascade model
                </div>
                <div style={{ fontFamily: 'Source Serif 4', fontSize: 36, color: 'var(--red)', letterSpacing: '-.02em', lineHeight: 1.05, marginTop: 4 }}>
                  ${totalRaw.toFixed(1)}B
                </div>
              </div>
              <div>
                <div style={{ fontSize: 10.5, letterSpacing: '.1em', textTransform: 'uppercase', color: 'var(--muted-2)', fontWeight: 600 }}>
                  Hidden cascade
                </div>
                <div style={{ fontFamily: 'Source Serif 4', fontSize: 36, color: 'var(--ink)', letterSpacing: '-.02em', lineHeight: 1.05, marginTop: 4 }}>
                  +${gap.toFixed(1)}B
                </div>
              </div>
            </div>

            <div style={{ marginTop: 18 }}>
              <Toggle on={hasModel} onChange={setHasModel}
                      label={hasModel ? 'NZ cascade overlay: ON' : 'NZ cascade overlay: OFF (vendor view only)'} />
            </div>
          </div>

          {/* sidebar — correlation diagram */}
          <div className="flex-col">
            <div className="card flat card-pad-sm">
              <div style={{ fontSize: 11, letterSpacing: '.16em', textTransform: 'uppercase', color: 'var(--muted-2)', fontWeight: 600, marginBottom: 10 }}>
                Correlation surface
              </div>
              <CorrelationGraph weights={cur.weights} severity={severity} />
              <div style={{ fontSize: 11.5, color: 'var(--muted)', lineHeight: 1.5, marginTop: 10 }}>
                Edge thickness = correlated exposure between sectors under {cur.short}.
                Click a scenario to see the topology shift.
              </div>
            </div>

            <blockquote className="pull" style={{ fontSize: 18, margin: '4px 0 0' }}>
              Defensible IP, recurring license revenue, and a pathway to regulatory recognition with XRB and RBNZ.
            </blockquote>
          </div>
        </div>
      </div>
    </section>
  );
}

function CorrelationGraph({ weights, severity }) {
  const tw = React.useContext(window.TweakCtx);
  const NS = tw.nodeScale, LS = tw.labelScale, LW = tw.lineWeight;
  // arrange nodes on a circle
  const W = 320, H = 240, cx = W/2, cy = H/2, r = 90;
  const nodes = SECTORS.map((s, i) => {
    const a = (i / SECTORS.length) * Math.PI * 2 - Math.PI/2;
    return { ...s, x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r, w: weights[s.id] || 0.3 };
  });
  // edges between sectors weighted by product of weights
  const edges = [];
  for (let i = 0; i < nodes.length; i++) {
    for (let j = i+1; j < nodes.length; j++) {
      const w = nodes[i].w * nodes[j].w * severity;
      if (w > 0.15) edges.push({ a: nodes[i], b: nodes[j], w });
    }
  }

  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: 'auto' }}>
      {edges.map((e, i) => (
        <line key={i} x1={e.a.x} y1={e.a.y} x2={e.b.x} y2={e.b.y}
              stroke="var(--red)" strokeOpacity={Math.min(0.5, e.w*0.6)}
              strokeWidth={Math.max(0.5, e.w * 4) * LW} />
      ))}
      {nodes.map(n => (
        <g key={n.id}>
          <circle cx={n.x} cy={n.y} r={(6 + n.w * 8) * NS} fill="var(--bg-card)" stroke="var(--ink)" strokeWidth="1.25" />
          <text x={n.x} y={n.y + 4} fontSize={9*LS} textAnchor="middle" fontWeight="600">
            {n.id.charAt(0).toUpperCase()}
          </text>
        </g>
      ))}
    </svg>
  );
}

window.Cascade = Cascade;
