/* web-tabs.jsx — History and Insights tab content for the web shell */

function dailyTotalInputStyle(fontSize = 18) {
  return {
    width: '100%',
    minWidth: 0,
    boxSizing: 'border-box',
    background: 'transparent',
    border: 'none',
    outline: 'none',
    color: 'var(--ink)',
    fontFamily: "'JetBrains Mono', monospace",
    fontSize,
    fontVariantNumeric: 'tabular-nums',
  };
}


// ═══════════════════════════════════════════════════════════════
// HISTORY TAB
// ═══════════════════════════════════════════════════════════════

function WebHistory({ scenario, tdee = TDEE, windowDays = 3, onUpdateDailyIntake }) {
  const isNarrow = useIsNarrow();
  const history = scenario.dailyTotalOnly
    ? scenario.days.map((day) => ({ ...day, dateObj: parseEntryDate(day), label: displayDate(day.date) }))
    : HISTORY.map((day) => ({ ...day, dateObj: day.date }));
  const todayIdx = history.length - 1;
  const todayKey = history[todayIdx]?.dateKey || history[todayIdx]?.date;
  const [selectedKey, setSelectedKey] = React.useState(todayKey);
  const rawSelectedIdx = history.findIndex((day) => (day.dateKey || day.date) === selectedKey);
  const selectedIdx = rawSelectedIdx >= 0 ? rawSelectedIdx : todayIdx;
  React.useEffect(() => {
    if (!history.some((day) => (day.dateKey || day.date) === selectedKey)) {
      setSelectedKey(todayKey);
    }
  }, [history.length, selectedKey, todayKey]);
  if (history.length === 0) {
    return <EmptyDataView title="history" detail="Daily totals will appear here after logging." />;
  }
  const loggedHistory = history.filter((day) => day.intakeSource === 'logged' && Number.isFinite(day.balance));
  const selected = history[selectedIdx];
  const canEditSelectedDay = scenario.dailyTotalOnly && typeof onUpdateDailyIntake === 'function';

  // Pad calendar so first row starts on Monday (ISO week)
  const firstDow = history[0].dateObj.getDay(); // 0 = Sun
  const padBefore = (firstDow + 6) % 7; // Mon=0
  // pad after: extend to fill last row
  const totalCells = padBefore + history.length;
  const padAfter = (7 - (totalCells % 7)) % 7;
  const grid = [
    ...Array.from({ length: padBefore }, () => null),
    ...history,
    ...Array.from({ length: padAfter }, () => 'future'),
  ];

  // Weekly averages for trend
  const weeks = [];
  for (let i = 0; i < history.length; i += 7) {
    const slice = history.slice(i, i + 7);
    const logged = slice.filter((d) => d.intakeSource === 'logged' && Number.isFinite(d.balance));
    const avg = logged.length ? logged.reduce((s, d) => s + d.balance, 0) / logged.length : null;
    weeks.push({ avg, days: slice, loggedDays: logged.length });
  }

  const streaks = getZoneStreaks(history, tdee);

  const overallAvg = loggedHistory.length ? loggedHistory.reduce((s, d) => s + d.balance, 0) / loggedHistory.length : null;
  const coveragePct = Math.round((loggedHistory.length / history.length) * 100);

  return (
    <div style={{ display: 'grid', gridTemplateColumns: isNarrow ? '1fr' : '1fr 380px', height: isNarrow ? 'auto' : '100%' }}>
      {/* LEFT — calendar */}
      <div style={{ padding: isNarrow ? '18px' : '20px 40px 28px', display: 'flex', flexDirection: 'column', overflow: isNarrow ? 'visible' : 'hidden', minWidth: 0 }}>
        <SectionEyebrow
          left={scenario.dailyTotalOnly ? `${history.length}-day history · ${history[0].label} — ${history[history.length - 1].label}` : 'last 4 weeks · apr 19 — may 16'}
          right={overallAvg == null ? 'avg N/A' : `avg ${overallAvg > 0 ? '+' : ''}${Math.round(overallAvg)} kcal/day`}
        />

        {/* day-of-week header */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, minmax(0, 1fr))', gap: isNarrow ? 5 : 12, marginTop: 18 }}>
          {['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].map((d) => (
            <div key={d} className="mono" style={{
              fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.10em',
              textTransform: 'uppercase', textAlign: 'center',
            }}>{d}</div>
          ))}
        </div>

        {/* calendar grid */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, minmax(0, 1fr))', gap: isNarrow ? 5 : 12, marginTop: 10 }}>
          {grid.map((cell, i) => {
            if (cell === null) {
              return <div key={`pad-${i}`} style={{ aspectRatio: '1 / 1' }} />;
            }
            if (cell === 'future') {
              return (
                <div key={`fut-${i}`} style={{ display: 'flex', justifyContent: 'center', aspectRatio: '1 / 1' }}>
                  <MiniOrb isFuture size={isNarrow ? 42 : 82} />
                </div>
              );
            }
            const idx = history.indexOf(cell);
            const cellKey = cell.dateKey || cell.date;
            const isToday = idx === todayIdx;
            const isSelected = idx === selectedIdx;
            const isLogged = cell.intakeSource === 'logged' && Number.isFinite(cell.balance);
            const orbSize = isNarrow ? 42 : 82;
            return (
              <button key={i} onClick={() => setSelectedKey(cellKey)} style={{
                background: 'transparent', border: 'none', padding: 0, cursor: 'pointer',
                display: 'flex', justifyContent: 'center', alignItems: 'flex-start', aspectRatio: '1 / 1',
                position: 'relative',
              }}>
                <div style={{
                  position: 'relative',
                  width: orbSize,
                  height: orbSize,
                  flex: '0 0 auto',
                  outline: isSelected ? '2px solid var(--ink)' : 'none',
                  outlineOffset: 3,
                  borderRadius: 999,
                }}>
                  {isLogged
                    ? <MiniOrb balance={cell.balance} label={String(cell.d)} size={orbSize} isToday={isToday} tdee={tdee} />
                    : <EmptyCalendarDay label={String(cell.d)} isToday={isToday} size={orbSize} />}
                </div>
              </button>
            );
          })}
        </div>

        {/* legend */}
        <div style={{ marginTop: 'auto', paddingTop: 12, display: 'flex', alignItems: 'center', gap: isNarrow ? 10 : 18, flexWrap: 'wrap' }}>
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.10em', textTransform: 'uppercase' }}>legend</span>
          <LegendDot color="var(--rust)" label="gaining" />
          <LegendDot color="var(--amber)" label="keeping" />
          <LegendDot color="var(--moss)" label="losing" />
          <LegendDot color="transparent" label="N/A empty" />
          <span style={{ flex: 1 }} />
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>fluid level = caloric balance</span>
        </div>
      </div>

      {/* RIGHT — selected day + stats */}
      <aside style={{
        borderLeft: isNarrow ? 'none' : '1px solid var(--hair)',
        borderTop: isNarrow ? '1px solid var(--hair)' : 'none',
        padding: isNarrow ? '18px' : '20px 28px 28px',
        display: 'flex', flexDirection: 'column', gap: 20, overflow: 'auto',
      }}>
        <div>
          <SectionEyebrow left={selected.dow.toLowerCase() + ' · ' + selected.label.toLowerCase()} />
          <div style={{ display: 'flex', alignItems: 'center', gap: 18, marginTop: 12 }}>
            {selected.intakeSource === 'logged' && Number.isFinite(selected.balance)
              ? <FluidOrb days={[selected]} size={132} showMarkers={false} animate={false} tdee={tdee} />
              : <InsufficientData label="N/A" detail="No total logged" />}
            <div>
              <div style={{
                fontFamily: "'Instrument Serif', serif", fontSize: 40, lineHeight: 1,
                fontStyle: 'italic', color: selected.intakeSource === 'logged' && Number.isFinite(selected.balance) ? ZONES[classify(selected.balance, tdee)].hex : 'var(--ink-3)',
              }}>{selected.intakeSource === 'logged' && Number.isFinite(selected.balance) ? `${classify(selected.balance, tdee)}.` : 'N/A'}</div>
              <div className="mono num-tab" style={{ fontSize: 13, color: 'var(--ink-2)', marginTop: 6 }}>
                {selected.intakeSource === 'logged' && Number.isFinite(selected.intake) ? `${selected.intake.toLocaleString()} kcal intake` : 'No logged intake'}
              </div>
              <div className="mono num-tab" style={{ fontSize: 13, color: 'var(--ink-3)', marginTop: 2 }}>
                {selected.intakeSource === 'logged' && Number.isFinite(selected.balance) ? `${selected.balance > 0 ? '+' : ''}${selected.balance} vs maintenance` : 'Not included in summaries'}
              </div>
            </div>
          </div>
        </div>

        {canEditSelectedDay && (
          <SelectedDayEditor
            selected={selected}
            tdee={tdee}
            onSave={(dateKey, intake) => onUpdateDailyIntake(dateKey, intake)}
            isNarrow={isNarrow}
          />
        )}

        <div>
          <SectionHead title="coverage" right={`${loggedHistory.length} logged`} />
          <div style={{
            background: 'var(--paper-2)', border: '1px solid var(--hair)',
            borderRadius: 12, padding: '14px', marginTop: 8,
            display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10,
          }}>
            <MiniSummary label="logged" value={loggedHistory.length} unit="days" />
            <MiniSummary label="coverage" value={`${coveragePct}%`} unit="range" />
            <MiniSummary label="window" value={windowDays} unit="days" />
          </div>
        </div>

        {/* weekly avg trend */}
        <div>
          <SectionHead title="weekly avg" right="logged days" />
          <div style={{
            background: 'var(--paper-2)', border: '1px solid var(--hair)',
            borderRadius: 12, padding: '14px', marginTop: 8,
          }}>
            <WeeklyBars weeks={weeks} tdee={tdee} />
          </div>
        </div>

        <div>
          <SectionHead title="streaks" />
          <div style={{
            background: 'var(--paper-2)', border: '1px solid var(--hair)',
            borderRadius: 12, padding: '14px', marginTop: 8,
            display: 'grid', gap: 10,
          }}>
            {['gaining', 'keeping', 'losing'].map((zone) => (
              <ZoneStreakRow key={zone} zone={zone} streak={streaks[zone]} />
            ))}
          </div>
        </div>
      </aside>
    </div>
  );
}

