// ====================================================================
// BUDGET, SMART BUDGET MODE (formerly "Simple")
// Franchisee sets ONE total monthly budget; LOCALACT controls the
// per-service mix and rebalances it monthly based on performance,
// competition, testing, impression share, and seasonality.
// Every move is logged and explained, no black boxes.
// ====================================================================
const { useState: useStateBS, useEffect: useEffectBS, useMemo: useMemoBS, useRef: useRefBS } = React;

// Per-channel meta the smart mode needs:
//   ratio , playbook target share of paid budget (sum to 1.0)
//   floor , playbook minimum monthly spend ($)
//   cpl   , local CPL signal (lower = under-CPL = gets boost)
//   target, peer median spend, used for "at-target" callouts
const PLAYBOOK_META = {
  'gs-local':    { ratio: 0.50, floor: 800,  cpl: 28, target: 1500, label: 'Local Google Search', what: 'High-intent searches: "house cleaning near me", "deep clean Austin", etc.' },
  'lsa-local':   { ratio: 0.20, floor: 400,  cpl: 15, target: 600,  label: 'Local Services Ads',  what: "Pay-per-lead, Google's verified call-only placements." },
  'meta-local':  { ratio: 0.20, floor: 350,  cpl: 42, target: 800,  label: 'Local Meta / IG Ads', what: 'Awareness + retargeting on Meta + Instagram.' },
  'meta-mothers':{ ratio: 0.10, floor: 250,  cpl: 38, target: 350,  label: 'Promo · Meta',        what: 'Limited-time campaigns (Fall Refresh, holidays, etc.)' },
};

// Brand-wide CPL benchmark, channels under this are "winning",
// over this are "losing". Used to tune ratios per location.
const BRAND_CPL_BENCHMARK = { 'gs-local': 34, 'lsa-local': 18, 'meta-local': 45, 'meta-mothers': 42 };

// ----------------------------------------------------------------
// REBALANCE REASONING, the five factors that drive monthly moves
// ----------------------------------------------------------------
const REASON_META = {
  performance:        { label: 'Performance',      color: 'var(--sage)' },
  competition:        { label: 'Competition',      color: 'var(--terra)' },
  testing:            { label: 'Testing',          color: 'var(--violet)' },
  'impression-share': { label: 'Impression share', color: '#5B8DB8' },
  seasonality:        { label: 'Seasonality',      color: '#917200' },
};

// This month's rebalance, per-channel net delta + reasoning.
const CHANNEL_MOVE_DETAIL = {
  'gs-local': {
    delta: +150, headline: 'Winning more than it could spend', reasons: ['impression-share', 'performance', 'testing'],
    detail: 'Search lost 21% of eligible impressions to budget caps in March, while converting at $28 CPL, $6 under the brand benchmark. $75 of the increase is held in a broad-match keyword test through Apr 21; if CPL holds under $32 it graduates into the core campaign.',
  },
  'lsa-local': {
    delta: +100, headline: 'Cheapest lead, new competition', reasons: ['performance', 'competition'],
    detail: '$15 CPL is your lowest-cost lead. Two competitors joined the local LSA auction in March, the added budget protects your top-3 call slot.',
  },
  'meta-local': {
    delta: -200, headline: 'CPL trending up, audience saturated', reasons: ['performance', 'competition'],
    detail: 'Meta CPL rose 18% over the last 60 days as auction pressure increased and your retargeting pool saturated. Budget shifted to channels converting under benchmark, it restores automatically if CPL recovers.',
  },
  'meta-mothers': {
    delta: -50, headline: 'Promo demand tapering', reasons: ['seasonality'],
    detail: 'Fall Refresh demand tapers through April. Freed spend backs verified-call leads instead; promo budget scales back up ahead of the next campaign window.',
  },
};

// This month's moves, source → destination, for the Lenny panel.
const REBALANCE = {
  month: 'April 2026',
  date: 'Apr 1, 2026 · 6:04 AM',
  impact: '+9 to 12 leads/mo vs. keeping the March split',
  moves: [
    { amount: 150, from: 'meta-local', to: 'gs-local', reasons: ['impression-share', 'performance'],
      why: 'Search lost 21% of impressions to budget while running $6 under CPL benchmark; Meta CPL trending 18% up.' },
    { amount: 50, from: 'meta-local', to: 'lsa-local', reasons: ['performance', 'competition'],
      why: 'LSA is your cheapest lead at $15 and two new competitors entered the auction, budget defends your top-3 call slot.' },
    { amount: 50, from: 'meta-mothers', to: 'lsa-local', reasons: ['seasonality'],
      why: 'Fall Refresh demand tapers in April; freed promo spend backs verified-call leads.' },
  ],
  test: 'Holding $75 of Search in a broad-match keyword test through Apr 21. If CPL stays under $32, it graduates into the core campaign.',
};

