/* global React */
/* Demand Planning — portfolio dashboard across customers
   ------------------------------------------------------
   Aggregates demand, lanes, and margin signals across all customers so
   leadership can see one view. Mirrors the customer-deep-dive plots from
   the Demand Profiling page (lane-wise demand, demand trend, margin
   trend), but every action card lists the major customers it touches.
*/

/* ============================================================
   Roll up customer metrics into a portfolio shape that re-uses
   the existing chart components (DemandProfileChart,
   LaneOpportunityChart, MarginDrillDown).
   ============================================================ */
function usePortfolioMetrics() {
  return React.useMemo(() => {
    const customers = window.CUSTOMER_HEALTH;
    const allMetrics = customers.map(c => ({ c, m: window.deriveCustomerMetrics(c) }));

    const months = allMetrics[0].m.months;
    const forecastMonths = allMetrics[0].m.forecastMonths;
    const totalRevenue = customers.reduce((s, c) => s + c.volume, 0);

    /* ----- 1) Demand trend roll-up (ocean + air segments) ----- */
    const sumSeg = (segKey) => {
      const sumArr = (key) => Array.from({ length: allMetrics[0].m[segKey][key].length }, (_, i) =>
        allMetrics.reduce((s, { m }) => s + m[segKey][key][i], 0)
      );
      return {
        totalSeries:    sumArr('totalSeries'),
        enquiredSeries: sumArr('enquiredSeries'),
        wonSeries:      sumArr('wonSeries'),
        forecastSeries: sumArr('forecastSeries'),
        forecastBand:   forecastMonths.map((_, i) => ({
          expected: allMetrics.reduce((s, { m }) => s + m[segKey].forecastBand[i].expected, 0),
          low:      allMetrics.reduce((s, { m }) => s + m[segKey].forecastBand[i].low, 0),
          high:     allMetrics.reduce((s, { m }) => s + m[segKey].forecastBand[i].high, 0),
        })),
        wonPerMonth:      allMetrics.reduce((s, { m }) => s + m[segKey].wonPerMonth, 0),
        enquiredPerMonth: allMetrics.reduce((s, { m }) => s + m[segKey].enquiredPerMonth, 0),
        totalPotential:   allMetrics.reduce((s, { m }) => s + m[segKey].totalPotential, 0),
        frac: 1,
      };
    };
    const ocean = { ...sumSeg('ocean'), unit: 'TEU/mo',    unitShort: 'TEU', mode: 'Ocean' };
    const air   = { ...sumSeg('air'),   unit: 'tonnes/mo', unitShort: 't',   mode: 'Air' };

    /* ----- 2) Lane roll-up — keyed by origin|dest|mode ----- */
    const laneMap = new Map();
    for (const { c, m } of allMetrics) {
      for (const lp of m.lanesPerformance) {
        const key = `${lp.origin}|${lp.dest}|${lp.mode}`;
        const entry = laneMap.get(key) || {
          origin: lp.origin, dest: lp.dest, mode: lp.mode,
          laneDemand: 0, received: 0, converted: 0, gap: 0, lostInEnquiry: 0,
          markupSum: 0, weightSum: 0,
          customers: [],
        };
        entry.laneDemand    += lp.laneDemand;
        entry.received      += lp.received;
        entry.converted     += lp.converted;
        entry.gap           += lp.gap;
        entry.lostInEnquiry += lp.lostInEnquiry;
        entry.markupSum     += lp.avgMarkup * Math.max(1, lp.received);
        entry.weightSum     += Math.max(1, lp.received);
        entry.customers.push({
          name: c.name, tier: c.tier,
          laneDemand: lp.laneDemand, converted: lp.converted, gap: lp.gap,
          conversionPct: lp.conversionPct, markup: lp.avgMarkup, received: lp.received,
        });
        laneMap.set(key, entry);
      }
    }
    const lanes = [...laneMap.values()].map(e => ({
      ...e,
      avgMarkup: e.weightSum > 0 ? +(e.markupSum / e.weightSum).toFixed(1) : 0,
      conversionPct: e.received > 0 ? Math.round((e.converted / e.received) * 100) : 0,
    })).sort((a, b) => b.gap - a.gap);
    const topLanes = lanes.slice(0, 8);

    /* ----- 3) Margin history roll-up (revenue-weighted) ----- */
    const marginHistory = months.map((monthName, i) => {
      let qSum = 0, aSum = 0, w = 0;
      for (const { c } of allMetrics) {
        const p = c.marginHistory[i]; if (!p) continue;
        qSum += p.quoted * c.volume;
        aSum += p.accepted * c.volume;
        w    += c.volume;
      }
      return { month: monthName, quoted: +(qSum / w).toFixed(2), accepted: +(aSum / w).toFixed(2) };
    });
    const convTrend = months.map((_, i) => {
      let s = 0, w = 0;
      for (const { c } of allMetrics) { s += c.convTrend[i] * c.volume; w += c.volume; }
      return Math.round(s / w);
    });
    const quotedAvg   = +(marginHistory.reduce((s, h) => s + h.quoted, 0) / marginHistory.length).toFixed(2);
    const acceptedAvg = +(marginHistory.reduce((s, h) => s + h.accepted, 0) / marginHistory.length).toFixed(2);
    const accVals = marginHistory.map(h => h.accepted);
    const marginVariance = +(Math.max(...accVals) - Math.min(...accVals)).toFixed(1);
    const convCurrent = convTrend[convTrend.length - 1];
    const convPrev6 = convTrend.slice(0, 6).reduce((a, b) => a + b, 0) / 6;
    const convNow3  = convTrend.slice(-3).reduce((a, b) => a + b, 0) / 3;
    const convDelta = Math.round(convNow3 - convPrev6);

    /* Portfolio synthesized 'c' & 'm' shapes for the reused chart components */
    const portfolioC = {
      name: 'Portfolio', industry: 'Mixed', am: '—',
      markup: acceptedAvg, marginVariance,
      convCurrent, convDelta,
      quotedAvg, acceptedAvg,
      marginHistory, convTrend,
      demandSlope: 0.5,
    };
    const portfolioM = {
      months, forecastMonths,
      ocean, air, oceanFrac: 1, airFrac: 1,
      lanesPerformance: topLanes,
      wonPerMonth: ocean.wonPerMonth + Math.round(air.wonPerMonth / 9),
      monthlyRevenue: Math.round(totalRevenue / 12),
      totalPotential: ocean.totalPotential + Math.round(air.totalPotential / 9),
      enquiredPerMonth: ocean.enquiredPerMonth + Math.round(air.enquiredPerMonth / 9),
      winRate: convCurrent,
      totalSeries: ocean.totalSeries.map((v, i) => v + Math.round(air.totalSeries[i] / 9)),
      enquiredSeries: ocean.enquiredSeries.map((v, i) => v + Math.round(air.enquiredSeries[i] / 9)),
      wonSeries: ocean.wonSeries.map((v, i) => v + Math.round(air.wonSeries[i] / 9)),
      forecastSeries: ocean.forecastSeries.map((v, i) => v + Math.round(air.forecastSeries[i] / 9)),
      forecastBand: ocean.forecastBand.map((b, i) => ({
        expected: b.expected + Math.round(air.forecastBand[i].expected / 9),
        low:      b.low      + Math.round(air.forecastBand[i].low / 9),
        high:     b.high     + Math.round(air.forecastBand[i].high / 9),
      })),
    };

    return {
      customers, allMetrics,
      months, forecastMonths,
      ocean, air,
      lanes, topLanes,
      marginHistory, convTrend, quotedAvg, acceptedAvg, marginVariance,
      convCurrent, convDelta,
      totalRevenue,
      portfolioC, portfolioM,
    };
  }, []);
}