function SelectedDayEditor({ selected, tdee, onSave, isNarrow }) {
  const selectedKey = selected.dateKey || selected.date;
  const isLogged = selected.intakeSource === 'logged' && Number.isFinite(selected.intake);
  const [draft, setDraft] = React.useState(isLogged ? String(selected.intake) : '');
  const [message, setMessage] = React.useState('');

  React.useEffect(() => {
    setDraft(isLogged ? String(selected.intake) : '');
  }, [selectedKey, selected.intake, isLogged]);

  React.useEffect(() => {
    setMessage('');
  }, [selectedKey]);

  const save = () => {
    const draftText = String(draft).trim();
    const numeric = Number(draftText);
    if (!draftText || !Number.isFinite(numeric) || numeric < 0) {
      setMessage('enter kcal');
      return;
    }
    onSave(selectedKey, numeric);
    setMessage('saved');
  };

  const resetDraft = () => {
    setDraft(isLogged ? String(selected.intake) : '');
    setMessage('');
  };

  const draftText = String(draft).trim();
  const draftValue = Number(draftText);
  const projectedBalance = draftText && Number.isFinite(draftValue) ? Math.round(draftValue - tdee) : null;
  const projectedZone = projectedBalance == null ? null : classify(projectedBalance, tdee);

  return (
    <div>
      <SectionHead title="daily total" right={isLogged ? 'edit selected day' : 'add selected day'} />
      <div style={{
        background: 'var(--paper-2)', border: '1px solid var(--hair)',
        borderRadius: 12, padding: 14, marginTop: 8,
        display: 'grid', gap: 12,
      }}>
        <label style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>
            {displayDate(selected.date)}
          </span>
          <div style={{
            display: 'grid',
            gridTemplateColumns: isNarrow ? '1fr' : 'minmax(0, 1fr) auto',
            gap: 10,
            alignItems: 'end',
          }}>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, minWidth: 0 }}>
              <input
                inputMode="numeric"
                value={draft}
                placeholder="N/A"
                onChange={(e) => {
                  setDraft(e.target.value);
                  setMessage('');
                }}
                onKeyDown={(e) => {
                  if (e.key === 'Enter') save();
                  if (e.key === 'Escape') resetDraft();
                }}
                style={{ ...dailyTotalInputStyle(28), minWidth: 0 }}
              />
              <span className="mono" style={{ fontSize: 12, color: 'var(--ink-3)' }}>kcal</span>
            </div>
            <div className="mono" style={{ fontSize: 10.5, color: projectedZone ? ZONES[projectedZone].hex : 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', whiteSpace: 'nowrap' }}>
              {projectedBalance == null ? 'not included' : `${projectedBalance > 0 ? '+' : ''}${projectedBalance} · ${projectedZone}`}
            </div>
          </div>
        </label>
        <div style={{
          display: 'grid',
          gridTemplateColumns: isNarrow ? '1fr' : '1fr 1fr',
          gap: 10,
          alignItems: 'center',
        }}>
          <button onClick={save} style={settingsButtonStyle()}>{isLogged ? 'save edit' : 'save day'}</button>
          <button onClick={resetDraft} style={settingsButtonStyle(true)}>cancel</button>
        </div>
        <div className="mono" style={{ minHeight: 14, fontSize: 10.5, color: message === 'saved' ? 'var(--moss)' : 'var(--ink-3)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
          {message || (isLogged ? 'logged day' : 'empty until saved')}
        </div>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// ABOUT TAB
// ═══════════════════════════════════════════════════════════════

function WebAbout() {
  const isNarrow = useIsNarrow();
  return (
    <div style={{
      height: isNarrow ? 'auto' : '100%',
      overflow: 'auto',
      padding: isNarrow ? '18px' : '20px 40px 28px',
      boxSizing: 'border-box',
    }}>
      <SectionEyebrow left="about cico" right="local estimate" />
      <div style={{
        display: 'grid',
        gridTemplateColumns: isNarrow ? '1fr' : 'minmax(0, 1fr) 360px',
        gap: isNarrow ? 18 : 28,
        marginTop: 20,
        alignItems: 'start',
      }}>
        <section style={{ display: 'flex', flexDirection: 'column', gap: 18, minWidth: 0 }}>
          <div>
            <h1 style={{
              margin: 0,
              fontFamily: "'Instrument Serif', serif",
              fontSize: isNarrow ? 48 : 68,
              lineHeight: 0.96,
              fontWeight: 400,
              color: 'var(--ink)',
            }}>
              recent balance, made visible.
            </h1>
            <p style={{
              margin: '16px 0 0',
              maxWidth: 700,
              color: 'var(--ink-2)',
              fontSize: 15,
              lineHeight: 1.55,
            }}>
              CICO turns manual daily calorie totals into a simple storage trend: gaining, keeping, or losing. It compares recent intake with your estimated maintenance and shows the result as a fluid storage vessel.
            </p>
          </div>

          <div style={{
            display: 'grid',
            gridTemplateColumns: isNarrow ? '1fr' : 'repeat(3, minmax(0, 1fr))',
            gap: 12,
          }}>
            <AboutNote title="maintenance" stat="TDEE" body="Total daily energy expenditure: the app's estimate of calories needed to hold weight steady." />
            <AboutNote title="daily balance" stat="intake - TDEE" body="A positive balance fills storage; a negative balance drains it." />
            <AboutNote title="state" stat="+/-10%" body="The keeping band is set at plus or minus ten percent of TDEE." />
          </div>

          <div style={{
            background: 'var(--paper-2)',
            border: '1px solid var(--hair)',
            borderRadius: 14,
            padding: 18,
          }}>
            <SectionHead title="how to read it" right="estimate" />
            <div style={{ marginTop: 12, display: 'grid', gap: 10, color: 'var(--ink-2)', fontSize: 13.5, lineHeight: 1.5 }}>
              <p style={{ margin: 0 }}>The default reading window is three days. You can make it longer in Settings.</p>
              <p style={{ margin: 0 }}>Missing recent days are held at maintenance and labeled as assumptions instead of copying today's intake.</p>
              <p style={{ margin: 0 }}>Projection copy is intentionally cautious. It is a rough calorie-balance estimate, not a measured weight forecast or a recommendation.</p>
            </div>
          </div>
        </section>

        <aside style={{ display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
          <div style={{
            background: 'var(--paper-2)',
            border: '1px solid var(--hair)',
            borderRadius: 14,
            padding: 18,
          }}>
            <SectionHead title="methods" right="v1" />
            <div style={{ marginTop: 12, display: 'grid', gap: 12 }}>
              <AboutMethod label="BMR" text="Mifflin-St Jeor by default; Katch-McArdle when body-fat percentage is provided." />
              <AboutMethod label="TDEE" text="BMR multiplied by activity level, unless you enter a manual maintenance override." />
              <AboutMethod label="storage trend" text="Average recent balance is classified as gaining, keeping, or losing." />
            </div>
          </div>

          <div style={{
            background: 'var(--paper-2)',
            border: '1px solid var(--hair)',
            borderRadius: 14,
            padding: 18,
          }}>
            <SectionHead title="your data" right="local" />
            <div style={{ marginTop: 12, display: 'grid', gap: 12 }}>
              <AboutMethod label="stored in browser" text="Your profile, settings, theme, and daily calorie totals are saved in this browser's local storage on this device." />
              <AboutMethod label="not synced" text="The current prototype has no account, backend, cloud sync, or analytics. Your CICO data is not shared across devices unless you export and import it yourself." />
              <AboutMethod label="persistence" text="Data stays across refreshes and visits in the same browser until you reset CICO, import different data, or clear this site's browser storage." />
            </div>
          </div>

          <div style={{
            background: 'var(--paper-2)',
            border: '1px solid var(--hair)',
            borderRadius: 14,
            padding: 18,
          }}>
            <SectionHead title="sources" />
            <div style={{ marginTop: 12, display: 'grid', gap: 9 }}>
              <AboutLink href="https://pubmed.ncbi.nlm.nih.gov/15883556/" label="Mifflin-St Jeor validation" />
              <AboutLink href="https://www.niddk.nih.gov/bwp" label="NIH Body Weight Planner" />
              <AboutLink href="https://arxiv.org/abs/0802.3234" label="Hall/Chow body-weight dynamics" />
            </div>
          </div>
        </aside>
      </div>
    </div>
  );
}

function AboutNote({ title, stat, body }) {
  return (
    <div style={{
      background: 'var(--paper-2)',
      border: '1px solid var(--hair)',
      borderRadius: 12,
      padding: 16,
      minWidth: 0,
    }}>
      <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{title}</div>
      <div className="mono num-tab" style={{ marginTop: 10, fontSize: 21, lineHeight: 1.1, color: 'var(--ink)' }}>{stat}</div>
      <div style={{ marginTop: 8, color: 'var(--ink-2)', fontSize: 12.5, lineHeight: 1.45 }}>{body}</div>
    </div>
  );
}

function AboutMethod({ label, text }) {
  return (
    <div style={{ borderTop: '1px solid var(--hair-faint)', paddingTop: 10 }}>
      <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{label}</div>
      <div style={{ marginTop: 5, color: 'var(--ink-2)', fontSize: 12.8, lineHeight: 1.45 }}>{text}</div>
    </div>
  );
}

function AboutLink({ href, label }) {
  return (
    <a href={href} target="_blank" rel="noreferrer" style={{
      color: 'var(--ink)',
      textDecoration: 'none',
      borderBottom: '1px solid var(--hair-strong)',
      fontSize: 13,
      lineHeight: 1.4,
    }}>
      {label}
    </a>
  );
}

function LegendDot({ color, label }) {
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
      <span style={{ width: 8, height: 8, borderRadius: 99, background: color, border: color === 'transparent' ? '1px dashed var(--hair-strong)' : 'none' }} />
      <span style={{ fontSize: 12, color: 'var(--ink-2)' }}>{label}</span>
    </span>
  );
}

function EmptyCalendarDay({ label, isToday, size = 82 }) {
  return (
    <div style={{
      width: size, height: size, borderRadius: 999,
      border: `1.5px ${isToday ? 'solid' : 'dashed'} var(--hair-strong)`,
      background: 'var(--paper-2)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      flexDirection: 'column', gap: 2,
      color: 'var(--ink-3)',
    }}>
      <span className="mono num-tab" style={{ fontSize: 16, color: 'var(--ink-2)' }}>{label}</span>
      <span className="mono" style={{ fontSize: 9, letterSpacing: '0.08em' }}>N/A</span>
    </div>
  );
}

function MiniSummary({ label, value, unit }) {
  return (
    <div>
      <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{label}</div>
      <div className="mono num-tab" style={{ marginTop: 5, fontSize: 22, color: 'var(--ink)' }}>{value}</div>
      <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{unit}</div>
    </div>
  );
}

function getZoneStreaks(days, tdee) {
  const streaks = {
    gaining: { count: 0, startLabel: '' },
    keeping: { count: 0, startLabel: '' },
    losing: { count: 0, startLabel: '' },
  };
  const current = {
    gaining: { count: 0, startLabel: '' },
    keeping: { count: 0, startLabel: '' },
    losing: { count: 0, startLabel: '' },
  };
  days.forEach((day) => {
    const dayZone = day.intakeSource === 'logged' && Number.isFinite(day.balance)
      ? classify(day.balance, tdee)
      : null;
    Object.keys(current).forEach((zone) => {
      if (dayZone === zone) {
        if (current[zone].count === 0) current[zone].startLabel = day.label || displayDate(day.date);
        current[zone].count += 1;
        if (current[zone].count > streaks[zone].count) {
          streaks[zone] = { ...current[zone] };
        }
      } else {
        current[zone] = { count: 0, startLabel: '' };
      }
    });
  });
  return streaks;
}

function ZoneStreakRow({ zone, streak }) {
  const color = ZONES[zone].hex;
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '58px 1fr', gap: 10, alignItems: 'baseline' }}>
      <span style={{
        fontFamily: "'Instrument Serif', serif", fontSize: 36, lineHeight: 1,
        fontStyle: 'italic', color,
      }}>{streak.count}</span>
      <div>
        <div style={{ fontSize: 13.5, color: 'var(--ink-2)' }}>{zone} days</div>
        <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 2 }}>
          {streak.count ? `started ${streak.startLabel.toLowerCase()}` : 'N/A'}
        </div>
      </div>
    </div>
  );
}