// Monthly changelog, every past rebalance, logged.
const REBALANCE_HISTORY = [
  { month: 'April 2026', date: 'Apr 1', summary: '3 moves · $250 shifted · 1 test started', entries: [
    { txt: 'Moved $150 from Local Meta / IG → Local Google Search', reasons: ['impression-share', 'performance'] },
    { txt: 'Moved $50 from Local Meta / IG → Local Services Ads', reasons: ['performance', 'competition'] },
    { txt: 'Moved $50 from Promo · Meta → Local Services Ads', reasons: ['seasonality'] },
    { txt: 'Started broad-match keyword test, $75 held in Search through Apr 21', reasons: ['testing'] },
  ] },
  { month: 'March 2026', date: 'Mar 1', summary: '2 moves · $175 shifted', entries: [
    { txt: 'Moved $125 from Promo · Meta → Meta retargeting as holiday promo wound down', reasons: ['seasonality', 'performance'] },
    { txt: 'Raised LSA weekly cap $50, a competitor doubled bids in your zip cluster', reasons: ['competition'] },
  ] },
  { month: 'February 2026', date: 'Feb 1', summary: '1 move · $100 shifted', entries: [
    { txt: 'Moved $100 from Search → LSA after a Search CPL spike ($39 vs. $34 benchmark)', reasons: ['performance'] },
  ] },
];

// ----------------------------------------------------------------
// CORE ALLOCATION FUNCTION
// Takes a total budget + the enrolled initiatives + their CPLs,
// returns { allocations: { key: $ }, deficit, locked }.
//   1. Pay all floors first.
//   2. Distribute remainder by ratio, BUT shift up-to-30% from
//      losing channels (high CPL) to winning channels (low CPL).
//   3. If total < sum of floors, return deficit and just allocate
//      proportionally what's available.
// ----------------------------------------------------------------
function autoAllocate(total, initiatives) {
  const inits = initiatives.map(s => {
    const meta = PLAYBOOK_META[s.key] || { ratio: 0.25, floor: 200, cpl: 30, target: 500 };
    const localCpl = s.cpl || meta.cpl;
    const benchCpl = BRAND_CPL_BENCHMARK[s.key] || meta.cpl;
    // Performance multiplier: <1 means worse than brand (penalize), >1 means better (reward)
    // Capped at +/-30% to keep playbook intact.
    const perf = Math.max(0.7, Math.min(1.3, benchCpl / localCpl));
    return { key: s.key, name: s.name || meta.label, color: s.color, ratio: meta.ratio, floor: meta.floor, target: meta.target, perf, cpl: localCpl };
  });

  const totalFloor = inits.reduce((s, i) => s + i.floor, 0);

  // CASE A, budget below floor: prorate floors down
  if (total < totalFloor) {
    const allocations = {};
    inits.forEach(i => {
      allocations[i.key] = Math.round(total * (i.floor / totalFloor) / 5) * 5;
    });
    return { allocations, deficit: totalFloor - total, totalFloor, perfAdjusted: false };
  }

  // CASE B, budget covers floors: distribute remainder
  const remainder = total - totalFloor;

  // Performance-weighted ratios (still sum to 1.0)
  const weightedTotal = inits.reduce((s, i) => s + i.ratio * i.perf, 0);
  const allocations = {};
  inits.forEach(i => {
    const share = (i.ratio * i.perf) / weightedTotal;
    const extra = Math.round((remainder * share) / 25) * 25;
    allocations[i.key] = i.floor + extra;
  });

  // Round to nearest $25, fix any rounding drift
  const sum = Object.values(allocations).reduce((s, v) => s + v, 0);
  const drift = total - sum;
  if (drift !== 0) {
    // Apply drift to the largest allocation
    const biggest = Object.entries(allocations).sort((a, b) => b[1] - a[1])[0][0];
    allocations[biggest] += drift;
  }

  return { allocations, deficit: 0, totalFloor, perfAdjusted: true };
}

// ----------------------------------------------------------------
// REASON CHIPS, tiny factor tags
// ----------------------------------------------------------------
function ReasonChips({ reasons }) {
  if (!reasons || reasons.length === 0) return null;
  return (
    <span style={{ display: 'inline-flex', gap: 5, flexWrap: 'wrap' }}>
      {reasons.map(r => {
        const m = REASON_META[r];
        if (!m) return null;
        return (
          <span key={r} style={{ fontSize: 9, fontWeight: 700, letterSpacing: '0.05em', textTransform: 'uppercase', padding: '2px 7px', borderRadius: 999, border: `1px solid ${m.color}`, color: m.color, whiteSpace: 'nowrap' }}>{m.label}</span>
        );
      })}
    </span>
  );
}