/* ============================================================
   Derive portfolio-level action cards for each section.
   Each action lists the "major customers impacted" beneath it.
   ============================================================ */
function deriveLaneActions(p) {
  const out = [];
  /* 1) Biggest untapped lane — upsell */
  const topGap = p.lanes[0];
  if (topGap && topGap.gap > 0) {
    const movers = [...topGap.customers].sort((a, b) => b.gap - a.gap).slice(0, 4);
    out.push({
      icon: 'mail-out', tone: 'agent',
      title: `Upsell ${topGap.origin}→${topGap.dest} (${topGap.mode === 'air' ? 'Air' : 'Ocean'}) — ${topGap.gap} ${topGap.mode === 'air' ? 't' : 'TEU'}/mo untapped`,
      reason: `Across the portfolio, customers ship ~${topGap.laneDemand} ${topGap.mode === 'air' ? 't' : 'TEU'}/mo on this lane but only enquire us for ${topGap.received}. Pitch a volume commitment at ${(topGap.avgMarkup - 0.6).toFixed(1)}% markup in exchange for first-look on the remaining ${topGap.gap}.`,
      cta: 'Draft outreach',
      impactedCustomers: movers.map(m => ({ name: m.name, detail: `+${m.gap} ${topGap.mode === 'air' ? 't' : 'TEU'}/mo gap` })),
    });
  }
  /* 2) Worst-converting heavily-enquired lane */
  const worst = p.lanes.filter(l => l.received >= 8).sort((a, b) => a.conversionPct - b.conversionPct)[0];
  if (worst && worst.conversionPct < 55) {
    const newMarkup = Math.max(3.5, worst.avgMarkup - 1.5);
    const priceDrop = +(((worst.avgMarkup - newMarkup) / (100 + worst.avgMarkup)) * 100).toFixed(1);
    const liftPp = Math.round((worst.avgMarkup - newMarkup) * 8);
    const movers = [...worst.customers].filter(c => c.received >= 2 && c.conversionPct < 60)
      .sort((a, b) => a.conversionPct - b.conversionPct).slice(0, 4);
    out.push({
      icon: 'trend-down', tone: 'brand',
      title: `Better rates on ${worst.origin}→${worst.dest} (${worst.mode === 'air' ? 'Air' : 'Ocean'}) — ${priceDrop}% off lifts win rate +${liftPp}pp`,
      reason: `Lane win rate sits at ${worst.conversionPct}% on ${worst.received} enquiries. Drop markup ${worst.avgMarkup}% → ${newMarkup.toFixed(1)}% to recover ~${Math.round(worst.received * liftPp / 100)} ${worst.mode === 'air' ? 't' : 'TEU'}/mo of demand we're already being asked for.`,
      cta: 'Apply new rate',
      impactedCustomers: movers.map(m => ({ name: m.name, detail: `${m.conversionPct}% win · ${m.markup}% markup` })),
    });
  }
  /* 3) Lane concentration / diversification */
  const surgingLanes = p.lanes.filter(l => l.gap > l.received * 0.8 && l.laneDemand > 12).slice(0, 1);
  if (surgingLanes.length) {
    const s = surgingLanes[0];
    const movers = [...s.customers].sort((a, b) => b.laneDemand - a.laneDemand).slice(0, 4);
    out.push({
      icon: 'route', tone: 'brand',
      title: `Capacity check — ${s.origin}→${s.dest} demand outpacing our reach`,
      reason: `${s.laneDemand} ${s.mode === 'air' ? 't' : 'TEU'}/mo of total demand on this lane, only ${s.received} reaching us. Pre-block carrier capacity and brief AMs before peak.`,
      cta: 'Plan capacity',
      impactedCustomers: movers.map(m => ({ name: m.name, detail: `${m.laneDemand} ${s.mode === 'air' ? 't' : 'TEU'}/mo demand` })),
    });
  }
  return out;
}