function WeeklyBars({ weeks, tdee = TDEE }) {
  // each bar: vertical, anchored at center line. up = gaining (rust), down = losing (moss).
  const H = 80;
  const max = Math.max(800, ...weeks.map((w) => Math.abs(w.avg || 0))) * 1.1;
  return (
    <div style={{ position: 'relative', height: H, display: 'flex', alignItems: 'stretch', gap: 14 }}>
      {/* center axis */}
      <div style={{ position: 'absolute', left: 0, right: 0, top: '50%', height: 1, background: 'var(--hair-strong)' }} />
      {weeks.map((w, i) => {
        if (w.avg == null) {
          return (
            <div key={i} style={{ flex: 1, position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
              <div className="mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>N/A</div>
              <div className="mono" style={{ position: 'absolute', bottom: -16, fontSize: 10, color: 'var(--ink-3)' }}>w{i + 1}</div>
            </div>
          );
        }
        const zone = classify(w.avg, tdee);
        const color = ZONES[zone].hex;
        const h = Math.abs(w.avg) / max * (H / 2);
        const top = w.avg >= 0 ? H / 2 - h : H / 2;
        return (
          <div key={i} style={{ flex: 1, position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-end' }}>
            <div style={{
              position: 'absolute', top, left: '50%', transform: 'translateX(-50%)',
              width: 18, height: h, background: color, borderRadius: 4,
            }} />
            <div className="mono" style={{ position: 'absolute', bottom: -16, fontSize: 10, color: 'var(--ink-3)' }}>w{i + 1}</div>
            <div className="mono num-tab" style={{
              position: 'absolute', top: w.avg >= 0 ? top - 14 : top + h + 2,
              fontSize: 10, color: 'var(--ink-2)',
            }}>{w.avg > 0 ? '+' : ''}{Math.round(w.avg)}</div>
          </div>
        );
      })}
    </div>
  );
}


// ═══════════════════════════════════════════════════════════════
// INSIGHTS TAB
// ═══════════════════════════════════════════════════════════════

function WebInsights({ scenario, tdee = TDEE, windowDays = 3, userData, unitSystem = 'metric' }) {
  const isNarrow = useIsNarrow();
  const days = scenario.days;
  const loggedDays = days.filter((day) => day.intakeSource === 'logged' && Number.isFinite(day.balance));
  const hasLoggedDays = loggedDays.length > 0;
  const reading = hasLoggedDays ? getBalanceReading(days, windowDays, tdee) : null;
  const zone = reading?.state || 'keeping';
  const avg = reading?.averageBalance ?? null;
  const accent = hasLoggedDays ? ZONES[zone].hex : 'var(--ink-3)';

  // 4-week projection based on current 3-day avg
  // weeklyKg = avg * 7 / 7700
  const weeklyKg = hasLoggedDays ? avg * 7 / 7700 : null;
  const weeklyDisplay = hasLoggedDays && unitSystem === 'imperial' ? weeklyKg * 2.2046226218 : weeklyKg;
  const proj4w = hasLoggedDays ? (weeklyDisplay * 4).toFixed(2) : 'N/A';
  const projSign = hasLoggedDays && weeklyKg >= 0 ? '+' : '';
  const startWeight = userData?.profile?.weightKg || PROFILE.weight;
  const startWeightDisplay = displayWeightValue(startWeight, unitSystem);
  const endWeight = hasLoggedDays ? displayWeightValue(startWeight + weeklyKg * 4, unitSystem).toFixed(1) : 'N/A';
  const weightLabel = weightUnit(unitSystem);
  const canShowProjection = loggedDays.length >= 7;
  const projectionLabel = loggedDays.length >= 14 ? 'trend forming' : 'limited estimate';

  const insightHistory = scenario.dailyTotalOnly
    ? loggedDays.map((day) => ({ ...day, dateObj: parseEntryDate(day) }))
    : HISTORY.map((day) => ({ ...day, dateObj: day.date }));

  // Day-of-week pattern from logged history where available.
  const dowBins = Array(7).fill(null).map(() => []);
  insightHistory.forEach((d) => {
    const dow = (d.dateObj.getDay() + 6) % 7; // Mon=0
    dowBins[dow].push(d.balance);
  });
  const dowAvg = dowBins.map((b) => b.reduce((s, x) => s + x, 0) / (b.length || 1));
  const hasPatternData = insightHistory.length >= 7;
  const loggedAvg = loggedDays.length ? loggedDays.reduce((sum, day) => sum + day.balance, 0) / loggedDays.length : null;
  const bestKeepingRun = getBestKeepingRun(days, tdee);
  const assumptionNote = reading?.assumed
    ? ` Missing days in the ${reading.days.length}-day reading are held at maintenance.`
    : '';

  return (
    <div style={{ height: isNarrow ? 'auto' : '100%', overflow: 'auto', padding: isNarrow ? '18px' : '20px 40px 28px', display: 'flex', flexDirection: 'column', gap: 18 }}>
      {/* Projection hero */}
      <div style={{
        background: 'var(--paper-2)', border: '1px solid var(--hair)',
        borderRadius: 16, padding: isNarrow ? '18px' : '24px 28px', display: 'grid',
        gridTemplateColumns: isNarrow ? '1fr' : '1.1fr 1fr', gap: 24,
      }}>
        <div>
          <SectionEyebrow left={canShowProjection ? `projection · ${projectionLabel}` : 'projection · collecting'} />
          <div style={{
            fontFamily: "'Instrument Serif', serif", fontSize: isNarrow ? 34 : 44, lineHeight: 1.08,
            color: 'var(--ink)', marginTop: 12,
          }}>
            {canShowProjection ? <>if this continued, <span style={{ color: accent, fontStyle: 'italic' }}>four weeks</span>{' '}
            could be around{' '}</> : <>projection is{' '}</>}
            <span className="mono num-tab" style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: isNarrow ? 34 : 44, color: accent, fontStyle: 'normal' }}>
              {canShowProjection ? endWeight : 'N/A'}
            </span>
            {canShowProjection && <span style={{ color: 'var(--ink-3)', fontSize: 24 }}> {weightLabel}</span>}.
          </div>
          <div style={{ marginTop: 10, fontSize: 13.5, color: 'var(--ink-2)', lineHeight: 1.5, maxWidth: 380 }}>
            {canShowProjection
              ? <>That implies about {projSign}{proj4w} {weightLabel} over four weeks from today's {startWeightDisplay} {weightLabel}, based on {loggedDays.length} logged days and assuming the recent pattern continued.
                This is a simple calorie-balance estimate, not a recommendation or measured weight forecast.{assumptionNote}</>
              : <>N/A until at least seven daily totals are logged. You have {loggedDays.length} logged {loggedDays.length === 1 ? 'day' : 'days'} so far, so the app avoids a future-weight estimate here.{assumptionNote}</>}
          </div>
        </div>
        <div>
          {canShowProjection
            ? <ProjectionChart startKg={startWeightDisplay} weeklyKg={weeklyDisplay} weeks={4} accent={accent} />
            : <InsufficientData label="N/A" detail="7 logged days needed" />}
        </div>
      </div>

      {/* insight cards */}
      <div style={{ display: 'grid', gridTemplateColumns: isNarrow ? '1fr' : 'repeat(3, 1fr)', gap: 14 }}>
        <InsightCard
          title="day of week"
          eyebrow={hasPatternData ? "patterns · logged days" : "patterns · collecting"}
        >
          {hasPatternData ? <DowChart dowAvg={dowAvg} tdee={tdee} /> : <InsufficientData label="N/A" detail="7 logged days needed" />}
        </InsightCard>

        <InsightCard
          title={scenario.dailyTotalOnly ? "daily totals" : "meal timing"}
          eyebrow={scenario.dailyTotalOnly ? "daily totals" : "demo meal clock"}
        >
          {scenario.dailyTotalOnly ? <DailyTotalSummary days={loggedDays} tdee={tdee} /> : <MealClock meals={TODAY_MEALS} />}
        </InsightCard>

        <InsightCard
          title="observations"
          eyebrow={hasPatternData ? "observations" : "limited data"}
        >
          <ObsList>
            <Obs hi={hasLoggedDays ? `${loggedDays.length}d` : 'N/A'} text="logged so far." color="var(--amber)" />
            <Obs hi={loggedAvg == null ? 'N/A' : `${loggedAvg > 0 ? '+' : ''}${Math.round(loggedAvg)}`} text="kcal/day average on logged days." color={accent} />
            <Obs hi={hasLoggedDays ? `${bestKeepingRun}d` : 'N/A'} text="best keeping run." color="var(--moss)" />
          </ObsList>
        </InsightCard>
      </div>
    </div>
  );
}

function getBestKeepingRun(days, tdee) {
  let best = 0;
  let current = 0;
  days.forEach((day) => {
    if (day.intakeSource === 'logged' && Number.isFinite(day.balance) && classify(day.balance, tdee) === 'keeping') {
      current++;
      best = Math.max(best, current);
    } else {
      current = 0;
    }
  });
  return best;
}

function ProjectionChart({ startKg, weeklyKg, weeks, accent }) {
  const W = 380, H = 200;
  const padL = 36, padR = 12, padT = 18, padB = 28;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  // y range ±2 kg from start
  const yMin = startKg - 2, yMax = startKg + 2;
  const yScale = (kg) => padT + (yMax - kg) / (yMax - yMin) * innerH;
  const xScale = (w) => padL + (w / weeks) * innerW;

  // historical line: last 4 weeks held flat-ish (just decorative based on weeklyKg / 3)
  const hist = [];
  for (let w = -4; w <= 0; w++) {
    hist.push({ w, kg: startKg - (weeklyKg / 3) * (w + 4) });
  }
  const proj = [];
  for (let w = 0; w <= weeks; w++) {
    proj.push({ w, kg: startKg + weeklyKg * w });
  }
  // Map historical x onto same scale (-4..0 → leftmost 40% of innerW)
  const histX = (w) => padL + ((w + 4) / 4) * (innerW * 0.42);
  const projX = (w) => padL + innerW * 0.42 + (w / weeks) * (innerW * 0.58);

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: 'block' }}>
      {/* axes */}
      <line x1={padL} x2={W - padR} y1={H - padB} y2={H - padB} stroke="var(--hair-strong)" />
      <line x1={padL} x2={W - padR} y1={yScale(startKg)} y2={yScale(startKg)} stroke="var(--hair-strong)" strokeDasharray="2 4" />
      {/* divider between past and projected */}
      <line x1={padL + innerW * 0.42} x2={padL + innerW * 0.42} y1={padT} y2={H - padB} stroke="var(--hair-strong)" strokeDasharray="2 4" />

      {/* y labels */}
      {[yMax, startKg, yMin].map((kg, i) => (
        <text key={i} x={padL - 6} y={yScale(kg) + 3} textAnchor="end" fontSize="9.5"
          fontFamily="JetBrains Mono, monospace" fill="var(--ink-3)">{kg.toFixed ? kg.toFixed(1) : kg}</text>
      ))}

      {/* x labels */}
      <text x={padL + innerW * 0.42} y={H - padB + 14} textAnchor="middle" fontSize="9.5"
        fontFamily="JetBrains Mono, monospace" fill="var(--ink-3)">today</text>
      <text x={padL + innerW * 0.42 + innerW * 0.58} y={H - padB + 14} textAnchor="end" fontSize="9.5"
        fontFamily="JetBrains Mono, monospace" fill="var(--ink-3)">+ 4 weeks</text>

      {/* historical (solid grey) */}
      <polyline
        points={hist.map((p) => `${histX(p.w)},${yScale(p.kg)}`).join(' ')}
        fill="none" stroke="var(--ink-2)" strokeWidth="1.6"
      />
      {/* projected (accent dashed → solid gradient) */}
      <polyline
        points={proj.map((p) => `${projX(p.w)},${yScale(p.kg)}`).join(' ')}
        fill="none" stroke={accent} strokeWidth="2.2" strokeDasharray="0"
      />
      {/* endpoint dot */}
      <circle cx={projX(weeks)} cy={yScale(proj[proj.length - 1].kg)} r="5" fill={accent} stroke="var(--paper-2)" strokeWidth="2" />
      <circle cx={projX(0)} cy={yScale(startKg)} r="4" fill="var(--ink)" />
    </svg>
  );
}