// ----------------------------------------------------------------
// CIRCULAR INPUT, the big budget number with steppers
// ----------------------------------------------------------------
function BigBudgetInput({ value, onChange }) {
  const step = 100;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 16, justifyContent: 'center' }}>
      <button onClick={() => onChange(Math.max(0, value - step))} aria-label="decrease"
        style={{ width: 44, height: 44, borderRadius: '50%', border: '1.5px solid var(--cream-3)', background: 'var(--paper)', fontSize: 22, color: 'var(--ink-3)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>−</button>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, fontFamily: 'var(--ff-display)' }}>
        <span style={{ fontSize: 32, color: 'var(--ink-3)', fontWeight: 400 }}>$</span>
        <input type="number" value={value} onChange={e => onChange(Math.max(0, parseInt(e.target.value || '0', 10)))} min={0} step={50}
          style={{ fontSize: 64, fontWeight: 500, color: 'var(--ink)', letterSpacing: '-0.04em', lineHeight: 1, border: 'none', background: 'transparent', textAlign: 'center', width: Math.max(180, String(value).length * 38), outline: 'none', fontFamily: 'var(--ff-display)' }} />
        <span style={{ fontSize: 14, color: 'var(--ink-3)', fontFamily: 'var(--ff-body)' }}>/mo</span>
      </div>
      <button onClick={() => onChange(value + step)} aria-label="increase"
        style={{ width: 44, height: 44, borderRadius: '50%', border: '1.5px solid var(--cream-3)', background: 'var(--paper)', fontSize: 22, color: 'var(--ink-3)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>+</button>
    </div>
  );
}

// ----------------------------------------------------------------
// PER-CHANNEL MIX ROW (read-only, the platform owns the mix)
// Shows the allocation, floor/peer markers, and a "why it changed
// this month" chip that expands into the reasoning.
// ----------------------------------------------------------------
function ChannelAllocRow({ init, value, share }) {
  const meta = PLAYBOOK_META[init.key] || {};
  const move = CHANNEL_MOVE_DETAIL[init.key];
  const [openWhy, setOpenWhy] = useStateBS(false);
  const cplDelta = init.cpl - (BRAND_CPL_BENCHMARK[init.key] || init.cpl);
  const winning = cplDelta < -2;
  const losing = cplDelta > 2;
  const min = meta.floor ? Math.round(meta.floor * 0.5) : 100;
  const max = Math.max((meta.target || 500) * 2.5, value * 1.5, 2000);
  const pct = (v) => Math.max(0, Math.min(100, (v - min) / (max - min) * 100));

  return (
    <div style={{ borderBottom: '1px solid var(--cream-2)' }}>
      <div style={{ padding: '14px 18px', display: 'grid', gridTemplateColumns: '220px 1fr 92px', gap: 18, alignItems: 'center' }}>
        {/* Channel + perf + move chip */}
        <div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 3 }}>
            <span style={{ width: 9, height: 9, borderRadius: '50%', background: init.color }} />
            <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>{meta.label || init.name}</div>
          </div>
          <div style={{ fontSize: 10.5, color: 'var(--ink-4)', display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ fontFamily: 'var(--ff-mono)' }}>${init.cpl} CPL</span>
            {winning && <span style={{ color: 'var(--sage)', fontWeight: 700 }}>● UNDER BENCHMARK</span>}
            {losing && <span style={{ color: '#917200', fontWeight: 700 }}>● OVER BENCHMARK</span>}
            {!winning && !losing && <span style={{ color: 'var(--ink-4)' }}>at benchmark</span>}
          </div>
          {move && (
            <button onClick={() => setOpenWhy(o => !o)} title="Why did this change?"
              style={{ marginTop: 6, display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 10, fontWeight: 700, padding: '2px 8px', borderRadius: 999, cursor: 'pointer',
                border: '1px solid ' + (move.delta >= 0 ? 'rgba(122,155,110,0.5)' : 'rgba(193,102,72,0.5)'),
                background: move.delta >= 0 ? 'rgba(122,155,110,0.12)' : 'rgba(193,102,72,0.10)',
                color: move.delta >= 0 ? 'var(--sage)' : 'var(--terra)' }}>
              {move.delta >= 0 ? '+' : '−'}${Math.abs(move.delta)} this month
              <span style={{ fontSize: 8 }}>{openWhy ? '▲' : '▼'}</span>
            </button>
          )}
        </div>

        {/* Read-only mix bar */}
        <div>
          <div style={{ position: 'relative', height: 28 }}>
            <div style={{ position: 'absolute', top: 12, left: 0, right: 0, height: 4, background: 'var(--cream-2)', borderRadius: 2 }} />
            <div style={{ position: 'absolute', top: 12, left: 0, width: pct(value) + '%', height: 4, background: init.color, borderRadius: 2, opacity: 0.85 }} />
            {/* Floor marker */}
            <div title={`Playbook floor: $${meta.floor}`} style={{ position: 'absolute', top: 8, left: pct(meta.floor) + '%', width: 2, height: 12, background: 'var(--ink-4)', opacity: 0.4 }} />
            {/* Peer marker */}
            {meta.target && <div title={`Peer median: $${meta.target}`} style={{ position: 'absolute', top: 8, left: pct(meta.target) + '%', width: 2, height: 12, background: 'var(--violet)', opacity: 0.7 }} />}
            {/* Position dot */}
            <div style={{ position: 'absolute', top: 9, left: `calc(${pct(value)}% - 5px)`, width: 10, height: 10, borderRadius: '50%', background: init.color, boxShadow: '0 1px 4px rgba(0,0,0,0.15)' }} />
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: 'var(--ink-4)', marginTop: 2, fontFamily: 'var(--ff-mono)' }}>
            <span>floor ${meta.floor}</span>
            <span style={{ color: 'var(--violet)' }}>peer ${meta.target}</span>
          </div>
        </div>

        {/* Value */}
        <div style={{ textAlign: 'right' }}>
          <div style={{ fontFamily: 'var(--ff-display)', fontSize: 22, fontWeight: 500, color: 'var(--ink)', letterSpacing: '-0.02em', lineHeight: 1 }}>${value.toLocaleString()}</div>
          <div style={{ fontSize: 10, color: 'var(--ink-4)', fontFamily: 'var(--ff-mono)', marginTop: 2 }}>{Math.round(share * 100)}% of mix</div>
        </div>
      </div>

      {/* Expanded reasoning */}
      {openWhy && move && (
        <div style={{ margin: '0 18px 14px', padding: '12px 14px', background: 'var(--cream)', borderRadius: 10, border: '1px solid var(--cream-2)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 6 }}>
            <span style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)' }}>{move.headline}</span>
            <ReasonChips reasons={move.reasons} />
          </div>
          <div style={{ fontSize: 11.5, color: 'var(--ink-3)', lineHeight: 1.55 }}>{move.detail}</div>
        </div>
      )}
    </div>
  );
}