function derivePortfolioDemandActions(p, segment) {
  const seg = segment === 'air' ? p.air : p.ocean;
  const last3 = seg.totalSeries.slice(-3).reduce((a, b) => a + b, 0) / 3;
  const prev3 = seg.totalSeries.slice(-6, -3).reduce((a, b) => a + b, 0) / 3;
  const recentDeltaPct = Math.round(((last3 - prev3) / prev3) * 100);
  const fcEnd = seg.forecastSeries[seg.forecastSeries.length - 1];
  const fcStart = seg.totalSeries[seg.totalSeries.length - 1];
  const fcDeltaPct = Math.round(((fcEnd - fcStart) / fcStart) * 100);
  const unit = seg.unitShort;
  const out = [];

  /* 1) Confirm surging demand */
  const surging = p.customers
    .map(c => ({ c, ratio: c.demandDeltaPct }))
    .filter(x => x.ratio >= 30)
    .sort((a, b) => b.ratio - a.ratio)
    .slice(0, 4);
  out.push({
    icon: 'mail-out', tone: 'agent',
    title: fcDeltaPct >= 5
      ? `Confirm upcoming ${segment === 'air' ? 'air' : 'ocean'} demand spike — +${fcDeltaPct}% over 3 months`
      : `Validate next-quarter ${segment === 'air' ? 'air' : 'ocean'} forecast with top accounts`,
    reason: `Portfolio forecast hits ~${fcEnd} ${unit}/mo by month +3 (band ${seg.forecastBand[2].low}–${seg.forecastBand[2].high}). ${surging.length} customers are surging materially — confirm with each AM before pre-blocking capacity.`,
    cta: 'Send AM brief',
    impactedCustomers: surging.length
      ? surging.map(s => ({ name: s.c.name, detail: `+${s.ratio}% vs prior 6m` }))
      : [{ name: 'All Enterprise accounts', detail: 'standard outreach' }],
  });

  /* 2) Investigate softening */
  const dropping = p.customers
    .map(c => ({ c, ratio: c.demandDeltaPct, convDelta: c.convDelta }))
    .filter(x => x.ratio <= -15 || x.convDelta <= -5)
    .sort((a, b) => a.ratio - b.ratio)
    .slice(0, 4);
  if (dropping.length) {
    out.push({
      icon: 'phone', tone: 'warning',
      title: `Investigate softening — ${dropping.length} accounts below trend`,
      reason: `Recent 3-month average is ${recentDeltaPct >= 0 ? '+' : ''}${recentDeltaPct}% vs prior. Schedule discovery calls with the softest accounts to diagnose competitor share-loss vs internal volume changes before the next renewal cycle.`,
      cta: 'Schedule calls',
      impactedCustomers: dropping.map(d => ({
        name: d.c.name,
        detail: `${d.ratio}% demand · ${d.convDelta > 0 ? '+' : ''}${d.convDelta}pp win`,
      })),
    });
  }

  /* 3) Tailwinds & headwinds context */
  const industries = {};
  p.customers.forEach(c => { industries[c.industry] = (industries[c.industry] || 0) + c.volume; });
  const topInd = Object.entries(industries).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([n]) => n);
  const driftCustomers = p.customers
    .filter(c => c.demandDeltaPct >= 25 || c.demandDeltaPct <= -25)
    .sort((a, b) => Math.abs(b.demandDeltaPct) - Math.abs(a.demandDeltaPct))
    .slice(0, 4);
  out.push({
    icon: 'info', tone: 'brand',
    title: `Tailwinds & headwinds across the portfolio`,
    reason: `Top industries by revenue: ${topInd.join(', ')}. ${segment === 'air'
      ? 'Air freight uplift from inventory-rebuild + electronics restock; cooling on apparel.'
      : 'Ocean lanes seeing Red-Sea-reroute spillover; Trans-Pac glut compresses margin.'} Forecast band widens to ±${Math.round((seg.forecastBand[2].high - seg.forecastBand[2].low) / 2)} ${unit} by month +3.`,
    cta: 'View context',
    impactedCustomers: driftCustomers.map(c => ({
      name: c.name,
      detail: `${c.industry} · ${c.demandDeltaPct > 0 ? '+' : ''}${c.demandDeltaPct}%`,
    })),
  });

  return out;
}