function InsightCard({ title, eyebrow, children }) {
  return (
    <div style={{
      background: 'var(--paper-2)', border: '1px solid var(--hair)',
      borderRadius: 14, padding: '18px 18px',
      display: 'flex', flexDirection: 'column', gap: 12,
      minHeight: 220,
    }}>
      <div>
        <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.10em', textTransform: 'uppercase' }}>{eyebrow}</div>
        <div style={{ fontFamily: "'Instrument Serif', serif", fontSize: 26, color: 'var(--ink)', lineHeight: 1, marginTop: 4 }}>{title}</div>
      </div>
      <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        {children}
      </div>
    </div>
  );
}

function DowChart({ dowAvg, tdee = TDEE }) {
  const labels = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
  const max = Math.max(...dowAvg.map(Math.abs)) * 1.15 || 500;
  const H = 110;
  return (
    <div style={{ width: '100%', position: 'relative', height: H, display: 'flex', alignItems: 'stretch', gap: 8 }}>
      <div style={{ position: 'absolute', left: 0, right: 0, top: '50%', height: 1, background: 'var(--hair-strong)' }} />
      {dowAvg.map((v, i) => {
        const zone = classify(v, tdee);
        const color = ZONES[zone].hex;
        const h = Math.abs(v) / max * (H / 2 - 8);
        const top = v >= 0 ? H / 2 - h : H / 2;
        return (
          <div key={i} style={{ flex: 1, position: 'relative' }}>
            <div style={{
              position: 'absolute', top, left: '50%', transform: 'translateX(-50%)',
              width: 16, height: h, background: color, borderRadius: 3,
            }} />
            <div className="mono" style={{
              position: 'absolute', bottom: -2, left: 0, right: 0, textAlign: 'center',
              fontSize: 10, color: 'var(--ink-3)',
            }}>{labels[i]}</div>
          </div>
        );
      })}
    </div>
  );
}