// ----------------------------------------------------------------
// LENNY MOVES PANEL, this month's rebalance, summarized
// ----------------------------------------------------------------
function LennyMovesPanel() {
  const label = (k) => (PLAYBOOK_META[k] || {}).label || k;
  return (
    <div className="card" style={{ marginTop: 16, overflow: 'hidden' }}>
      <div style={{ padding: '16px 18px 12px', display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
        <window.LennyLlama size={32} mood="happy" bg="circle" />
        <div style={{ flex: 1, minWidth: 220 }}>
          <div style={{ fontSize: 10.5, color: 'var(--violet)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 700 }}>April rebalance · ran {REBALANCE.date}</div>
          <div style={{ fontFamily: 'var(--ff-display)', fontSize: 16, fontWeight: 500, color: 'var(--ink)' }}>Why your mix changed this month</div>
        </div>
        <span className="badge badge-ok" style={{ flexShrink: 0 }}>● {REBALANCE.impact}</span>
      </div>

      {REBALANCE.moves.map((m, i) => (
        <div key={i} style={{ padding: '11px 18px', borderTop: '1px solid var(--cream-2)', display: 'grid', gridTemplateColumns: '210px 1fr', gap: 14, alignItems: 'start' }}>
          <div>
            <div style={{ fontSize: 13, fontWeight: 700, fontFamily: 'var(--ff-display)', color: 'var(--ink)', marginBottom: 3 }}>${m.amount} moved</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', lineHeight: 1.4 }}>{label(m.from)} <span style={{ color: 'var(--ink-4)' }}>→</span> {label(m.to)}</div>
          </div>
          <div>
            <div style={{ marginBottom: 5 }}><ReasonChips reasons={m.reasons} /></div>
            <div style={{ fontSize: 11.5, color: 'var(--ink-3)', lineHeight: 1.5 }}>{m.why}</div>
          </div>
        </div>
      ))}

      <div style={{ padding: '11px 18px', borderTop: '1px solid var(--cream-2)', background: 'var(--violet-soft, rgba(77,105,137,0.06))', display: 'flex', gap: 10, alignItems: 'center' }}>
        <ReasonChips reasons={['testing']} />
        <div style={{ fontSize: 11.5, color: 'var(--ink-3)', lineHeight: 1.5, flex: 1 }}>{REBALANCE.test}</div>
      </div>
    </div>
  );
}

// ----------------------------------------------------------------
// REBALANCE LOG, the monthly changelog, every move on record
// ----------------------------------------------------------------
function RebalanceLog() {
  const [open, setOpen] = useStateBS(() => ({ [REBALANCE_HISTORY[0].month]: true }));
  const toggle = (m) => setOpen(prev => ({ ...prev, [m]: !prev[m] }));
  return (
    <div style={{ marginTop: 20 }}>
      <div className="section-label" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <span>Rebalance log <span className="count">every budget move, logged &amp; explained</span></span>
        <span style={{ fontSize: 10.5, color: 'var(--ink-4)', fontFamily: 'var(--ff-mono)' }}>next run · May 1</span>
      </div>
      <div className="card" style={{ overflow: 'hidden' }}>
        {REBALANCE_HISTORY.map((mo) => (
          <div key={mo.month} style={{ borderBottom: '1px solid var(--cream-2)' }}>
            <button onClick={() => toggle(mo.month)}
              style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 18px', background: 'transparent', border: 'none', cursor: 'pointer', textAlign: 'left' }}>
              <span style={{ fontSize: 9, color: 'var(--ink-4)' }}>{open[mo.month] ? '▼' : '▶'}</span>
              <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', fontFamily: 'var(--ff-display)', width: 130 }}>{mo.month}</span>
              <span style={{ fontSize: 11.5, color: 'var(--ink-3)', flex: 1 }}>{mo.summary}</span>
              <span style={{ fontSize: 10.5, color: 'var(--ink-4)', fontFamily: 'var(--ff-mono)' }}>ran {mo.date}</span>
            </button>
            {open[mo.month] && (
              <div style={{ padding: '0 18px 12px 39px', display: 'flex', flexDirection: 'column', gap: 8 }}>
                {mo.entries.map((e, i) => (
                  <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'baseline', flexWrap: 'wrap' }}>
                    <span style={{ fontSize: 11.5, color: 'var(--ink-2)', lineHeight: 1.5 }}>{e.txt}</span>
                    <ReasonChips reasons={e.reasons} />
                  </div>
                ))}
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

// ----------------------------------------------------------------
// LENNY MATH PANEL, explains the split + ±$500 forecast
// ----------------------------------------------------------------
function LennyMathPanel({ total, initiatives, allocations, onApplyTotal }) {
  const [showMath, setShowMath] = useStateBS(false);

  // Forecast at +/- $500
  const plus = autoAllocate(total + 500, initiatives);
  const minus = autoAllocate(Math.max(0, total - 500), initiatives);

  // Estimate leads = $/CPL summed
  const estLeads = (alloc) => initiatives.reduce((s, i) => {
    const cpl = i.cpl || PLAYBOOK_META[i.key]?.cpl || 30;
    return s + Math.floor((alloc[i.key] || 0) / cpl);
  }, 0);
  const leadsNow = estLeads(allocations);
  const leadsPlus = estLeads(plus.allocations);
  const leadsMinus = estLeads(minus.allocations);

  // Identify the channel that benefited most from perf-tuning
  const tunedUp = initiatives.map(i => {
    const meta = PLAYBOOK_META[i.key] || {};
    const benchCpl = BRAND_CPL_BENCHMARK[i.key] || meta.cpl || 30;
    const perf = benchCpl / (i.cpl || meta.cpl || 30);
    return { key: i.key, name: meta.label || i.name, perf, cpl: i.cpl || meta.cpl };
  }).sort((a, b) => b.perf - a.perf);
  const topUp = tunedUp[0];
  const topDown = tunedUp[tunedUp.length - 1];

  return (
    <div className="card card-pad" style={{ background: 'var(--ink)', color: 'var(--cream)', position: 'relative', overflow: 'hidden', marginTop: 16 }}>
      <div style={{ position: 'absolute', top: -50, right: -50, width: 240, height: 240, background: 'radial-gradient(circle, rgba(206,158,7,.18), transparent 70%)' }} />

      <div style={{ position: 'relative', zIndex: 1 }}>
        {/* Header */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
          <window.LennyLlama size={32} mood="thinking" bg="circle" />
          <div>
            <div style={{ fontSize: 11, color: 'var(--marigold)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 700 }}>Lenny · the math</div>
            <div style={{ fontFamily: 'var(--ff-display)', fontSize: 16, fontWeight: 500 }}>Here's how I'd split ${total.toLocaleString()}</div>
          </div>
        </div>

        {/* The why */}
        <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 12, fontSize: 12.5, lineHeight: 1.55, color: 'rgba(250,246,239,0.9)', marginBottom: 10 }}>
          <div style={{ fontSize: 10.5, color: 'rgba(250,246,239,0.5)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 700, paddingTop: 2 }}>Playbook ratios</div>
          <div>50% Search, 20% LSA, 20% Meta, 10% promos. Floors paid first (${initiatives.reduce((s, i) => s + (PLAYBOOK_META[i.key]?.floor || 0), 0).toLocaleString()}/mo total).</div>
        </div>

        {topUp && topUp.perf > 1.05 && (
          <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 12, fontSize: 12.5, lineHeight: 1.55, color: 'rgba(250,246,239,0.9)', marginBottom: 10 }}>
            <div style={{ fontSize: 10.5, color: 'rgba(250,246,239,0.5)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 700, paddingTop: 2 }}>Local tuning</div>
            <div>
              <strong style={{ color: 'var(--marigold)' }}>+{Math.round((topUp.perf - 1) * 100)}% to {topUp.name}</strong>, your ${topUp.cpl} CPL is below the brand ${BRAND_CPL_BENCHMARK[topUp.key]} benchmark, so it earns more share.
              {topDown && topDown.perf < 0.95 && (
                <> <strong style={{ color: 'var(--cream)' }}>−{Math.round((1 - topDown.perf) * 100)}% from {topDown.name}</strong>, running over benchmark.</>
              )}
            </div>
          </div>
        )}

        {/* Sensitivity strip */}
        <div style={{ marginTop: 16, padding: 14, background: 'rgba(250,246,239,0.06)', borderRadius: 10, border: '1px solid rgba(250,246,239,0.12)' }}>
          <div style={{ fontSize: 10.5, color: 'rgba(250,246,239,0.6)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 700, marginBottom: 10 }}>What changes at ±$500</div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 1, background: 'rgba(250,246,239,0.12)', borderRadius: 6, overflow: 'hidden' }}>
            {[
              { label: '−$500/mo', total: Math.max(0, total - 500), leads: leadsMinus, delta: leadsMinus - leadsNow, action: () => onApplyTotal(Math.max(0, total - 500)) },
              { label: 'Current', total, leads: leadsNow, delta: 0, current: true },
              { label: '+$500/mo', total: total + 500, leads: leadsPlus, delta: leadsPlus - leadsNow, action: () => onApplyTotal(total + 500) },
            ].map((c, i) => (
              <div key={i} onClick={c.action} style={{ padding: '12px 10px', background: c.current ? 'rgba(206,158,7,0.16)' : 'var(--ink)', cursor: c.action ? 'pointer' : 'default', textAlign: 'center', transition: 'background 0.15s' }}
                onMouseEnter={e => { if (c.action) e.currentTarget.style.background = 'rgba(250,246,239,0.08)'; }}
                onMouseLeave={e => { if (c.action) e.currentTarget.style.background = 'var(--ink)'; }}>
                <div style={{ fontSize: 10, color: c.current ? 'var(--marigold)' : 'rgba(250,246,239,0.5)', fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase', marginBottom: 4 }}>{c.label}</div>
                <div style={{ fontFamily: 'var(--ff-display)', fontSize: 22, fontWeight: 500, color: 'var(--cream)', letterSpacing: '-0.02em', lineHeight: 1 }}>${c.total.toLocaleString()}</div>
                <div style={{ fontSize: 11, color: 'rgba(250,246,239,0.7)', marginTop: 4 }}>~{c.leads} leads/mo</div>
                {!c.current && <div style={{ fontSize: 11, color: c.delta > 0 ? 'var(--sage)' : '#f6a663', fontWeight: 600, marginTop: 2 }}>{c.delta > 0 ? '+' : ''}{c.delta} leads</div>}
              </div>
            ))}
          </div>
          <div style={{ fontSize: 10.5, color: 'rgba(250,246,239,0.45)', marginTop: 8, textAlign: 'center' }}>Click ± to bump your monthly total · estimated at your local CPLs</div>
        </div>

        {/* Show full math link */}
        <div style={{ marginTop: 12 }}>
          <button onClick={() => setShowMath(!showMath)} style={{ background: 'transparent', border: '1px solid rgba(250,246,239,0.2)', color: 'rgba(250,246,239,0.85)', padding: '6px 12px', borderRadius: 6, fontSize: 11.5, cursor: 'pointer', fontWeight: 500 }}>
            {showMath ? 'Hide' : 'Show'} the formula →
          </button>
        </div>
        {showMath && (
          <div style={{ marginTop: 12, padding: 14, background: 'rgba(250,246,239,0.04)', borderRadius: 8, fontSize: 12, lineHeight: 1.65, color: 'rgba(250,246,239,0.85)', fontFamily: 'var(--ff-mono)' }}>
            <div style={{ marginBottom: 6 }}><span style={{ color: 'var(--marigold)' }}>1.</span> Pay floors: ${initiatives.reduce((s, i) => s + (PLAYBOOK_META[i.key]?.floor || 0), 0).toLocaleString()}</div>
            <div style={{ marginBottom: 6 }}><span style={{ color: 'var(--marigold)' }}>2.</span> Remainder ${Math.max(0, total - initiatives.reduce((s, i) => s + (PLAYBOOK_META[i.key]?.floor || 0), 0)).toLocaleString()} split by ratio × perf-multiplier</div>
            <div style={{ marginBottom: 6 }}><span style={{ color: 'var(--marigold)' }}>3.</span> Perf-multiplier = brand_CPL / your_CPL · capped at 0.7×–1.3×</div>
            <div><span style={{ color: 'var(--marigold)' }}>4.</span> Round to nearest $25 · drift goes to largest channel</div>
          </div>
        )}
      </div>
    </div>
  );
}

// ----------------------------------------------------------------
// LOW-BUDGET WARNING
// ----------------------------------------------------------------
function FloorWarning({ deficit, totalFloor }) {
  return (
    <div className="card card-pad" style={{ background: 'var(--marigold-soft, #fcf6e1)', border: '1.5px solid var(--marigold)', display: 'flex', gap: 14, alignItems: 'center', marginTop: 16 }}>
      <div style={{ fontSize: 24, flexShrink: 0 }}>⚠️</div>
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 13.5, fontWeight: 700, color: '#886805', marginBottom: 3 }}>Below playbook floor by ${deficit.toLocaleString()}/mo</div>
        <div style={{ fontSize: 12, color: '#886805', lineHeight: 1.5 }}>The FreshNest playbook recommends a minimum of <strong>${totalFloor.toLocaleString()}/mo</strong> across enrolled channels. Below this, channels can't bid competitively and CPL gets worse fast. Strongly consider hitting at least the floor.</div>
      </div>
    </div>
  );
}

// ----------------------------------------------------------------
// SELF-MANAGED CONFIRMATION, friction before leaving Smart Budget
// ----------------------------------------------------------------
function SelfManagedConfirmModal({ onCancel, onConfirm }) {
  return (
    <window.ModalPortal>
      <div onClick={onCancel} style={{ position: 'fixed', inset: 0, background: 'rgba(36,30,28,0.55)', zIndex: 9500, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
        <div onClick={e => e.stopPropagation()} className="card" style={{ width: 500, maxWidth: '100%', padding: 24 }}>
          <div style={{ display: 'flex', gap: 12, alignItems: 'center', marginBottom: 12 }}>
            <window.LennyLlama size={36} mood="thinking" bg="circle" />
            <div style={{ fontFamily: 'var(--ff-display)', fontSize: 19, fontWeight: 600, color: 'var(--ink)' }}>Switch to Self-managed?</div>
          </div>
          <div style={{ fontSize: 12.5, color: 'var(--ink-2)', lineHeight: 1.6, marginBottom: 14 }}>
            Smart Budget is the recommended way to run this location. You keep control of the total, the platform optimizes the mix. Switching to Self-managed means:
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 16 }}>
            {[
              'Monthly auto-rebalancing stops, your split stays fixed until you change it yourself.',
              'You take on per-channel budgets, playbook floors, and pacing.',
              'Larger changes may require a review call with your marketing advisor.',
            ].map((t, i) => (
              <div key={i} style={{ display: 'flex', gap: 8, alignItems: 'baseline', fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.5 }}>
                <span style={{ color: 'var(--terra)', fontWeight: 700, flexShrink: 0 }}>–</span>
                <span>{t}</span>
              </div>
            ))}
          </div>
          <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
            <button className="btn btn-ghost btn-sm" onClick={onConfirm}>Switch anyway</button>
            <button className="btn btn-primary btn-sm" onClick={onCancel}>Keep Smart Budget</button>
          </div>
        </div>
      </div>
    </window.ModalPortal>
  );
}

// ----------------------------------------------------------------
// MAIN COMPONENT, Smart Budget wrapper
// ----------------------------------------------------------------
function BudgetSimpleMode({ initiatives, initialTotal, onCommit }) {
  const STORAGE_KEY = 'localact_budget_simple_total';

  // Default total = sum of current localBudgets, or peer median total
  const fallbackTotal = useMemoBS(() => {
    return initiatives.reduce((s, i) => s + (i.localBudget || PLAYBOOK_META[i.key]?.target || 500), 0);
  }, [initiatives]);

  const [total, setTotal] = useStateBS(() => {
    try {
      const stored = parseInt(localStorage.getItem(STORAGE_KEY) || '0', 10);
      return stored > 0 ? stored : (initialTotal || fallbackTotal);
    } catch { return initialTotal || fallbackTotal; }
  });

  // Persist
  useEffectBS(() => {
    try { localStorage.setItem(STORAGE_KEY, String(total)); } catch {}
  }, [total]);

  // Platform-controlled allocations from the current total
  const auto = useMemoBS(() => autoAllocate(total, initiatives), [total, initiatives]);
  const effective = auto.allocations;
  const effectiveTotal = Object.values(effective).reduce((s, v) => s + v, 0);

  const apply = () => {
    onCommit && onCommit(effective, total);
    if (window.T) window.T('Smart Budget set', `$${total.toLocaleString()}/mo, LOCALACT manages the mix across ${initiatives.length} channels and rebalances monthly.`, 'ok');
  };

  return (
    <div>
      {/* HERO, single budget input */}
      <div className="card card-pad" style={{ background: 'linear-gradient(135deg, var(--cream-2), var(--cream))', marginBottom: 18, padding: '32px 24px' }}>
        <div style={{ textAlign: 'center', marginBottom: 8 }}>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.1em', fontWeight: 700, marginBottom: 6 }}>Your monthly marketing budget</div>
          <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginBottom: 24 }}>One number, you control the total. The platform controls the mix and rebalances it monthly as performance, competition, and demand shift.</div>
        </div>
        <BigBudgetInput value={total} onChange={setTotal} />
        <div style={{ display: 'flex', gap: 10, justifyContent: 'center', marginTop: 18, flexWrap: 'wrap' }}>
          {[1500, 2500, 3500, 5000].map(p => (
            <button key={p} onClick={() => setTotal(p)}
              style={{ background: total === p ? 'var(--ink)' : 'var(--paper)', color: total === p ? 'var(--cream)' : 'var(--ink-2)', border: '1px solid ' + (total === p ? 'var(--ink)' : 'var(--cream-3)'), borderRadius: 999, padding: '6px 14px', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>
              ${p.toLocaleString()}
            </button>
          ))}
        </div>
      </div>

      {/* WARNING if below floor */}
      {auto.deficit > 0 && <FloorWarning deficit={auto.deficit} totalFloor={auto.totalFloor} />}

      {/* CURRENT MIX, platform-managed, read-only */}
      <div style={{ marginTop: auto.deficit > 0 ? 16 : 0 }}>
        <div className="section-label" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
          <span>Your current mix <span className="count">managed by LOCALACT · rebalanced monthly</span></span>
          <span style={{ fontSize: 10.5, color: 'var(--ink-4)', fontFamily: 'var(--ff-mono)' }}>tap a change chip to see why</span>
        </div>
        <div className="card" style={{ overflow: 'hidden' }}>
          {initiatives.map(init => {
            const v = effective[init.key] || 0;
            const share = effectiveTotal > 0 ? v / effectiveTotal : 0;
            return <ChannelAllocRow key={init.key} init={init} value={v} share={share} />;
          })}
          {/* Total row */}
          <div style={{ padding: '14px 18px', background: 'var(--cream)', display: 'grid', gridTemplateColumns: '220px 1fr 92px', gap: 18, alignItems: 'center', borderTop: '2px solid var(--ink)' }}>
            <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink-2)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>Total / month</div>
            <div style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>✓ Matches your budget · next rebalance May 1</div>
            <div style={{ textAlign: 'right', fontFamily: 'var(--ff-display)', fontSize: 24, fontWeight: 600, color: 'var(--ink)' }}>${effectiveTotal.toLocaleString()}</div>
          </div>
        </div>
      </div>

      {/* THIS MONTH'S MOVES */}
      <LennyMovesPanel />

      {/* LENNY MATH PANEL */}
      <LennyMathPanel total={total} initiatives={initiatives} allocations={effective} onApplyTotal={setTotal} />

      {/* COMMIT BUTTON */}
      <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 16 }}>
        <button className="btn btn-primary" onClick={apply}>Set monthly budget →</button>
      </div>

      {/* REBALANCE LOG */}
      <RebalanceLog />
    </div>
  );
}

window.BudgetSimpleMode = BudgetSimpleMode;
window.autoAllocate = autoAllocate;
window.SelfManagedConfirmModal = SelfManagedConfirmModal;