function derivePortfolioMarginActions(p) {
  const out = [];
  const PORTFOLIO_AVG = p.acceptedAvg;

  /* 1) Increase markup on compressed-but-winning customers */
  const increaseTargets = p.customers
    .filter(c => c.markup < PORTFOLIO_AVG - 0.5 && c.convCurrent > 35 && c.convDelta >= -1)
    .sort((a, b) => (b.convCurrent - b.markup) - (a.convCurrent - a.markup))
    .slice(0, 4);
  if (increaseTargets.length) {
    out.push({
      icon: 'trend-up', tone: 'agent',
      title: `Test +1pp markup on ${increaseTargets.length} compressed-but-winning accounts`,
      reason: `These accounts win at margins below portfolio avg (${PORTFOLIO_AVG.toFixed(1)}%) — strong conversion suggests headroom. Pilot +1pp on the next 5 RFQs each, monitor win rate at 14-day checkpoint.`,
      cta: 'Apply uplift',
      impactedCustomers: increaseTargets.map(c => ({
        name: c.name,
        detail: `${c.markup}% · ${c.convCurrent}% win`,
      })),
    });
  }

  /* 2) Reduce markup where conversion is softening */
  const reduceTargets = p.customers
    .filter(c => c.markup > PORTFOLIO_AVG + 0.5 && c.convDelta < -2)
    .sort((a, b) => a.convDelta - b.convDelta)
    .slice(0, 4);
  if (reduceTargets.length) {
    out.push({
      icon: 'trend-down', tone: 'warning',
      title: `Reduce markup on ${reduceTargets.length} accounts losing share`,
      reason: `Markup is above portfolio avg (${PORTFOLIO_AVG.toFixed(1)}%) and win rate is dropping. Test −1pp to recover share before competitors lock in the next contract cycle.`,
      cta: 'Apply reduction',
      impactedCustomers: reduceTargets.map(c => ({
        name: c.name,
        detail: `${c.markup}% · ${c.convDelta}pp win shift`,
      })),
    });
  }

  /* 3) Tighten variance — playbook update */
  const highVar = p.customers
    .filter(c => c.marginVariance > 1.6)
    .sort((a, b) => b.marginVariance - a.marginVariance)
    .slice(0, 4);
  out.push({
    icon: 'gauge', tone: 'brand',
    title: `Tighten quoting variance — portfolio swings ±${(p.marginVariance / 2).toFixed(1)}pp`,
    reason: `Accepted markup across the portfolio drifts by ${p.marginVariance}pp over 12 months. Sweet spot is ±0.6pp — refresh the rate playbook with current floor / ceiling per tier and stop quoting outside the band.`,
    cta: 'Update playbook',
    impactedCustomers: highVar.length
      ? highVar.map(c => ({ name: c.name, detail: `±${c.marginVariance}pp variance` }))
      : [{ name: 'Sales team', detail: 'playbook owners' }],
  });

  return out;
}