function EmptyDataView({ title, detail }) {
  return (
    <div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40 }}>
      <div style={{
        width: 'min(420px, 100%)', minHeight: 180,
        border: '1px dashed var(--hair-strong)', borderRadius: 14,
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        textAlign: 'center', gap: 8, color: 'var(--ink-3)',
      }}>
        <div className="mono" style={{ fontSize: 32, color: 'var(--ink)' }}>N/A</div>
        <div className="mono" style={{ fontSize: 10.5, letterSpacing: '0.10em', textTransform: 'uppercase' }}>{title}</div>
        <div style={{ fontSize: 13, color: 'var(--ink-2)' }}>{detail}</div>
      </div>
    </div>
  );
}

function InsufficientData({ label, detail }) {
  return (
    <div style={{
      width: '100%', height: 130,
      flexDirection: 'column', gap: 6,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      border: '1px dashed var(--hair-strong)', borderRadius: 12,
      color: 'var(--ink-3)',
    }}>
      <span className="mono" style={{ fontSize: 24, color: 'var(--ink-2)' }}>{label}</span>
      {detail && <span className="mono" style={{ fontSize: 10, letterSpacing: '0.06em', textTransform: 'uppercase' }}>{detail}</span>}
    </div>
  );
}

function DailyTotalSummary({ days, tdee }) {
  if (days.length === 0) {
    return <InsufficientData label="N/A" detail="No logged totals" />;
  }
  const max = Math.max(1, ...days.map((day) => Math.abs(day.balance)));
  return (
    <div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 8 }}>
      {days.map((day, index) => {
        const zone = classify(day.balance, tdee);
        const color = ZONES[zone].hex;
        const width = Math.max(8, Math.abs(day.balance) / max * 100);
        return (
          <div key={index} style={{ display: 'grid', gridTemplateColumns: '52px 1fr 58px', gap: 8, alignItems: 'center' }}>
            <span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{displayDate(day.date)}</span>
            <div style={{ height: 8, background: 'var(--hair-faint)', borderRadius: 99, overflow: 'hidden' }}>
              <div style={{ width: `${width}%`, height: '100%', background: color, borderRadius: 99 }} />
            </div>
            <span className="mono num-tab" style={{ fontSize: 10.5, color: 'var(--ink-2)', textAlign: 'right' }}>
              {day.balance > 0 ? '+' : ''}{day.balance}
            </span>
          </div>
        );
      })}
    </div>
  );
}

