// Pregnancy nutrition tracker — main app
// Designed mobile-first, 17w pregnant, due Oct 23 2026.

const { useState, useEffect, useMemo, useRef } = React;
// Pull tweak helpers from window (loaded by tweaks-panel.jsx in a sibling script)
const {
  useTweaks, TweaksPanel, TweakSection,
  TweakSlider, TweakToggle, TweakRadio, TweakSelect,
  TweakColor, TweakButton,
} = window;

// ——————————————————————————————————————————————————————————————
// Utilities
// ——————————————————————————————————————————————————————————————
const DUE_DATE = new Date(2026, 9, 23); // Oct 23 2026
const PREG_START = new Date(DUE_DATE.getTime() - 280 * 24 * 60 * 60 * 1000);

function ymd(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${dd}`;
}
function fmtDate(d) {
  return d.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" });
}
function fmtShort(d) {
  return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
}
function gestWeeks(d) {
  const diff = (d - PREG_START) / (1000 * 60 * 60 * 24);
  const w = Math.floor(diff / 7);
  const days = Math.floor(diff - w * 7);
  return { weeks: w, days };
}
function trimester(d) {
  const { weeks } = gestWeeks(d);
  if (weeks < 13) return "T1";
  if (weeks < 27) return "T2";
  return "T3";
}
function daysUntilDue(d) {
  return Math.ceil((DUE_DATE - d) / (1000 * 60 * 60 * 24));
}

// ——————————————————————————————————————————————————————————————
// Time picker — resolves a "when" choice to a real Date
// ——————————————————————————————————————————————————————————————
const TIME_SLOTS = {
  earlyam:   { hour: 6,  min: 30, label: "Early AM",  sub: "5–8a" },
  morning:   { hour: 9,  min: 0,  label: "Morning",   sub: "8–11a" },
  midday:    { hour: 12, min: 30, label: "Midday",    sub: "11a–2p" },
  afternoon: { hour: 15, min: 30, label: "Afternoon", sub: "2–5p" },
  evening:   { hour: 18, min: 30, label: "Evening",   sub: "5–8p" },
  night:     { hour: 21, min: 0,  label: "Night",     sub: "8–11p" },
};

function resolveTime(viewDate, choice) {
  const now = new Date();
  if (choice === "now") return now;
  const m = /^ago(\d+)$/.exec(choice);
  if (m) return new Date(now.getTime() - parseInt(m[1], 10) * 60 * 1000);
  const slot = TIME_SLOTS[choice];
  if (slot) {
    return new Date(viewDate.getFullYear(), viewDate.getMonth(), viewDate.getDate(), slot.hour, slot.min);
  }
  return now;
}

// Pick a sensible default time choice given the current real time
function defaultTimeChoice(viewDate, isToday) {
  if (!isToday) {
    // Past or future day — choose slot closest to "midday"
    return "midday";
  }
  // Today → "now"
  return "now";
}

function TimePicker({ viewDate, value, onChange, isToday }) {
  const relOpts = isToday ? [
    { v: "now",    label: "Now" },
    { v: "ago30",  label: "<30m" },
    { v: "ago60",  label: "<1hr" },
    { v: "ago120", label: "<2hr" },
    { v: "ago180", label: "<3hr" },
    { v: "ago240", label: "<4hr" },
  ] : [];
  const slotOpts = Object.keys(TIME_SLOTS).map(k => ({ v: k, label: TIME_SLOTS[k].label }));

  const previewDate = resolveTime(viewDate, value);
  const previewStr = previewDate.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });

  return (
    <div className="tp">
      <div className="tp-head">
        <span className="tp-label">When</span>
        <span className="tp-time">{previewStr}</span>
      </div>
      {relOpts.length > 0 && (
        <div className="tp-row">
          {relOpts.map(o => (
            <button key={o.v} type="button" className={`tp-pill ${value === o.v ? "active" : ""}`} onClick={() => onChange(o.v)}>{o.label}</button>
          ))}
        </div>
      )}
      <div className="tp-row tp-slots">
        {slotOpts.map(o => (
          <button key={o.v} type="button" className={`tp-pill ${value === o.v ? "active" : ""}`} onClick={() => onChange(o.v)}>{o.label}</button>
        ))}
      </div>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Storage
// ——————————————————————————————————————————————————————————————
const STORAGE_KEY = "nutri_log_v1";
function loadLog() {
  try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}"); }
  catch { return {}; }
}
function saveLog(log) {
  try { localStorage.setItem(STORAGE_KEY, JSON.stringify(log)); } catch {}
}

// ——————————————————————————————————————————————————————————————
// Totals
// ——————————————————————————————————————————————————————————————
const NUTRIENT_KEYS = ["kcal", "protein", "folate", "iron", "calcium", "dha", "choline", "fiber", "vitd", "mg", "sugar", "water"];

function totalsForDay(entries) {
  const t = Object.fromEntries(NUTRIENT_KEYS.map(k => [k, 0]));
  if (!entries) return t;
  for (const e of entries) {
    if (e.kind && e.kind !== "food") continue;  // skip symptoms etc.
    const food = window.FOODS.find(f => f.id === e.foodId);
    if (!food) continue;
    const p = e.portions ?? 1;
    for (const k of NUTRIENT_KEYS) t[k] += (food[k] || 0) * p;
  }
  return t;
}

// ——————————————————————————————————————————————————————————————
// Ring
// ——————————————————————————————————————————————————————————————
function Ring({ pct, hue, size = 72, stroke = 6, children, style = "soft", kind }) {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const safePct = Math.min(pct, 1.25);
  const dash = c * safePct;
  const trackColor = `oklch(92% 0.012 ${hue})`;
  let fillColor;
  if (kind === "limit") {
    // Limit nutrient: ramp from gentle to red as you approach/exceed
    if (pct >= 1)        fillColor = `oklch(56% 0.18 25)`;   // over — strong red
    else if (pct >= 0.8) fillColor = `oklch(65% 0.14 40)`;   // close — amber
    else                 fillColor = `oklch(62% 0.10 ${hue})`;
  } else {
    fillColor = pct >= 1 ? `oklch(58% 0.13 ${hue})` : `oklch(62% 0.11 ${hue})`;
  }
  return (
    <div className="ring-wrap" style={{ width: size, height: size }}>
      <svg width={size} height={size} className={`ring ring-${style}`}>
        <circle cx={size/2} cy={size/2} r={r} stroke={trackColor} strokeWidth={stroke} fill="none" />
        <circle
          cx={size/2} cy={size/2} r={r}
          stroke={fillColor} strokeWidth={stroke} fill="none"
          strokeDasharray={`${dash} ${c}`}
          strokeLinecap={style === "soft" ? "round" : "butt"}
          transform={`rotate(-90 ${size/2} ${size/2})`}
          style={{ transition: "stroke-dasharray 600ms cubic-bezier(.2,.7,.2,1), stroke 240ms ease" }}
        />
      </svg>
      <div className="ring-inner">{children}</div>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Nutrient card
// ——————————————————————————————————————————————————————————————
function NutrientCard({ k, value, target, ringStyle }) {
  const meta = window.NUTRIENT_META[k];
  const pct = target > 0 ? value / target : 0;
  const isLimit = meta.kind === "limit";
  const displayVal = value >= 100 ? Math.round(value).toString()
                   : value >= 10 ? value.toFixed(0)
                   : value > 0 ? value.toFixed(1) : "0";
  const pctStr = Math.round(pct * 100);
  const stateClass = isLimit
    ? (pct >= 1 ? "over" : pct >= 0.8 ? "near" : "")
    : (pct >= 1 ? "met" : "");
  return (
    <div className={`ncard ${stateClass} ${isLimit ? "is-limit" : ""}`}>
      <Ring pct={pct} hue={meta.hue} size={72} stroke={6} style={ringStyle} kind={meta.kind}>
        <div className="ring-pct">{pctStr}<span className="ring-pct-pc">%</span></div>
      </Ring>
      <div className="ncard-body">
        <div className="ncard-label">
          {meta.label}
          {isLimit && <span className="ncard-cap"> · max</span>}
        </div>
        <div className="ncard-vals">
          <span className="ncard-val">{displayVal}</span>
          <span className="ncard-target"> / {target}<span className="ncard-unit"> {meta.unit}</span></span>
        </div>
      </div>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Today header
// ——————————————————————————————————————————————————————————————
function TodayHeader({ date, onPrev, onNext, isToday, onJumpToday, onOpenSettings, syncAttention }) {
  const { weeks, days } = gestWeeks(date);
  const dueIn = daysUntilDue(date);
  const tri = trimester(date);
  const triLabel = { T1: "First trimester", T2: "Second trimester", T3: "Third trimester" }[tri];
  return (
    <header className="hdr">
      <div className="hdr-top">
        <button className="iconbtn" onClick={onPrev} aria-label="Previous day">‹</button>
        <button className="hdr-date" onClick={onJumpToday}>
          <div className="hdr-date-main">{isToday ? "Today" : fmtShort(date)}</div>
          <div className="hdr-date-sub">{isToday ? fmtDate(date) : "tap to return to today"}</div>
        </button>
        <button className="iconbtn" onClick={onNext} aria-label="Next day">›</button>
        <button className={`iconbtn settings-btn ${syncAttention ? "has-attention" : ""}`} onClick={onOpenSettings} aria-label={syncAttention ? "Settings — sync needs attention" : "Settings"}>
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <circle cx="12" cy="12" r="3" />
            <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
          </svg>
        </button>
      </div>
      <div className="hdr-meta">
        <div className="meta-cell">
          <div className="meta-num">
            <em>{weeks}</em><span>w</span><em>{days}</em><span>d</span>
          </div>
          <div className="meta-lbl">{triLabel}</div>
        </div>
        <div className="meta-divider" />
        <div className="meta-cell">
          <div className="meta-num"><em>{Math.max(dueIn, 0)}</em><span>days</span></div>
          <div className="meta-lbl">until Oct 23</div>
        </div>
      </div>
    </header>
  );
}

// ——————————————————————————————————————————————————————————————
// Log row
// ——————————————————————————————————————————————————————————————
function LogRow({ entry, onDelete, onAdjust }) {
  const food = window.FOODS.find(f => f.id === entry.foodId);
  if (!food) return null;
  const time = new Date(entry.ts).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
  const p = entry.portions ?? 1;
  const kcal = Math.round(food.kcal * p);
  const protRaw = food.protein * p;
  const protein = protRaw < 10 ? protRaw.toFixed(1) : Math.round(protRaw).toString();
  return (
    <div className="log-row">
      <div className="log-time">{time}</div>
      <div className="log-mid">
        <div className="log-name">{food.name}</div>
        <div className="log-sub">
          <span>{p === 1 ? food.portion : `${fmtPortionMultiplier(p)} × ${food.portion}`}</span>
          <span className="log-sep">·</span>
          <span>{kcal} kcal</span>
          <span className="log-sep">·</span>
          <span>{protein}g prot</span>
        </div>
      </div>
      <div className="log-ctrls">
        <button className="log-portion" onClick={() => onAdjust(entry.id, Math.max(0.25, +(p - 0.5).toFixed(2)))} aria-label="Less">−</button>
        <button className="log-portion" onClick={() => onAdjust(entry.id, +(p + 0.5).toFixed(2))} aria-label="More">+</button>
        <button className="log-del" onClick={() => onDelete(entry.id)} aria-label="Remove">×</button>
      </div>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Portion guide
// ——————————————————————————————————————————————————————————————
const PORTION_GUIDE = [
  { hand: "Palm",        approx: "≈ 100 g cooked",     use: "Meat, fish, tofu" },
  { hand: "Fist",        approx: "≈ 1 cup",            use: "Grains, beans, cooked veg, yogurt" },
  { hand: "Handful",     approx: "≈ ¼–½ cup",          use: "Berries, nuts, dried fruit" },
  { hand: "Big handful", approx: "≈ 1 cup leaves",     use: "Raw salad greens" },
  { hand: "Thumb",       approx: "≈ 1 tbsp",           use: "Nut butter, oil, seeds, cheese" },
  { hand: "Thumb tip",   approx: "≈ 1 tsp",            use: "Butter, honey" },
  { hand: "Pinch",       approx: "a few shakes",       use: "Salt, spices" },
  { hand: "Glass",       approx: "≈ 240 mL (1 cup)",   use: "Water, milk, juice" },
  { hand: "Bottle",      approx: "≈ 500 mL",           use: "Water bottle" },
];

function PortionGuide({ onClose }) {
  return (
    <div className="guide" onClick={onClose}>
      <div className="guide-inner" onClick={e => e.stopPropagation()}>
        <div className="guide-head">
          <h3>Portion sizes</h3>
          <button className="guide-close" onClick={onClose}>×</button>
        </div>
        <div className="guide-rows">
          {PORTION_GUIDE.map(g => (
            <div key={g.hand} className="guide-row">
              <div className="guide-hand">{g.hand}</div>
              <div className="guide-mid">
                <div className="guide-approx">{g.approx}</div>
                <div className="guide-use">{g.use}</div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Add food sheet
// ——————————————————————————————————————————————————————————————
function AddSheet({ open, onClose, onAdd, viewDate, isToday }) {
  const [q, setQ] = useState("");
  const [activeCat, setActiveCat] = useState("All");
  const [staged, setStaged] = useState(null);
  const [showGuide, setShowGuide] = useState(false);

  useEffect(() => { if (!open) { setQ(""); setStaged(null); setActiveCat("All"); setShowGuide(false); } }, [open]);

  const cats = ["All", "Protein", "Dairy", "Grain", "Veg", "Fruit", "Nuts", "Drink", "Combo", "Supp"];
  const results = useMemo(() => {
    let r = window.FOODS;
    if (activeCat !== "All") r = r.filter(f => f.cat === activeCat);
    if (q.trim()) {
      const lq = q.toLowerCase();
      r = r.filter(f => f.name.toLowerCase().includes(lq) || f.portion.toLowerCase().includes(lq));
    }
    return r;
  }, [q, activeCat]);

  if (!open) return null;
  return (
    <div className="sheet-scrim" onClick={onClose}>
      <div className="sheet" onClick={e => e.stopPropagation()}>
        <div className="sheet-grip" />
        {staged ? (
          <PortionPicker
            food={staged.food}
            portions={staged.portions}
            onChange={p => setStaged({ ...staged, portions: p })}
            onBack={() => setStaged(null)}
            onConfirm={(ts) => { onAdd(staged.food, staged.portions, ts); onClose(); }}
            viewDate={viewDate}
            isToday={isToday}
          />
        ) : (
          <React.Fragment>
            <div className="sheet-head">
              <h2>Log food</h2>
              <div className="sheet-head-right">
                <button className="sheet-link" onClick={() => setShowGuide(true)}>What's a palm?</button>
                <button className="sheet-close" onClick={onClose}>Close</button>
              </div>
            </div>
            {showGuide && <PortionGuide onClose={() => setShowGuide(false)} />}
            <div className="search-wrap">
              <input
                className="search"
                placeholder="Search foods…"
                value={q}
                onChange={e => setQ(e.target.value)}
                autoFocus
              />
            </div>
            <div className="cats">
              {cats.map(c => (
                <button
                  key={c}
                  className={`cat ${activeCat === c ? "active" : ""}`}
                  onClick={() => setActiveCat(c)}
                >{c}</button>
              ))}
            </div>
            <div className="results">
              {results.length === 0 && <div className="empty">No matches.</div>}
              {results.map(f => (
                <button key={f.id} className="result" onClick={() => setStaged({ food: f, portions: 1 })}>
                  <div className="result-main">
                    <div className="result-name">{f.name}</div>
                    <div className="result-sub">{f.portion} · {f.kcal} kcal · {f.protein}g protein</div>
                  </div>
                  <div className="result-tag">{f.cat}</div>
                </button>
              ))}
            </div>
          </React.Fragment>
        )}
      </div>
    </div>
  );
}

function fmtPortionMultiplier(p) {
  // Pretty fraction labels
  const map = { 0.25: "¼", 0.5: "½", 0.75: "¾", 1: "1", 1.25: "1¼", 1.5: "1½", 1.75: "1¾", 2: "2", 2.5: "2½", 3: "3" };
  if (map[p]) return map[p];
  return p.toString();
}

function PortionPicker({ food, portions, onChange, onBack, onConfirm, viewDate, isToday }) {
  const [timeChoice, setTimeChoice] = useState(() => defaultTimeChoice(viewDate, isToday));
  const pSteps = [
    { v: 0.5, label: "½" },
    { v: 1,   label: "1" },
    { v: 1.5, label: "1½" },
    { v: 2,   label: "2" },
    { v: 3,   label: "3" },
  ];
  const macroKeys = ["kcal", "protein", "folate", "iron", "calcium", "dha", "choline", "fiber", "vitd", "mg", "sugar", "water"];
  const portionLabel = portions === 1
    ? food.portion
    : `${fmtPortionMultiplier(portions)} × (${food.portion})`;
  return (
    <div className="portion">
      <div className="sheet-head">
        <button className="sheet-back" onClick={onBack}>‹ Back</button>
        <button className="sheet-confirm" onClick={() => onConfirm(resolveTime(viewDate, timeChoice).getTime())}>Add to log</button>
      </div>
      <div className="portion-card">
        <div className="portion-name">{food.name}</div>
        <div className="portion-portion">{portionLabel}</div>
        <div className="portion-quick">
          {pSteps.map(s => (
            <button
              key={s.v}
              className={`pq ${portions === s.v ? "active" : ""}`}
              onClick={() => onChange(s.v)}
            >{s.label}</button>
          ))}
        </div>
        <div className="portion-fine">
          <button onClick={() => onChange(Math.max(0.25, +(portions - 0.25).toFixed(2)))}>−</button>
          <div className="portion-num">{fmtPortionMultiplier(portions)}</div>
          <button onClick={() => onChange(+(portions + 0.25).toFixed(2))}>+</button>
        </div>
        <div className="portion-hint">tap + / − for ¼ steps</div>
      </div>
      <TimePicker viewDate={viewDate} value={timeChoice} onChange={setTimeChoice} isToday={isToday} />
      <div className="portion-nutri">
        {macroKeys.filter(k => (food[k] || 0) > 0).map(k => {
          const meta = window.NUTRIENT_META[k];
          const v = food[k] * portions;
          const disp = v >= 100 ? Math.round(v) : v >= 10 ? v.toFixed(0) : v.toFixed(1);
          return (
            <div key={k} className="pn">
              <div className="pn-lbl">{meta.label}</div>
              <div className="pn-val">{disp}<span>{meta.unit}</span></div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Quick add bar
// ——————————————————————————————————————————————————————————————
function QuickAddBar({ onAdd }) {
  const items = window.QUICK_ADDS.map(id => window.FOODS.find(f => f.id === id)).filter(Boolean);
  return (
    <div className="quickbar">
      <div className="quickbar-label">Quick add</div>
      <div className="quickbar-row">
        {items.map(f => (
          <button key={f.id} className="quickchip" onClick={() => onAdd(f, 1)}>
            <span className="quickchip-name">{f.name}</span>
            <span className="quickchip-sub">{f.portion}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Highlights
// ——————————————————————————————————————————————————————————————
function Highlights({ totals, targets }) {
  // For "behind/ahead" — only consider nutrients you want to HIT (skip limits)
  const goalKeys = NUTRIENT_KEYS.filter(k => window.NUTRIENT_META[k]?.kind !== "limit");
  const limitKeys = NUTRIENT_KEYS.filter(k => window.NUTRIENT_META[k]?.kind === "limit");

  const ranked = goalKeys
    .map(k => ({ k, pct: targets[k] ? totals[k] / targets[k] : 0, meta: window.NUTRIENT_META[k] }))
    .filter(x => x.meta);
  const behind = [...ranked].sort((a,b) => a.pct - b.pct)[0];
  const ahead = [...ranked].sort((a,b) => b.pct - a.pct)[0];

  // Limit alert — if any limit is >= 80%, surface it instead of "Strong"
  const limitAlert = limitKeys
    .map(k => ({ k, pct: targets[k] ? totals[k] / targets[k] : 0, meta: window.NUTRIENT_META[k] }))
    .filter(x => x.pct >= 0.8)
    .sort((a,b) => b.pct - a.pct)[0];

  if (!behind) return null;
  const sugBehind = behind.pct < 0.4 ? "needs attention" : behind.pct < 0.85 ? "almost there" : "right on track";
  return (
    <div className="hl">
      <div className="hl-card hl-behind">
        <div className="hl-tag">Focus on</div>
        <div className="hl-val">{behind.meta.label}</div>
        <div className="hl-pct">{Math.round(behind.pct * 100)}% — {sugBehind}</div>
      </div>
      {limitAlert ? (
        <div className={`hl-card hl-limit ${limitAlert.pct >= 1 ? "over" : "near"}`}>
          <div className="hl-tag">{limitAlert.pct >= 1 ? "Over limit" : "Watch"}</div>
          <div className="hl-val">{limitAlert.meta.label}</div>
          <div className="hl-pct">{Math.round(limitAlert.pct * 100)}% of daily max</div>
        </div>
      ) : (
        <div className="hl-card hl-ahead">
          <div className="hl-tag">Strong</div>
          <div className="hl-val">{ahead.meta.label}</div>
          <div className="hl-pct">{Math.round(ahead.pct * 100)}%</div>
        </div>
      )}
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Symptom logging
// ——————————————————————————————————————————————————————————————
function SymptomBar({ entries, onAdd, viewDate, isToday }) {
  const [active, setActive] = useState(null);  // symptom obj being picked
  const countsBySym = useMemo(() => {
    const m = {};
    for (const e of entries) if (e.kind === "symptom") m[e.symptomId] = (m[e.symptomId] || 0) + 1;
    return m;
  }, [entries]);

  return (
    <section className="symptoms">
      <div className="quickbar-label">How you're feeling</div>
      <div className="symptom-row">
        {window.SYMPTOMS.map(s => {
          const c = countsBySym[s.id] || 0;
          return (
            <button
              key={s.id}
              className={`sym-chip sym-${s.kind} ${c > 0 ? "has-count" : ""}`}
              onClick={() => setActive(s)}
            >
              <span className="sym-chip-dot" />
              <span className="sym-chip-label">{s.label}</span>
              {c > 0 && <span className="sym-chip-count">{c}</span>}
            </button>
          );
        })}
      </div>
      {active && (
        <SymptomPicker
          symptom={active}
          viewDate={viewDate}
          isToday={isToday}
          onPick={(sev, ts) => { onAdd(active.id, sev, ts); setActive(null); }}
          onClose={() => setActive(null)}
        />
      )}
    </section>
  );
}

function SymptomPicker({ symptom, onPick, onClose, viewDate, isToday }) {
  const levels = window.SEVERITY[symptom.kind] || window.SEVERITY.neutral;
  const [timeChoice, setTimeChoice] = useState(() => defaultTimeChoice(viewDate, isToday));
  const [pendingSev, setPendingSev] = useState(null);
  const handlePick = () => {
    if (pendingSev == null) return;
    onPick(pendingSev, resolveTime(viewDate, timeChoice).getTime());
  };
  return (
    <div className="sym-scrim" onClick={onClose}>
      <div className="sym-pop" onClick={e => e.stopPropagation()}>
        <div className="sym-pop-title">{symptom.label}</div>
        <div className="sym-pop-sub">
          {symptom.kind === "positive" ? "How good was it?"
            : symptom.kind === "negative" ? "How intense?"
            : "How strong?"}
        </div>
        <div className="sym-pop-row">
          {levels.map(lv => (
            <button
              key={lv.value}
              className={`sym-pop-btn lvl-${lv.value} kind-${symptom.kind} ${pendingSev === lv.value ? "selected" : ""}`}
              onClick={() => setPendingSev(lv.value)}
            >{lv.label}</button>
          ))}
        </div>
        <TimePicker viewDate={viewDate} value={timeChoice} onChange={setTimeChoice} isToday={isToday} />
        <div className="sym-pop-actions">
          <button className="sym-pop-cancel" onClick={onClose}>Cancel</button>
          <button
            className={`sym-pop-confirm ${pendingSev == null ? "disabled" : ""}`}
            onClick={handlePick}
            disabled={pendingSev == null}
          >Log it</button>
        </div>
      </div>
    </div>
  );
}

function SymptomRow({ entry, onDelete }) {
  const sym = window.SYMPTOMS.find(s => s.id === entry.symptomId);
  if (!sym) return null;
  const time = new Date(entry.ts).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
  const levels = window.SEVERITY[sym.kind] || window.SEVERITY.neutral;
  const sevLabel = (levels.find(l => l.value === entry.severity) || levels[0]).label;
  return (
    <div className={`sym-row sym-row-${sym.kind}`}>
      <div className="log-time">{time}</div>
      <div className="sym-row-mid">
        <div className="sym-row-name">
          <span className={`sym-row-dot kind-${sym.kind}`} />
          {sym.label}
        </div>
        <div className="sym-row-sev">{sevLabel}</div>
      </div>
      <button className="log-del" onClick={() => onDelete(entry.id)} aria-label="Remove">×</button>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Weekly summary
// ——————————————————————————————————————————————————————————————
function severityLabel(kind, value) {
  const levels = window.SEVERITY[kind] || window.SEVERITY.neutral;
  return (levels.find(l => l.value === value) || levels[0]).label;
}

function buildSummaryText(days, dayData, avgPcts, symStats, topFoods, targets) {
  const range = `${days[0].toLocaleDateString("en-US", { month: "short", day: "numeric" })} – ${days[6].toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
  const daysLogged = dayData.filter(d => d.foods.length > 0).length;

  let s = "";
  s += `WEEKLY NUTRITION SUMMARY\n`;
  s += `Week of ${range}\n`;
  s += `Days logged: ${daysLogged} of 7\n\n`;

  s += `NUTRIENT AVERAGES (% of pregnancy target)\n`;
  for (const { k, pct, avg } of avgPcts) {
    const meta = window.NUTRIENT_META[k];
    const isLimit = meta.kind === "limit";
    const tar = targets[k];
    const pad = (meta.label + (isLimit ? " (max)" : "")).padEnd(17, " ");
    const mark = isLimit
      ? (pct >= 1 ? " ⚠ OVER" : pct >= 0.8 ? " ⚠ close" : "")
      : (pct >= 1 ? " ✓" : pct < 0.6 ? " ⚠" : "");
    s += `  ${pad} ${Math.round(pct * 100).toString().padStart(3)}%   (avg ${avg >= 100 ? Math.round(avg) : avg.toFixed(1)} / ${tar} ${meta.unit})${mark}\n`;
  }
  s += `\n`;

  const symEntries = Object.entries(symStats).sort((a,b) => b[1].count - a[1].count);
  if (symEntries.length > 0) {
    s += `SYMPTOMS THIS WEEK\n`;
    for (const [symId, stat] of symEntries) {
      const sym = window.SYMPTOMS.find(x => x.id === symId);
      if (!sym) continue;
      const sevCounts = { 1: 0, 2: 0, 3: 0 };
      stat.severities.forEach(v => { sevCounts[v] = (sevCounts[v] || 0) + 1; });
      const sevParts = [1,2,3]
        .filter(v => sevCounts[v] > 0)
        .map(v => `${sevCounts[v]} ${severityLabel(sym.kind, v).toLowerCase()}`)
        .join(", ");
      s += `  ${sym.label} (${stat.count}): ${sevParts}\n`;
    }
    s += `\n`;
  }

  if (topFoods.length > 0) {
    s += `MOST LOGGED\n`;
    for (const [fid, count] of topFoods) {
      const f = window.FOODS.find(x => x.id === fid);
      if (!f) continue;
      s += `  ${f.name} — ${count}×\n`;
    }
    s += `\n`;
  }

  s += `DAY BY DAY\n`;
  for (const d of dayData) {
    const dayLabel = d.date.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
    if (d.foods.length === 0 && d.symptoms.length === 0) {
      s += `  ${dayLabel}: —\n`;
      continue;
    }
    const metCount = NUTRIENT_KEYS.filter(k => {
      if (d.foods.length === 0) return false;
      const meta = window.NUTRIENT_META[k];
      const ratio = d.totals[k] / targets[k];
      return meta?.kind === "limit" ? ratio < 1 : ratio >= 1;
    }).length;
    const sympPart = d.symptoms.length > 0
      ? `, ${d.symptoms.length} symptom${d.symptoms.length === 1 ? "" : "s"}` : "";
    s += `  ${dayLabel}: ${d.foods.length} food${d.foods.length === 1 ? "" : "s"}${sympPart} — ${metCount}/${NUTRIENT_KEYS.length} nutrients met\n`;
  }

  return s;
}

function WeeklySummary({ log, onClose }) {
  const [copied, setCopied] = useState(false);
  const textRef = useRef(null);

  const days = useMemo(() => {
    const out = [];
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    for (let i = 6; i >= 0; i--) {
      const d = new Date(today);
      d.setDate(today.getDate() - i);
      out.push(d);
    }
    return out;
  }, []);

  const tri = trimester(new Date());
  const targets = window.NUTRIENT_TARGETS[tri];

  const dayData = days.map(d => {
    const key = ymd(d);
    const entries = log[key] || [];
    const foods = entries.filter(e => !e.kind || e.kind === "food");
    const symptoms = entries.filter(e => e.kind === "symptom");
    const totals = totalsForDay(entries);
    return { date: d, key, foods, symptoms, totals };
  });

  const daysLogged = dayData.filter(d => d.foods.length > 0).length;

  // Per-nutrient avg % across logged days (skip empty days so they don't drag avg)
  const avgPcts = NUTRIENT_KEYS.map(k => {
    const logged = dayData.filter(d => d.foods.length > 0);
    if (logged.length === 0) return { k, pct: 0, avg: 0 };
    const sumPct = logged.reduce((s, d) => s + (d.totals[k] / targets[k]), 0);
    const sumVal = logged.reduce((s, d) => s + d.totals[k], 0);
    return { k, pct: sumPct / logged.length, avg: sumVal / logged.length };
  });
  const sortedByPct = [...avgPcts].sort((a, b) => a.pct - b.pct);
  const weakest = sortedByPct[0];
  const strongest = sortedByPct[sortedByPct.length - 1];

  // Symptom stats
  const symStats = {};
  dayData.forEach(d => {
    d.symptoms.forEach(s => {
      if (!symStats[s.symptomId]) symStats[s.symptomId] = { count: 0, severities: [] };
      symStats[s.symptomId].count++;
      symStats[s.symptomId].severities.push(s.severity);
    });
  });
  const topSymptom = Object.entries(symStats).sort((a,b) => b[1].count - a[1].count)[0];

  // Top foods
  const foodCounts = {};
  dayData.forEach(d => d.foods.forEach(e => {
    foodCounts[e.foodId] = (foodCounts[e.foodId] || 0) + 1;
  }));
  const topFoods = Object.entries(foodCounts).sort((a,b) => b[1] - a[1]).slice(0, 5);

  const summaryText = buildSummaryText(days, dayData, avgPcts, symStats, topFoods, targets);

  function doCopy() {
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(summaryText)
        .then(() => { setCopied(true); setTimeout(() => setCopied(false), 2200); })
        .catch(() => fallbackCopy());
    } else {
      fallbackCopy();
    }
  }
  function fallbackCopy() {
    if (textRef.current) {
      textRef.current.style.display = "block";
      const range = document.createRange();
      range.selectNodeContents(textRef.current);
      const sel = window.getSelection();
      sel.removeAllRanges();
      sel.addRange(range);
      try { document.execCommand("copy"); setCopied(true); setTimeout(() => setCopied(false), 2200); }
      catch {}
    }
  }

  const rangeLabel = `${days[0].toLocaleDateString("en-US", { month: "short", day: "numeric" })} – ${days[6].toLocaleDateString("en-US", { month: "short", day: "numeric" })}`;

  return (
    <div className="ws-overlay">
      <header className="ws-head">
        <div>
          <div className="ws-eyebrow">Weekly summary</div>
          <div className="ws-range">{rangeLabel}</div>
        </div>
        <button className="ws-close" onClick={onClose}>×</button>
      </header>

      <section className="ws-bigstats">
        <div className="ws-stat">
          <div className="ws-stat-val">{daysLogged}<span>/7</span></div>
          <div className="ws-stat-lbl">days logged</div>
        </div>
        <div className="ws-stat">
          <div className="ws-stat-val">{topSymptom ? window.SYMPTOMS.find(s => s.id === topSymptom[0])?.label : "—"}</div>
          <div className="ws-stat-lbl">{topSymptom ? `top symptom (${topSymptom[1].count}×)` : "no symptoms"}</div>
        </div>
        <div className="ws-stat">
          <div className="ws-stat-val">{strongest && window.NUTRIENT_META[strongest.k]?.label}</div>
          <div className="ws-stat-lbl">strongest ({Math.round(strongest?.pct * 100 || 0)}%)</div>
        </div>
        <div className="ws-stat ws-stat-warn">
          <div className="ws-stat-val">{weakest && window.NUTRIENT_META[weakest.k]?.label}</div>
          <div className="ws-stat-lbl">focus on ({Math.round(weakest?.pct * 100 || 0)}%)</div>
        </div>
      </section>

      <section className="ws-section">
        <h3>Nutrient averages</h3>
        <div className="ws-bars">
          {avgPcts.map(({ k, pct, avg }) => {
            const meta = window.NUTRIENT_META[k];
            const isLimit = meta.kind === "limit";
            const widthPct = Math.min(pct * 100, 130);
            const displayed = avg >= 100 ? Math.round(avg) : avg.toFixed(1);
            const cls = isLimit
              ? (pct >= 1 ? "over" : pct >= 0.8 ? "near" : "")
              : (pct >= 1 ? "met" : pct < 0.6 ? "low" : "");
            const fillColor = isLimit && pct >= 1
              ? `oklch(56% 0.18 25)`
              : isLimit && pct >= 0.8
                ? `oklch(70% 0.14 40)`
                : `oklch(60% 0.11 ${meta.hue})`;
            return (
              <div key={k} className="ws-bar-row">
                <div className="ws-bar-lbl">{meta.label}{isLimit && <span className="ws-bar-cap"> max</span>}</div>
                <div className="ws-bar-track">
                  <div
                    className={`ws-bar-fill ${cls}`}
                    style={{ width: `${widthPct}%`, background: fillColor }}
                  />
                  <div className="ws-bar-100" />
                </div>
                <div className="ws-bar-val">
                  <span className="ws-bar-pct">{Math.round(pct * 100)}%</span>
                  <span className="ws-bar-num"> {displayed}<span>{meta.unit}</span></span>
                </div>
              </div>
            );
          })}
        </div>
      </section>

      {Object.keys(symStats).length > 0 && (
        <section className="ws-section">
          <h3>Symptoms this week</h3>
          <div className="ws-sym-list">
            {Object.entries(symStats).sort((a,b) => b[1].count - a[1].count).map(([symId, stat]) => {
              const sym = window.SYMPTOMS.find(s => s.id === symId);
              if (!sym) return null;
              const sevCounts = { 1: 0, 2: 0, 3: 0 };
              stat.severities.forEach(v => { sevCounts[v] = (sevCounts[v] || 0) + 1; });
              const parts = [1,2,3]
                .filter(v => sevCounts[v] > 0)
                .map(v => `${sevCounts[v]} ${severityLabel(sym.kind, v).toLowerCase()}`)
                .join(" · ");
              return (
                <div key={symId} className={`ws-sym ws-sym-${sym.kind}`}>
                  <div className="ws-sym-name">
                    <span className={`sym-row-dot kind-${sym.kind}`} />
                    {sym.label}
                  </div>
                  <div className="ws-sym-count">{stat.count}×</div>
                  <div className="ws-sym-sev">{parts}</div>
                </div>
              );
            })}
          </div>
        </section>
      )}

      <section className="ws-section">
        <h3>Day by day</h3>
        <div className="ws-days">
          {dayData.map(d => {
            const dayLabel = d.date.toLocaleDateString("en-US", { weekday: "short" });
            const dateLabel = d.date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
            const isEmpty = d.foods.length === 0 && d.symptoms.length === 0;
            const metCount = d.foods.length > 0
              ? NUTRIENT_KEYS.filter(k => {
                  const meta = window.NUTRIENT_META[k];
                  const ratio = d.totals[k] / targets[k];
                  return meta?.kind === "limit" ? ratio < 1 : ratio >= 1;
                }).length
              : 0;
            return (
              <div key={d.key} className={`ws-day ${isEmpty ? "empty" : ""}`}>
                <div className="ws-day-date">
                  <div className="ws-day-wk">{dayLabel}</div>
                  <div className="ws-day-d">{dateLabel}</div>
                </div>
                <div className="ws-day-body">
                  {isEmpty ? (
                    <div className="ws-day-empty">no entries</div>
                  ) : (
                    <React.Fragment>
                      <div className="ws-day-stats">
                        <span className="ws-day-pill">{d.foods.length} food{d.foods.length === 1 ? "" : "s"}</span>
                        {d.symptoms.length > 0 && (
                          <span className="ws-day-pill ws-day-pill-sym">
                            {d.symptoms.length} symptom{d.symptoms.length === 1 ? "" : "s"}
                          </span>
                        )}
                        <span className="ws-day-pill ws-day-pill-met">
                          {metCount}/{NUTRIENT_KEYS.length} met
                        </span>
                      </div>
                      <div className="ws-day-dots">
                        {NUTRIENT_KEYS.map(k => {
                          const pct = d.totals[k] / targets[k];
                          const meta = window.NUTRIENT_META[k];
                          const isLimit = meta?.kind === "limit";
                          let bg;
                          if (isLimit) {
                            bg = pct >= 1 ? `oklch(56% 0.18 25)`
                              : pct >= 0.8 ? `oklch(70% 0.14 40)`
                              : `color-mix(in oklab, oklch(60% 0.11 ${meta.hue}) 50%, var(--border-soft))`;
                          } else {
                            bg = pct >= 1
                              ? `oklch(60% 0.11 ${meta.hue})`
                              : pct >= 0.5
                                ? `color-mix(in oklab, oklch(60% 0.11 ${meta.hue}) ${Math.round(pct * 100)}%, var(--border-soft))`
                                : `var(--border-soft)`;
                          }
                          return (
                            <div
                              key={k}
                              className="ws-day-dot"
                              title={`${meta.label} ${Math.round(pct * 100)}%${isLimit ? " of max" : ""}`}
                              style={{ background: bg }}
                            />
                          );
                        })}
                      </div>
                    </React.Fragment>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      </section>

      {topFoods.length > 0 && (
        <section className="ws-section">
          <h3>Most logged foods</h3>
          <div className="ws-foods">
            {topFoods.map(([fid, count]) => {
              const f = window.FOODS.find(x => x.id === fid);
              if (!f) return null;
              return (
                <div key={fid} className="ws-food">
                  <div className="ws-food-name">{f.name}</div>
                  <div className="ws-food-count">{count}×</div>
                </div>
              );
            })}
          </div>
        </section>
      )}

      <section className="ws-section ws-copy-section">
        <h3>Share with your doctor</h3>
        <p className="ws-copy-hint">Plain-text summary you can paste into an email or message.</p>
        <button className={`ws-copy-btn ${copied ? "ok" : ""}`} onClick={doCopy}>
          {copied ? "✓ Copied to clipboard" : "Copy summary"}
        </button>
        <pre ref={textRef} className="ws-text">{summaryText}</pre>
      </section>

      <div className="ws-foot">
        <button className="ws-foot-close" onClick={onClose}>Done</button>
      </div>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Cloud sync (Cloudflare Pages Function at /api/log)
// ——————————————————————————————————————————————————————————————
const SYNC_ENABLED_KEY = "nutri_sync_enabled";

// Fetch can't resolve relative URLs in some sandboxed iframes (this design
// preview, for one). Skip sync entirely when we're not on a real http(s) origin.
function syncAvailable() {
  try {
    return location.protocol === "https:" || location.protocol === "http:";
  } catch { return false; }
}

function mergeLogs(local, remote) {
  const out = { ...local };
  for (const day of Object.keys(remote || {})) {
    const li = local[day] || [];
    const ri = remote[day] || [];
    const seen = new Set(li.map(e => e.id));
    const merged = [...li];
    for (const e of ri) if (!seen.has(e.id)) merged.push(e);
    out[day] = merged.sort((a, b) => (a.ts || 0) - (b.ts || 0));
  }
  return out;
}

function timeAgo(ts) {
  const diff = Date.now() - ts;
  if (diff < 30_000) return "just now";
  if (diff < 60_000) return "a few seconds ago";
  if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
  if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
  return `${Math.floor(diff / 86_400_000)}d ago`;
}

// ——————————————————————————————————————————————————————————————
// Diagnostic — actually probes /api/log and explains what's wrong
// ——————————————————————————————————————————————————————————————
async function runSyncDiagnostic() {
  const out = {
    ranAt: Date.now(),
    origin: location.origin,
    endpoint: location.origin + "/api/log",
    available: syncAvailable(),
    httpStatus: null,
    statusText: "",
    contentType: "",
    cfRay: "",
    server: "",
    durationMs: 0,
    bodyPreview: "",
    parsedJson: null,
    parsedError: null,
    networkError: null,
    diagnosis: "unknown",     // 'ok' | 'function_missing' | 'binding_missing' | 'query_failed' | 'write_failed' | 'http_error' | 'bad_json' | 'network_error' | 'sandboxed'
    headline: "",
    nextSteps: [],
  };

  if (!out.available) {
    out.diagnosis = "sandboxed";
    out.headline = "Not on a real http(s) origin — sync can't run here.";
    out.nextSteps = ["Open the deployed Cloudflare URL and run the diagnostic there."];
    return out;
  }

  const t0 = performance.now();
  let resp;
  try {
    resp = await fetch("/api/log", { headers: { "Accept": "application/json" }, cache: "no-store" });
  } catch (err) {
    out.networkError = err && err.message || String(err);
    out.durationMs = Math.round(performance.now() - t0);
    out.diagnosis = "network_error";
    out.headline = "Couldn't reach /api/log at all.";
    out.nextSteps = [
      "Check your internet connection.",
      "Confirm the Pages URL is actually loading (refresh).",
      `Raw error: ${out.networkError}`,
    ];
    return out;
  }
  out.durationMs = Math.round(performance.now() - t0);
  out.httpStatus = resp.status;
  out.statusText = resp.statusText || "";
  out.contentType = resp.headers.get("content-type") || "";
  out.cfRay = resp.headers.get("cf-ray") || "";
  out.server = resp.headers.get("server") || "";

  const text = await resp.text();
  out.bodyPreview = text.length > 600 ? text.slice(0, 600) + "…" : text;

  // CASE A — got HTML, not JSON → function isn't actually responding
  if (!out.contentType.includes("application/json")) {
    out.diagnosis = "function_missing";
    out.headline = "/api/log returned HTML instead of JSON — the Pages Function isn't handling this route.";
    out.nextSteps = [
      "In Cloudflare → your Pages project → latest deployment, scroll to the build summary. Confirm it says \"Functions: 1\" (or more). If it says 0, the functions/ folder didn't make it into the upload.",
      "Verify your upload has functions/api/log.js at the path functions/api/log.js relative to the project root (not nested inside another folder).",
      "If you uploaded recently, wait ~30s for propagation, then re-run this diagnostic.",
      out.cfRay ? `Server: ${out.server || "?"} · cf-ray: ${out.cfRay}` : `Server: ${out.server || "?"}`,
    ];
    return out;
  }

  // It IS JSON — parse it
  try { out.parsedJson = JSON.parse(text); } catch (e) {
    out.diagnosis = "bad_json";
    out.headline = `Got JSON content-type but body wasn't parseable: ${e.message}`;
    out.nextSteps = ["Check the body preview below for the raw response."];
    return out;
  }
  out.parsedError = out.parsedJson && out.parsedJson.error || null;

  // CASE B — function ran but env.DB undefined
  if (resp.status === 500 && out.parsedError === "DB_BINDING_MISSING") {
    out.diagnosis = "binding_missing";
    out.headline = "Function ran, but env.DB is undefined — your D1 database isn't bound to this deployment.";
    out.nextSteps = [
      "Cloudflare dashboard → Workers & Pages → your Pages project → Settings → Functions → D1 database bindings → Add binding.",
      "Variable name MUST be exactly DB (uppercase, no quotes, no spaces). Anything else and env.DB will be undefined.",
      "Pages has separate bindings for Production and Preview environments. Make sure the Production environment has the binding (or Preview, if you're testing on a preview URL).",
      "AFTER adding the binding you must redeploy — bindings only take effect on new deployments. Click \"Retry deployment\" on the latest deploy, or re-upload.",
      "Once redeployed, re-run this diagnostic.",
    ];
    return out;
  }

  // CASE C — binding exists but query fails
  if (resp.status === 500 && out.parsedError === "DB_QUERY_FAILED") {
    out.diagnosis = "query_failed";
    out.headline = "D1 binding is wired up, but a SELECT against it threw.";
    out.nextSteps = [
      "Open Cloudflare → D1 → your database → Console. Run: SELECT name FROM sqlite_master WHERE type='table'; — you should see 'logs'.",
      "If 'logs' is missing, the auto-create might be failing on permissions. Manually run: CREATE TABLE logs (token TEXT PRIMARY KEY, data TEXT NOT NULL, updated_at INTEGER NOT NULL);",
      "Confirm the bound database wasn't deleted/recreated under the same name (binding caches the DB ID).",
      out.parsedJson && out.parsedJson.detail ? `Server detail: ${out.parsedJson.detail}` : null,
    ].filter(Boolean);
    return out;
  }

  // CASE C2 — write failure
  if (resp.status === 500 && out.parsedError === "DB_WRITE_FAILED") {
    out.diagnosis = "write_failed";
    out.headline = "D1 binding works for reads, but the upsert failed.";
    out.nextSteps = [
      "Most often: the table exists but with a different schema. Drop and recreate: DROP TABLE logs; — the function will recreate it on next request.",
      out.parsedJson && out.parsedJson.detail ? `Server detail: ${out.parsedJson.detail}` : null,
    ].filter(Boolean);
    return out;
  }

  // Any other non-2xx
  if (!resp.ok) {
    out.diagnosis = "http_error";
    out.headline = `HTTP ${resp.status} ${out.statusText} from /api/log.`;
    out.nextSteps = [
      "Check the body preview below for a server-supplied hint.",
      "Look at the Cloudflare Pages → Functions → Logs tab for this request (filter by the cf-ray below).",
      out.cfRay ? `cf-ray: ${out.cfRay}` : null,
    ].filter(Boolean);
    return out;
  }

  // 2xx with JSON
  out.diagnosis = "ok";
  out.headline = "Sync endpoint is fully working. D1 is bound and responding.";
  const updated = out.parsedJson && out.parsedJson.updated_at;
  out.nextSteps = [
    updated ? `Last server write: ${new Date(updated).toLocaleString()}` : "Server has no data row yet — first push will create one.",
    out.cfRay ? `cf-ray: ${out.cfRay}` : null,
  ].filter(Boolean);
  return out;
}

function formatDiagnosticReport(d) {
  if (!d) return "";
  const lines = [];
  lines.push("NUTRITION TRACKER — SYNC DIAGNOSTIC");
  lines.push(`Ran: ${new Date(d.ranAt).toISOString()}`);
  lines.push(`Origin: ${d.origin}`);
  lines.push(`Endpoint: ${d.endpoint}`);
  lines.push(`Available (real http(s) origin): ${d.available}`);
  lines.push("");
  lines.push(`Diagnosis: ${d.diagnosis.toUpperCase()}`);
  lines.push(`Headline: ${d.headline}`);
  lines.push("");
  if (d.httpStatus != null) lines.push(`HTTP: ${d.httpStatus} ${d.statusText}`);
  if (d.contentType)        lines.push(`Content-Type: ${d.contentType}`);
  if (d.server)             lines.push(`Server: ${d.server}`);
  if (d.cfRay)              lines.push(`cf-ray: ${d.cfRay}`);
  lines.push(`Round-trip: ${d.durationMs}ms`);
  if (d.networkError)       lines.push(`Network error: ${d.networkError}`);
  if (d.parsedError)        lines.push(`Server error code: ${d.parsedError}`);
  lines.push("");
  lines.push("Body preview:");
  lines.push(d.bodyPreview || "(empty)");
  lines.push("");
  lines.push("Next steps:");
  d.nextSteps.forEach((s, i) => lines.push(`  ${i + 1}. ${s}`));
  return lines.join("\n");
}

function SyncDiagnostic({ syncStatus, syncErr, lastDiag, running, onRun }) {
  const [copied, setCopied] = useState(false);
  const [showRaw, setShowRaw] = useState(false);

  function copyReport() {
    const txt = formatDiagnosticReport(lastDiag);
    if (!txt) return;
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(txt)
        .then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); })
        .catch(() => {});
    }
  }

  const diagCls = lastDiag ? `diag diag-${lastDiag.diagnosis}` : "diag";

  return (
    <div className="diag-wrap">
      <div className="diag-btn-row">
        <button className="diag-btn" onClick={onRun} disabled={running}>
          {running ? "Probing /api/log…" : (lastDiag ? "Re-run diagnostic" : "Diagnose sync")}
        </button>
        {lastDiag && (
          <button className={`diag-btn diag-btn-ghost ${copied ? "ok" : ""}`} onClick={copyReport}>
            {copied ? "✓ Copied" : "Copy report"}
          </button>
        )}
      </div>

      {lastDiag && (
        <div className={diagCls}>
          <div className="diag-head">
            <span className={`diag-pill diag-pill-${lastDiag.diagnosis}`}>{lastDiag.diagnosis.replace(/_/g, " ")}</span>
            {lastDiag.httpStatus != null && (
              <span className="diag-meta">HTTP {lastDiag.httpStatus} · {lastDiag.durationMs}ms</span>
            )}
          </div>
          <div className="diag-headline">{lastDiag.headline}</div>

          {lastDiag.nextSteps.length > 0 && (
            <ol className="diag-steps">
              {lastDiag.nextSteps.map((s, i) => <li key={i}>{s}</li>)}
            </ol>
          )}

          <div className="diag-facts">
            <div><span>Endpoint</span><code>{lastDiag.endpoint}</code></div>
            {lastDiag.contentType && <div><span>Content-Type</span><code>{lastDiag.contentType}</code></div>}
            {lastDiag.cfRay && <div><span>cf-ray</span><code>{lastDiag.cfRay}</code></div>}
            {lastDiag.parsedError && <div><span>Error code</span><code>{lastDiag.parsedError}</code></div>}
          </div>

          <button className="diag-raw-toggle" onClick={() => setShowRaw(s => !s)}>
            {showRaw ? "Hide raw response" : "Show raw response"}
          </button>
          {showRaw && (
            <pre className="diag-raw">{lastDiag.bodyPreview || "(empty)"}</pre>
          )}
        </div>
      )}

      {!lastDiag && (
        <p className="set-hint diag-hint">
          Probes <code>/api/log</code> directly and tells you exactly which Cloudflare-side thing
          (functions folder, binding name, environment scope, redeploy) is the actual blocker.
        </p>
      )}
    </div>
  );
}

function SyncControl({ syncEnabled, syncStatus, syncErr, lastSync, available, onToggle, onSyncNow, diagProps }) {
  const statusLabel = !available ? "Not available"
    : !syncEnabled ? "Off"
    : syncStatus === "syncing" ? "Syncing…"
    : syncStatus === "synced" ? "Connected"
    : syncStatus === "error" ? "Error"
    : syncStatus === "unsupported" ? "Cloudflare D1 not set up"
    : "Idle";
  const lastSyncStr = lastSync ? timeAgo(lastSync) : null;

  return (
    <div className="sync-ctrl">
      <div className={`sync-status ${available ? syncStatus : "unavailable"} ${syncEnabled ? "" : "off"}`}>
        <span className="sync-dot" /> {statusLabel}
        {syncEnabled && lastSyncStr && syncStatus === "synced" && (
          <span className="sync-ago"> · last sync {lastSyncStr}</span>
        )}
      </div>

      {!available && (
        <div className="sync-hint">
          Sync only works on the deployed site. Once your site is live on
          Cloudflare and you've bound a D1 database, this turns on automatically.
        </div>
      )}

      {available && syncStatus === "unsupported" && (
        <div className="sync-hint">
          Your Cloudflare D1 database isn't bound yet. In the Cloudflare
          dashboard: <b>Pages project → Settings → Functions → D1 database
          bindings → add binding</b> (variable name <code>DB</code>), then
          redeploy. The app will auto-sync after that.
        </div>
      )}

      {syncErr && available && syncStatus !== "unsupported" && (
        <div className="sync-err">{syncErr}</div>
      )}

      <label className="set-toggle">
        <input
          type="checkbox"
          checked={!!syncEnabled}
          disabled={!available}
          onChange={e => onToggle(e.target.checked)}
        />
        <span>Sync to Cloudflare automatically</span>
      </label>

      {syncEnabled && available && syncStatus !== "unsupported" && (
        <div className="sync-actions">
          <button className="sync-btn" onClick={onSyncNow}>Sync now</button>
        </div>
      )}

      {available && diagProps && (
        <SyncDiagnostic {...diagProps} syncStatus={syncStatus} syncErr={syncErr} />
      )}
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Inline sync banner — surfaces unsupported / error states on the main screen
// ——————————————————————————————————————————————————————————————
const SYNC_BANNER_DISMISSED_KEY = "nutri_sync_banner_dismissed_v1";

function SyncBanner({ syncStatus, syncErr, onOpenSettings }) {
  const [dismissed, setDismissed] = useState(() => {
    try { return localStorage.getItem(SYNC_BANNER_DISMISSED_KEY) || ""; }
    catch { return ""; }
  });

  // Only show for states that need user action
  const active = syncStatus === "unsupported" || syncStatus === "error";
  // Reset dismissal whenever the *kind* of problem changes
  useEffect(() => {
    if (!active) return;
    if (dismissed && dismissed !== syncStatus) {
      try { localStorage.removeItem(SYNC_BANNER_DISMISSED_KEY); } catch {}
      setDismissed("");
    }
  }, [syncStatus, active, dismissed]);

  if (!active) return null;
  if (dismissed === syncStatus) return null;

  const isUnsupported = syncStatus === "unsupported";
  const title = isUnsupported ? "Cloud sync isn't connecting" : "Cloud sync hit an error";
  const body = isUnsupported
    ? "Could be the binding, the deploy, a missing functions folder, or something else. Tap to run a diagnostic that tells you exactly which one."
    : (syncErr || "Couldn't reach the sync endpoint. Tap to diagnose.");

  function dismiss(e) {
    e.stopPropagation();
    try { localStorage.setItem(SYNC_BANNER_DISMISSED_KEY, syncStatus); } catch {}
    setDismissed(syncStatus);
  }

  return (
    <div
      className={`sync-banner sync-banner-${isUnsupported ? "setup" : "error"}`}
      role="button"
      tabIndex={0}
      onClick={onOpenSettings}
      onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onOpenSettings(); } }}
    >
      <div className="sync-banner-icon" aria-hidden="true">
        {isUnsupported ? (
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" />
            <line x1="12" y1="12" x2="12" y2="16" />
            <line x1="12" y1="18" x2="12" y2="18" />
          </svg>
        ) : (
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <circle cx="12" cy="12" r="10" />
            <line x1="12" y1="8" x2="12" y2="13" />
            <line x1="12" y1="16" x2="12" y2="16" />
          </svg>
        )}
      </div>
      <div className="sync-banner-body">
        <div className="sync-banner-title">{title}</div>
        <div className="sync-banner-sub">{body}</div>
      </div>
      <div className="sync-banner-cta">Open Settings ›</div>
      <button className="sync-banner-x" onClick={dismiss} aria-label="Dismiss">×</button>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Settings sheet (user-facing, always accessible in deployed app)
// ——————————————————————————————————————————————————————————————
function SettingsSheet({ open, onClose, tweaks, setTweak, log, setLog, syncProps }) {
  if (!open) return null;
  const palettes = [
    { id: "cream", colors: ["#f7f3ea", "#1f1d1a", "#6f8b6a"], label: "Cream" },
    { id: "sage",  colors: ["#eef0ea", "#191c19", "#4f7563"], label: "Sage" },
    { id: "clay",  colors: ["#f5ecea", "#1c1717", "#a36254"], label: "Clay" },
    { id: "ink",   colors: ["#0e0e10", "#f5f5f4", "#c8b27a"], label: "Ink" },
  ];
  const activePaletteIdx = palettes.findIndex(p =>
    p.colors[0] === (tweaks.palette || [])[0] && p.colors[1] === (tweaks.palette || [])[1]
  );

  function exportData() {
    const payload = { exportedAt: new Date().toISOString(), appVersion: "nutri-1", log };
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `nutrition-backup-${new Date().toISOString().slice(0, 10)}.json`;
    document.body.appendChild(a);
    a.click();
    setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 0);
  }
  function importData() {
    const input = document.createElement("input");
    input.type = "file";
    input.accept = "application/json,.json";
    input.onchange = (e) => {
      const file = e.target.files && e.target.files[0];
      if (!file) return;
      const reader = new FileReader();
      reader.onload = () => {
        try {
          const parsed = JSON.parse(reader.result);
          const restored = parsed && parsed.log ? parsed.log : parsed;
          if (!restored || typeof restored !== "object") throw new Error("bad shape");
          const merge = window.confirm("Merge with current log? Click Cancel to REPLACE current log instead.");
          setLog(prev => {
            if (merge) {
              const out = { ...prev };
              for (const day of Object.keys(restored)) {
                const existing = out[day] || [];
                const ids = new Set(existing.map(e => e.id));
                const incoming = (restored[day] || []).filter(e => !ids.has(e.id));
                out[day] = [...existing, ...incoming];
              }
              return out;
            }
            return restored;
          });
          alert("Import complete.");
        } catch (err) { alert("Couldn't read that file: " + err.message); }
      };
      reader.readAsText(file);
    };
    input.click();
  }

  return (
    <div className="sheet-scrim" onClick={onClose}>
      <div className="sheet settings-sheet" onClick={e => e.stopPropagation()}>
        <div className="sheet-grip" />
        <div className="sheet-head">
          <h2>Settings</h2>
          <button className="sheet-close" onClick={onClose}>Done</button>
        </div>

        <div className="settings-scroll">
          <div className="set-section">
            <h3>Cloud sync</h3>
            <SyncControl {...syncProps} />
          </div>

          <div className="set-section">
            <h3>Backup</h3>
            <p className="set-hint">Download a JSON file you can email to yourself, or restore from one later. Recommended even if you're using sync.</p>
            <div className="set-btn-row">
              <button className="set-btn" onClick={exportData}>Export all data</button>
              <button className="set-btn" onClick={importData}>Import from file</button>
            </div>
          </div>

          <div className="set-section">
            <h3>Trimester</h3>
            <p className="set-hint">Targets auto-adjust by gestational week. Override if your doctor uses different numbers.</p>
            <div className="set-radio-row">
              {[
                { v: "auto", label: "Auto" },
                { v: "T1", label: "1st" },
                { v: "T2", label: "2nd" },
                { v: "T3", label: "3rd" },
              ].map(o => (
                <button
                  key={o.v}
                  className={`set-radio ${tweaks.trimesterOverride === o.v ? "active" : ""}`}
                  onClick={() => setTweak("trimesterOverride", o.v)}
                >{o.label}</button>
              ))}
            </div>
          </div>

          <div className="set-section">
            <h3>Appearance</h3>
            <div className="set-sub">Theme</div>
            <div className="set-palettes">
              {palettes.map((p, i) => (
                <button
                  key={p.id}
                  className={`set-palette ${activePaletteIdx === i ? "active" : ""}`}
                  onClick={() => setTweak("palette", p.colors)}
                  aria-label={p.label}
                >
                  <span className="set-swatch" style={{ background: p.colors[0] }} />
                  <span className="set-swatch" style={{ background: p.colors[1] }} />
                  <span className="set-swatch" style={{ background: p.colors[2] }} />
                  <span className="set-palette-lbl">{p.label}</span>
                </button>
              ))}
            </div>
            <div className="set-sub" style={{ marginTop: 14 }}>Ring style</div>
            <div className="set-radio-row">
              <button className={`set-radio ${tweaks.ringStyle === "soft" ? "active" : ""}`} onClick={() => setTweak("ringStyle", "soft")}>Soft</button>
              <button className={`set-radio ${tweaks.ringStyle === "sharp" ? "active" : ""}`} onClick={() => setTweak("ringStyle", "sharp")}>Sharp</button>
            </div>
            <div className="set-toggle-row">
              <label className="set-toggle">
                <input type="checkbox" checked={!!tweaks.compactRings} onChange={e => setTweak("compactRings", e.target.checked)} />
                <span>Compact ring layout (3 columns)</span>
              </label>
              <label className="set-toggle">
                <input type="checkbox" checked={!!tweaks.showHighlights} onChange={e => setTweak("showHighlights", e.target.checked)} />
                <span>Show focus / strong cards</span>
              </label>
            </div>
          </div>

          <div className="set-section set-section-danger">
            <h3>Danger zone</h3>
            <button
              className="set-btn danger"
              onClick={() => {
                if (window.confirm("Clear ALL logged data on this device? This cannot be undone (unless you have cloud sync or a backup).")) {
                  setLog({});
                }
              }}
            >Clear all data</button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ——————————————————————————————————————————————————————————————
// Settings persistence — localStorage-backed (works in deployed app)
// Mirrors the useTweaks API so the same state shape works.
// ——————————————————————————————————————————————————————————————
const SETTINGS_KEY = "nutri_settings_v1";
function useSettings(defaults) {
  const [values, setValues] = useState(() => {
    try {
      const stored = JSON.parse(localStorage.getItem(SETTINGS_KEY) || "{}");
      return { ...defaults, ...stored };
    } catch { return defaults; }
  });
  const setSetting = React.useCallback((keyOrEdits, val) => {
    const edits = (typeof keyOrEdits === "object" && keyOrEdits !== null)
      ? keyOrEdits : { [keyOrEdits]: val };
    setValues(prev => {
      const next = { ...prev, ...edits };
      try { localStorage.setItem(SETTINGS_KEY, JSON.stringify(next)); } catch {}
      // Notify dev host for hot-reload (no-op when deployed)
      try { window.parent.postMessage({ type: "__edit_mode_set_keys", edits }, "*"); } catch {}
      return next;
    });
  }, []);
  return [values, setSetting];
}

// ——————————————————————————————————————————————————————————————
// App
// ——————————————————————————————————————————————————————————————
const DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": ["#f7f3ea", "#1f1d1a", "#6f8b6a"],
  "ringStyle": "soft",
  "showHighlights": true,
  "compactRings": false,
  "trimesterOverride": "auto"
}/*EDITMODE-END*/;

function App() {
  const [tweaks, setTweak] = useSettings(DEFAULTS);
  const [settingsOpen, setSettingsOpen] = useState(false);
  const today = useMemo(() => {
    const d = new Date();
    return new Date(d.getFullYear(), d.getMonth(), d.getDate());
  }, []);
  const [date, setDate] = useState(today);
  const [log, setLog] = useState(() => loadLog());
  const [sheetOpen, setSheetOpen] = useState(false);
  const [weekOpen, setWeekOpen] = useState(false);

  // ── Sync state ─────────────────────────────────────────────────
  const available = useMemo(() => syncAvailable(), []);
  const [syncEnabled, setSyncEnabledState] = useState(() => {
    try {
      const stored = localStorage.getItem(SYNC_ENABLED_KEY);
      return stored == null ? true : stored === "1";  // default on
    } catch { return true; }
  });
  const [syncStatus, setSyncStatus] = useState("idle");
  const [syncErr, setSyncErr] = useState(null);
  const [lastSync, setLastSync] = useState(null);
  const [lastDiag, setLastDiag] = useState(null);
  const [diagRunning, setDiagRunning] = useState(false);
  const lastSyncedJsonRef = useRef("");
  const initialPullDoneRef = useRef(false);

  async function runDiagnostic() {
    setDiagRunning(true);
    try {
      const result = await runSyncDiagnostic();
      setLastDiag(result);
    } finally {
      setDiagRunning(false);
    }
  }

  function setSyncEnabled(on) {
    try { localStorage.setItem(SYNC_ENABLED_KEY, on ? "1" : "0"); } catch {}
    setSyncEnabledState(on);
    setSyncErr(null);
    if (!on) setSyncStatus("idle");
    initialPullDoneRef.current = false;
    lastSyncedJsonRef.current = "";
  }

  async function doPull() {
    if (!available || !syncEnabled) return;
    setSyncStatus("syncing");
    try {
      const r = await fetch("/api/log", { headers: { "Accept": "application/json" } });
      const ct = r.headers.get("content-type") || "";
      if (!ct.includes("application/json")) {
        // Got HTML/text — function not actually deployed (Pages SPA fallback served index)
        setSyncStatus("unsupported");
        setSyncErr("The /api/log function isn't deployed. Make sure the 'functions' folder is at the root of your upload.");
        return;
      }
      const payload = await r.json();
      if (!r.ok) {
        if (payload && payload.error === "DB_BINDING_MISSING") {
          setSyncStatus("unsupported");
          setSyncErr(null);
          return;
        }
        // Surface the actual server message
        const msg = (payload && (payload.detail || payload.hint || payload.error)) || `HTTP ${r.status}`;
        setSyncStatus("error");
        setSyncErr(msg);
        return;
      }
      const data = payload && payload.data;
      if (data && typeof data === "object") {
        setLog(local => mergeLogs(local, data));
      }
      setSyncStatus("synced");
      setLastSync(Date.now());
      setSyncErr(null);
      initialPullDoneRef.current = true;
    } catch (err) {
      setSyncStatus("error");
      setSyncErr(err && err.message ? err.message : "pull failed");
    }
  }

  async function doPush(logToPush) {
    if (!available || !syncEnabled) return;
    const body = JSON.stringify(logToPush);
    if (body === lastSyncedJsonRef.current) return;
    setSyncStatus("syncing");
    try {
      const r = await fetch("/api/log", {
        method: "POST",
        headers: { "Content-Type": "application/json", "Accept": "application/json" },
        body: JSON.stringify({ data: logToPush }),
      });
      const ct = r.headers.get("content-type") || "";
      if (!ct.includes("application/json")) {
        setSyncStatus("unsupported");
        setSyncErr("The /api/log function isn't deployed.");
        return;
      }
      const payload = await r.json();
      if (!r.ok) {
        if (payload && payload.error === "DB_BINDING_MISSING") {
          setSyncStatus("unsupported");
          setSyncErr(null);
          return;
        }
        const msg = (payload && (payload.detail || payload.hint || payload.error)) || `HTTP ${r.status}`;
        setSyncStatus("error");
        setSyncErr(msg);
        return;
      }
      lastSyncedJsonRef.current = body;
      setSyncStatus("synced");
      setLastSync(Date.now());
      setSyncErr(null);
    } catch (err) {
      setSyncStatus("error");
      setSyncErr(err && err.message ? err.message : "push failed");
    }
  }

  // Pull on mount / when enabled
  useEffect(() => {
    if (!available || !syncEnabled) return;
    doPull();
  }, [syncEnabled, available]);

  // Push on log change (debounced, only after initial pull)
  useEffect(() => {
    if (!available || !syncEnabled) return;
    if (!initialPullDoneRef.current) return;
    const h = setTimeout(() => doPush(log), 1500);
    return () => clearTimeout(h);
  }, [log, syncEnabled, available]);

  // Auto-run diagnostic the first time sync goes unhealthy, so the report is
  // already there when the user opens Settings.
  useEffect(() => {
    if (!available || !syncEnabled) return;
    if (syncStatus !== "unsupported" && syncStatus !== "error") return;
    if (lastDiag) return;
    runDiagnostic();
  }, [syncStatus, available, syncEnabled]);

  useEffect(() => { saveLog(log); }, [log]);

  // Apply theme palette to CSS vars
  useEffect(() => {
    const [bg, fg, accent] = tweaks.palette || ["#f7f3ea", "#1f1d1a", "#6f8b6a"];
    const root = document.documentElement;
    root.style.setProperty("--bg", bg);
    root.style.setProperty("--fg", fg);
    root.style.setProperty("--accent", accent);
  }, [tweaks.palette]);

  const dayKey = ymd(date);
  const entries = log[dayKey] || [];
  const totals = useMemo(() => totalsForDay(entries), [entries]);
  const triKey = tweaks.trimesterOverride === "auto" ? trimester(date) : tweaks.trimesterOverride;
  const targets = window.NUTRIENT_TARGETS[triKey];

  function addFood(food, portions = 1, ts) {
    const id = Math.random().toString(36).slice(2, 10);
    const finalTs = ts || Date.now();
    setLog(prev => {
      const next = { ...prev };
      next[dayKey] = [...(next[dayKey] || []), { id, kind: "food", foodId: food.id, portions, ts: finalTs }];
      return next;
    });
  }
  function addSymptom(symptomId, severity, ts) {
    const id = Math.random().toString(36).slice(2, 10);
    const finalTs = ts || Date.now();
    setLog(prev => {
      const next = { ...prev };
      next[dayKey] = [...(next[dayKey] || []), { id, kind: "symptom", symptomId, severity, ts: finalTs }];
      return next;
    });
  }
  function deleteEntry(id) {
    setLog(prev => {
      const next = { ...prev };
      next[dayKey] = (next[dayKey] || []).filter(e => e.id !== id);
      return next;
    });
  }
  function adjustPortion(id, portions) {
    setLog(prev => {
      const next = { ...prev };
      next[dayKey] = (next[dayKey] || []).map(e => e.id === id ? { ...e, portions } : e);
      return next;
    });
  }
  function shiftDay(days) {
    setDate(d => new Date(d.getFullYear(), d.getMonth(), d.getDate() + days));
  }

  const isToday = ymd(date) === ymd(today);
  const triLabel = triKey === "T1" ? "1st" : triKey === "T2" ? "2nd" : "3rd";
  const syncAttention = syncEnabled && available && (syncStatus === "unsupported" || syncStatus === "error");

  return (
    <div className="app">
      <TodayHeader
        date={date}
        onPrev={() => shiftDay(-1)}
        onNext={() => shiftDay(1)}
        isToday={isToday}
        onJumpToday={() => setDate(today)}
        onOpenSettings={() => setSettingsOpen(true)}
        syncAttention={syncAttention}
      />

      {syncAttention && (
        <SyncBanner
          syncStatus={syncStatus}
          syncErr={syncErr}
          onOpenSettings={() => setSettingsOpen(true)}
        />
      )}

      <div className="primary-cta">
        <button className="log-cta" onClick={() => setSheetOpen(true)}>
          <span className="log-cta-plus">+</span>
          <span className="log-cta-main">
            <span className="log-cta-title">Log food</span>
            <span className="log-cta-sub">search 50+ foods · palms, fists, handfuls</span>
          </span>
        </button>
      </div>

      <QuickAddBar onAdd={addFood} />

      <SymptomBar entries={entries} onAdd={addSymptom} viewDate={date} isToday={isToday} />

      {tweaks.showHighlights && entries.length > 0 && (
        <Highlights totals={totals} targets={targets} />
      )}

      <section className="ngrid-wrap">
        <div className="section-head">
          <h3>Daily nutrients</h3>
          <div className="section-tag">{triLabel} trimester targets</div>
        </div>
        <div className={`ngrid ${tweaks.compactRings ? "compact" : ""}`}>
          {NUTRIENT_KEYS.map(k => (
            <NutrientCard
              key={k}
              k={k}
              value={totals[k]}
              target={targets[k]}
              ringStyle={tweaks.ringStyle}
            />
          ))}
        </div>
      </section>

      <section className="log-wrap">
        <div className="section-head">
          <h3>Today's log</h3>
          <div className="section-tag">{entries.length} item{entries.length !== 1 ? "s" : ""}</div>
        </div>
        <div className="log-list">
          {entries.length === 0 && (
            <div className="log-empty">
              <div className="log-empty-title">Nothing logged yet</div>
              <div className="log-empty-sub">Tap a quick-add above, or "Log food" up top.</div>
            </div>
          )}
          {entries
            .slice()
            .sort((a, b) => a.ts - b.ts)
            .map(e => e.kind === "symptom"
              ? <SymptomRow key={e.id} entry={e} onDelete={deleteEntry} />
              : <LogRow key={e.id} entry={e} onDelete={deleteEntry} onAdjust={adjustPortion} />
            )}
        </div>
      </section>

      <div className="week-cta-wrap">
        <button className="week-cta" onClick={() => setWeekOpen(true)}>
          <span className="week-cta-main">
            <span className="week-cta-title">Weekly summary</span>
            <span className="week-cta-sub">last 7 days · share with your doctor</span>
          </span>
          <span className="week-cta-arrow">›</span>
        </button>
      </div>

      <AddSheet
        open={sheetOpen}
        onClose={() => setSheetOpen(false)}
        onAdd={addFood}
        viewDate={date}
        isToday={isToday}
      />

      {weekOpen && <WeeklySummary log={log} onClose={() => setWeekOpen(false)} />}

      <SettingsSheet
        open={settingsOpen}
        onClose={() => setSettingsOpen(false)}
        tweaks={tweaks}
        setTweak={setTweak}
        log={log}
        setLog={setLog}
        syncProps={{
          syncEnabled,
          syncStatus,
          syncErr,
          lastSync,
          available,
          onToggle: setSyncEnabled,
          onSyncNow: doPull,
          diagProps: {
            lastDiag,
            running: diagRunning,
            onRun: runDiagnostic,
          },
        }}
      />

    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