/* ============================================================
   Action card with impacted-customers footer
   ============================================================ */
const ACTION_PALETTES = {
  agent:   { bg: 'var(--agent-50)',   border: 'var(--agent-200)',   color: 'var(--agent-700)',   btn: 'btn-agent' },
  brand:   { bg: 'var(--brand-50)',   border: 'var(--brand-200)',   color: 'var(--brand-700)',   btn: 'btn-primary' },
  warning: { bg: 'var(--warning-50)', border: 'var(--warning-100)', color: 'var(--warning-700)', btn: 'btn-secondary' },
  success: { bg: 'var(--success-50)', border: 'var(--success-100)', color: 'var(--success-700)', btn: 'btn-secondary' },
};

function PortfolioActionCard({ icon, tone, title, reason, cta, impactedCustomers }) {
  const p = ACTION_PALETTES[tone] || ACTION_PALETTES.brand;
  return (
    <div style={{
      background: p.bg, border: `1px solid ${p.border}`, borderRadius: 12, overflow: 'hidden',
    }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '14px 16px 12px' }}>
        <div style={{
          width: 32, height: 32, borderRadius: 8,
          background: 'white', color: p.color, border: `1px solid ${p.border}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
        }}>
          <window.Icon name={icon} size={16}/>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--fg-1)', lineHeight: 1.4 }}>{title}</div>
          <div style={{ fontSize: 12, color: 'var(--fg-2)', lineHeight: 1.5, marginTop: 4 }}>{reason}</div>
        </div>
        <button className={`btn btn-sm ${p.btn}`} style={{ flexShrink: 0 }}>{cta}</button>
      </div>
      {impactedCustomers && impactedCustomers.length > 0 && (
        <div style={{
          borderTop: `1px solid ${p.border}`, background: 'white',
          padding: '10px 16px',
          display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap',
        }}>
          <div style={{
            fontSize: 9.5, fontWeight: 700, color: p.color,
            textTransform: 'uppercase', letterSpacing: '0.08em',
            display: 'inline-flex', alignItems: 'center', gap: 4,
          }}>
            <window.Icon name="users" size={11}/>
            Customers impacted
          </div>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {impactedCustomers.map((c, i) => (
              <span key={i} style={{
                display: 'inline-flex', alignItems: 'center', gap: 6,
                padding: '4px 9px', borderRadius: 999,
                background: 'var(--n-25)', border: '1px solid var(--n-100)',
                fontSize: 11, fontWeight: 600, color: 'var(--fg-1)',
              }}>
                {c.name}
                <span style={{ fontSize: 10, fontWeight: 500, color: 'var(--fg-3)' }}>{c.detail}</span>
              </span>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function PortfolioSuggestionStack({ label, items }) {
  if (!items || items.length === 0) return null;
  return (
    <div style={{ marginTop: 12 }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8,
        fontSize: 10.5, fontWeight: 700, color: 'var(--agent-700)',
        textTransform: 'uppercase', letterSpacing: '0.08em',
      }}>
        <window.Icon name="sparkles" size={11} color="#7C5CFF"/>
        <span>Agent suggestions · {label}</span>
        <span style={{ flex: 1, height: 1, background: 'var(--n-100)' }}/>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {items.map((a, i) => <PortfolioActionCard key={i} {...a}/>)}
      </div>
    </div>
  );
}

/* ============================================================
   Screen
   ============================================================ */
window.usePortfolioMetrics = usePortfolioMetrics;

window.DemandPlanningScreen = function DemandPlanningScreen() {
  const p = usePortfolioMetrics();
  const [segment, setSegment] = React.useState('ocean');

  /* Header KPIs */
  const customerCount = p.customers.length;
  const enterpriseCount = p.customers.filter(c => c.tier === 'Enterprise').length;
  const surging = p.customers.filter(c => c.demandDeltaPct >= 30).length;
  const dropping = p.customers.filter(c => c.demandDeltaPct <= -15).length;
  const totalGap = p.lanes.reduce((s, l) => s + l.gap, 0);

  return (
    <div style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 18 }}>
      {/* Header */}
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between' }}>
        <div>
          <div style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em' }}>Demand Planning</div>
          <div style={{ fontSize: 13, color: 'var(--fg-3)', marginTop: 4, display: 'flex', alignItems: 'center', gap: 8 }}>
            <window.Icon name="sparkles" size={12} color="#7C5CFF"/>
            <span>Portfolio-wide view of demand, lanes, and margin — refreshed nightly. Drill into any customer in <strong style={{ color: 'var(--fg-1)' }}>Demand Profiling</strong>.</span>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 6 }}>
          <button className="btn btn-sm btn-secondary">Last quarter</button>
          <button className="btn btn-sm btn-secondary" style={{ background: 'var(--brand-50)', borderColor: 'var(--brand-200)', color: 'var(--brand-700)' }}>This quarter</button>
          <button className="btn btn-sm btn-secondary"><window.Icon name="download" size={12}/> Export</button>
        </div>
      </div>

      {/* Portfolio KPI strip */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 12 }}>
        <div className="kpi">
          <div className="kpi-label">Active customers</div>
          <div className="kpi-value tnum">{customerCount}</div>
          <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>{enterpriseCount} Enterprise</div>
        </div>
        <div className="kpi">
          <div className="kpi-label">Ocean demand · monthly</div>
          <div className="kpi-value tnum">{p.ocean.wonPerMonth.toLocaleString()}</div>
          <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>TEU won of {p.ocean.totalPotential.toLocaleString()} potential</div>
        </div>
        <div className="kpi">
          <div className="kpi-label">Air demand · monthly</div>
          <div className="kpi-value tnum">{p.air.wonPerMonth.toLocaleString()}</div>
          <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>tonnes won of {p.air.totalPotential.toLocaleString()} potential</div>
        </div>
        <div className="kpi">
          <div className="kpi-label">Demand not received</div>
          <div className="kpi-value tnum" style={{ color: 'var(--warning-700)' }}>{totalGap.toLocaleString()}</div>
          <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>Untapped customer demand</div>
        </div>
        <div className="kpi">
          <div className="kpi-label">Accounts to action</div>
          <div className="kpi-value tnum" style={{ color: 'var(--agent-700)', display: 'flex', alignItems: 'center', gap: 6 }}>
            <span className="agent-dot pulse" style={{ background: 'var(--agent-500)' }}/>
            {surging + dropping}
          </div>
          <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>{surging} surging · {dropping} dropping</div>
        </div>
      </div>

      {/* ============================================================
          1. LANE-WISE DEMAND
          ============================================================ */}
      <section>
        <window.SectionTitle
          title="Lane-wise demand · portfolio"
          subtitle="Top lanes across all customers — what's shipped, what reaches us, what we win"
        />
        <window.LaneOpportunityChart m={p.portfolioM}/>
        <PortfolioSuggestionStack label="Lane opportunity" items={deriveLaneActions(p)}/>
      </section>

      {/* ============================================================
          2. DEMAND TREND
          ============================================================ */}
      <section>
        <window.SectionTitle
          title="Demand trend · portfolio"
          subtitle="Aggregate customer demand, enquired and won — with 3-month forecast"
          right={<window.ModeSegmentToggle value={segment} onChange={setSegment} m={{ oceanFrac: 1, airFrac: 1 }}/>}
        />
        <window.DemandProfileChart c={p.portfolioC} m={p.portfolioM} segment={segment}/>
        <PortfolioSuggestionStack label="Demand actions" items={derivePortfolioDemandActions(p, segment)}/>
      </section>

      {/* ============================================================
          3. MARGIN TRENDS
          ============================================================ */}
      <section>
        <window.SectionTitle
          title="Margin trends · portfolio"
          subtitle="Revenue-weighted quoted vs accepted markup, variance band, conversion overlay"
        />
        <window.MarginDrillDown c={p.portfolioC} m={p.portfolioM}/>
        <PortfolioSuggestionStack label="Margin actions" items={derivePortfolioMarginActions(p)}/>
      </section>
    </div>
  );
};