function MealClock({ meals }) {
  // 24h ring with dots at meal times
  const cx = 70, cy = 70, R = 56;
  return (
    <svg viewBox="0 0 140 140" width="140" height="140">
      <circle cx={cx} cy={cy} r={R} fill="none" stroke="var(--hair-strong)" strokeWidth="1.2" strokeDasharray="2 4" />
      {/* day/night halves */}
      <path d={`M ${cx} ${cy - R} A ${R} ${R} 0 0 1 ${cx} ${cy + R}`} fill="none" stroke="var(--amber)" strokeWidth="2" opacity="0.6" />
      <path d={`M ${cx} ${cy + R} A ${R} ${R} 0 0 1 ${cx} ${cy - R}`} fill="none" stroke="var(--ink-2)" strokeWidth="2" opacity="0.3" />
      {/* hour ticks */}
      {[0, 6, 12, 18].map((h) => {
        const a = (h / 24) * Math.PI * 2 - Math.PI / 2;
        const x1 = cx + Math.cos(a) * (R - 4), y1 = cy + Math.sin(a) * (R - 4);
        const x2 = cx + Math.cos(a) * (R + 4), y2 = cy + Math.sin(a) * (R + 4);
        const lx = cx + Math.cos(a) * (R + 14), ly = cy + Math.sin(a) * (R + 14);
        return (
          <React.Fragment key={h}>
            <line x1={x1} y1={y1} x2={x2} y2={y2} stroke="var(--ink-2)" strokeWidth="1.4" />
            <text x={lx} y={ly + 3} textAnchor="middle" fontSize="9" fontFamily="JetBrains Mono, monospace" fill="var(--ink-3)">{h}</text>
          </React.Fragment>
        );
      })}
      {meals.map((m, i) => {
        const [hh, mm] = m.time.split(':').map(Number);
        const t = (hh + mm / 60) / 24;
        const a = t * Math.PI * 2 - Math.PI / 2;
        const x = cx + Math.cos(a) * R, y = cy + Math.sin(a) * R;
        const r = Math.max(3, Math.sqrt(m.kcal) / 6);
        return (
          <circle key={i} cx={x} cy={y} r={r} fill="var(--rust)" stroke="var(--paper-2)" strokeWidth="1.4" />
        );
      })}
      <text x={cx} y={cy + 4} textAnchor="middle" fontSize="11" fontFamily="JetBrains Mono, monospace" fill="var(--ink-2)">{meals.length} meals</text>
    </svg>
  );
}

function ObsList({ children }) {
  return <div style={{ display: 'flex', flexDirection: 'column', gap: 10, width: '100%' }}>{children}</div>;
}

function Obs({ hi, text, color }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
      <span className="mono num-tab" style={{
        fontSize: 18, fontWeight: 500, color, minWidth: 46,
      }}>{hi}</span>
      <span style={{ fontSize: 12.5, color: 'var(--ink-2)', lineHeight: 1.4 }}>{text}</span>
    </div>
  );
}

function parseEntryDate(day) {
  if (day.dateObj instanceof Date) return day.dateObj;
  const parsed = new Date(day.date);
  if (!Number.isNaN(parsed.getTime())) return parsed;
  const fallback = new Date();
  fallback.setDate(day.d || fallback.getDate());
  return fallback;
}

Object.assign(window, { WebHistory, WebInsights, WebAbout });
