// Shared components & utilities — exported to window for sibling Babel scripts.
const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* ---------- Storage ---------- */
const STORAGE_KEY = 'nutrition_entries';
const PREFS_KEY = 'nutrition_prefs';

const Storage = {
  get(key, fallback) {
    // Read from BOTH backing stores and prefer the richer payload. This guards
    // against the case where one store gets wiped (e.g. preview-session storage
    // resets) while the other still has the user's data.
    let fromWin = null, fromLocal = null;
    try {
      if (window.storage && window.storage.get) {
        const v = window.storage.get(key);
        if (v !== undefined && v !== null) fromWin = typeof v === 'string' ? JSON.parse(v) : v;
      }
    } catch (e) {}
    try {
      const raw = localStorage.getItem(key);
      if (raw) fromLocal = JSON.parse(raw);
    } catch (e) {}
    if (fromWin && fromLocal) {
      if (Array.isArray(fromWin) && Array.isArray(fromLocal)) {
        return fromWin.length >= fromLocal.length ? fromWin : fromLocal;
      }
      return fromWin;
    }
    return fromWin || fromLocal || fallback;
  },
  set(key, value) {
    const s = JSON.stringify(value);
    // Each write is wrapped independently so one failing doesn't kill the other.
    try {
      if (window.storage && window.storage.set) window.storage.set(key, s);
    } catch (e) { console.warn('window.storage write failed', e); }
    try {
      localStorage.setItem(key, s);
    } catch (e) { console.warn('localStorage write failed (quota?)', e); }
  },
  remove(key) {
    try { if (window.storage && window.storage.remove) window.storage.remove(key); } catch (e) {}
    try { localStorage.removeItem(key); } catch (e) {}
  }
};;

/* ---------- Date helpers (EST/EDT-aware) ---------- */
function tzNow() {
  // Return a Date with current US Eastern time components
  const now = new Date();
  const fmt = new Intl.DateTimeFormat('en-US', {
    timeZone: 'America/New_York',
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
  });
  const parts = fmt.formatToParts(now);
  const get = (t) => parts.find(p => p.type === t).value;
  return {
    iso: `${get('year')}-${get('month')}-${get('day')}`,
    hour: parseInt(get('hour'), 10),
    minute: parseInt(get('minute'), 10),
    dateObj: new Date(`${get('year')}-${get('month')}-${get('day')}T12:00:00`)
  };
}

function todayISO() { return tzNow().iso; }
function todayHour() { return tzNow().hour; }
function defaultMealForHour(h) {
  if (h < 11) return 'Breakfast';
  if (h >= 11 && h < 15) return 'Lunch';
  if (h >= 17 && h <= 21) return 'Dinner';
  return 'Snack';
}
function isoOffset(iso, days) {
  const d = new Date(iso + 'T12:00:00');
  d.setDate(d.getDate() + days);
  const yr = d.getFullYear(), mo = String(d.getMonth() + 1).padStart(2, '0'), dy = String(d.getDate()).padStart(2, '0');
  return `${yr}-${mo}-${dy}`;
}
function formatDay(iso) {
  if (!iso) return '';
  const d = new Date(iso + 'T12:00:00');
  return d.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
}
function formatDayLong(iso) {
  const d = new Date(iso + 'T12:00:00');
  return d.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
}
function relativeDayLabel(iso) {
  const today = todayISO();
  if (iso === today) return 'Today';
  if (iso === isoOffset(today, -1)) return 'Yesterday';
  return formatDay(iso);
}

/* ---------- Meals ---------- */
const MEALS = ['Breakfast', 'Lunch', 'Dinner', 'Snack', 'Water'];

/* ---------- Goals ---------- */
const GOAL_PROFILES = {
  standard: {
    label: 'Standard 2000',
    calories: 2000, protein: 75, carbs: 250, fat: 65, fiber: 28,
    sugar: 50, sodium: 2300, water: 64, caffeine: 400,
    vitC: 90, iron: 18, calcium: 1000, potassium: 3500
  },
  performance: {
    label: 'Performance 3000',
    calories: 3000, protein: 120, carbs: 450, fat: 90, fiber: 38,
    sugar: 90, sodium: 3000, water: 100, caffeine: 400,
    vitC: 120, iron: 20, calcium: 1200, potassium: 4200
  }
};

/* ---------- Number helpers ---------- */
function fmtNum(n, digits = 0) {
  if (n === null || n === undefined || isNaN(n)) return '0';
  const d = digits;
  const v = Number(n);
  if (d === 0) return Math.round(v).toLocaleString();
  return v.toFixed(d);
}
function ozToMl(oz) { return oz * 29.5735; }
function gToOz(g) { return g * 0.035274; }
function convertVol(oz, unit) {
  if (unit === 'ml') return { v: ozToMl(oz), u: 'ml' };
  return { v: oz, u: 'oz' };
}
function convertMass(g, unit) {
  if (unit === 'oz') return { v: gToOz(g), u: 'oz' };
  return { v: g, u: 'g' };
}

/* ---------- Day rollup ---------- */
const SUMMABLE = [
  'calories','fat','satFat','transFat','cholesterol','sodium','carbs','fiber','sugar','protein',
  'vitA','vitB6','vitB12','vitC','vitD','calcium','iron','potassium','caffeine','water'
];
function emptyTotals() {
  const t = {};
  SUMMABLE.forEach(k => t[k] = 0);
  return t;
}
function sumEntries(entries) {
  const t = emptyTotals();
  entries.forEach(e => {
    SUMMABLE.forEach(k => {
      // Water is only counted from dedicated "Water" meal rows — otherwise
      // beverage entries that happen to carry a water value double-count.
      if (k === 'water' && e.meal !== 'Water') return;
      t[k] += Number(e[k]) || 0;
    });
  });
  return t;
}
function entriesForDay(entries, iso) {
  return entries.filter(e => e.date === iso);
}
function totalsForDay(entries, iso) {
  return sumEntries(entriesForDay(entries, iso));
}
function totalsForRange(entries, startISO, endISO) {
  return sumEntries(entries.filter(e => e.date >= startISO && e.date <= endISO));
}
function uniqueDatesInRange(entries, startISO, endISO) {
  const s = new Set();
  entries.forEach(e => { if (e.date >= startISO && e.date <= endISO) s.add(e.date); });
  return s;
}
function avgPerDay(totals, dayCount) {
  if (!dayCount) return totals;
  const o = {};
  Object.entries(totals).forEach(([k, v]) => { o[k] = v / dayCount; });
  return o;
}

/* ---------- UI primitives ---------- */
function Icon({ name, size = 16 }) {
  const paths = {
    log: <><path d="M3 6.5 6.5 3M3 6.5l4 4M3 6.5h7.5M13.5 12 21 4.5M13.5 12l-4-4M13.5 12V4.5"/><circle cx="18" cy="18" r="3"/><path d="m20.5 20.5 1.5 1.5"/></>,
    paste: <><path d="M9 3h6a2 2 0 0 1 2 2v0H7v0a2 2 0 0 1 2-2Z"/><path d="M17 5h2a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2"/><path d="M8 12h8M8 16h5"/></>,
    dashboard: <><path d="M3 13h7V3H3v10ZM14 21h7v-7h-7v7ZM14 11h7V3h-7v8ZM3 21h7v-6H3v6Z"/></>,
    trends: <><path d="M3 17l5-5 4 4 8-8"/><path d="M14 8h6v6"/></>,
    history: <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/><path d="M3 12a9 9 0 0 0 .5 3"/></>,
    sun: <><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></>,
    moon: <><path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79Z"/></>,
    plus: <><path d="M12 5v14M5 12h14"/></>,
    chevL: <><path d="m15 6-6 6 6 6"/></>,
    chevR: <><path d="m9 6 6 6-6 6"/></>,
    sparkle: <><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M5.6 18.4l2.8-2.8M15.6 8.4l2.8-2.8"/></>,
    download: <><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></>,
    trash: <><path d="M4 7h16M9 7V4h6v3M6 7l1 13a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-13"/></>,
    edit: <><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5Z"/></>,
    cog: <><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 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z"/></>,
    search: <><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></>,
    today: <><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/><circle cx="12" cy="15" r="2"/></>,
    info: <><circle cx="12" cy="12" r="9"/><path d="M12 8h.01M11 12h1v5h1"/></>,
    lock: <><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/><circle cx="12" cy="16" r="1"/></>
  };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
         stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      {paths[name] || null}
    </svg>
  );
}

function Progress({ value, max, color }) {
  const pct = max > 0 ? Math.min(100, (value / max) * 100) : 0;
  return (
    <div className="progress" style={color ? {} : undefined}>
      <span style={{ width: pct + '%', background: color || 'var(--accent)' }} />
    </div>
  );
}

function ProgressRow({ name, value, max, unit, color, digits = 0 }) {
  const pct = max > 0 ? Math.min(999, (value / max) * 100) : 0;
  return (
    <div className="progress-row">
      <div className="pr-head">
        <span className="pr-name">{name}</span>
        <span className="pr-val">{fmtNum(value, digits)}{unit ? ' ' + unit : ''} <span className="dim">/ {fmtNum(max, digits)}</span></span>
      </div>
      <Progress value={value} max={max} color={color} />
      <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--mono)' }}>{Math.round(pct)}%</div>
    </div>
  );
}

function StatTile({ label, value, unit, sub, accent }) {
  return (
    <div className="stat">
      <div className="stat-label">{label}</div>
      <div>
        <div className="stat-val" style={accent ? { color: accent } : undefined}>{value}{unit ? <small>{unit}</small> : null}</div>
        {sub ? <div className="stat-sub mt-8">{sub}</div> : null}
      </div>
    </div>
  );
}

function Toast({ message, type = 'ok', onDone }) {
  useEffect(() => {
    const t = setTimeout(onDone, 2400);
    return () => clearTimeout(t);
  }, [onDone]);
  return <div className={`toast ${type === 'error' ? 'error' : ''}`}><span className="dot" />{message}</div>;
}

function ToastHost({ toasts, dismiss }) {
  return (
    <div className="toast-host">
      {toasts.map(t => <Toast key={t.id} message={t.message} type={t.type} onDone={() => dismiss(t.id)} />)}
    </div>
  );
}

/* ---------- Claude wrapper for nutrition lookup ---------- */
async function claudeLookupSingle(text, onProgress) {
  // Step 1: extract item summaries (compact — name/serving only)
  const summaries = await extractItemSummaries(text);
  if (!summaries.length) return { items: [] };
  if (onProgress) onProgress({ stage: 'parsing', total: summaries.length, done: 0 });
  // Step 2: enrich with nutrition in batches
  const items = await enrichItemsBatched(summaries, (done, total) => {
    if (onProgress) onProgress({ stage: 'looking up', total, done });
  });
  return { items };
}

async function claudeLookupMulti(text, onProgress) {
  // Step 1: parse free-form notes into structured summaries with meal + date
  const summaries = await extractItemSummariesMulti(text);
  if (!summaries.length) return { items: [] };
  if (onProgress) onProgress({ stage: 'parsing', total: summaries.length, done: 0 });
  // Step 2: enrich each with full nutrition in batches
  const items = await enrichItemsBatched(summaries, (done, total) => {
    if (onProgress) onProgress({ stage: 'looking up', total, done });
  });
  return { items };
}

/* Step 1a — single-meal Log Food: just split into items */
async function extractItemSummaries(text) {
  const prompt = `Parse the food entry below into a list of separate items. Could be one item or many. Return ONLY a JSON array, no markdown.

Entry:
"""
${text}
"""

Each item: {"name": "clean canonical name", "serving": "e.g. 1 cup, 12 oz, 1 medium"}

Return: [{"name": "...", "serving": "..."}]`;
  const raw = await window.claude.complete(prompt);
  const parsed = extractJSON(raw);
  if (Array.isArray(parsed)) return parsed;
  if (parsed.items) return parsed.items;
  return [parsed];
}

/* Step 1b — Paste Day: split with meal + date */
async function extractItemSummariesMulti(text) {
  const prompt = `Parse the free-form food notes below into a list of items. Notes may include multiple days, multiple meals, bullet points, or running prose. Return ONLY a JSON array, no markdown, no explanation.

Notes:
"""
${text}
"""

Each item must have: {"name": "...", "serving": "...", "meal": "Breakfast"|"Lunch"|"Dinner"|"Snack"|"Water", "date": "YYYY-MM-DD or null"}

- If a section header mentions a meal (Breakfast, Lunch, Dinner, Snack, Water), tag items under it with that meal.
- If a date is mentioned (any format), convert to YYYY-MM-DD and tag items under it. If no date, use null.
- If the item is plain water (just "water" or "X oz water"), set meal to "Water" and include the oz in the name.
- Each individual food/drink should be its own item.

Return: [{"name": "...", "serving": "...", "meal": "...", "date": "..."}]`;
  const raw = await window.claude.complete(prompt);
  const parsed = extractJSON(raw);
  if (Array.isArray(parsed)) return parsed;
  if (parsed.items) return parsed.items;
  return [parsed];
}

/* Step 2 — enrich a batch of items with full nutrition */
async function enrichItemsBatched(summaries, onProgress) {
  const BATCH = 3; // keeps each response well under token cap
  const all = [];
  for (let i = 0; i < summaries.length; i += BATCH) {
    const batch = summaries.slice(i, i + BATCH);
    const enriched = await enrichBatch(batch);
    all.push(...enriched);
    if (onProgress) onProgress(all.length, summaries.length);
  }
  return all;
}

async function enrichBatch(items) {
  const listText = items.map((it, i) => `${i + 1}. ${it.name}${it.serving ? ' (' + it.serving + ')' : ''}`).join('\n');
  const prompt = `Look up nutrition for each of these food items. Return ONLY a JSON array (no markdown) with one object per item, in the same order:

Items:
${listText}

For each item return:
{
  "name": "clean canonical name",
  "servingDescription": "serving as described",
  "calories": number, "fat": number, "satFat": number, "transFat": number,
  "cholesterol": number, "sodium": number, "carbs": number, "fiber": number,
  "sugar": number, "protein": number,
  "vitA": number, "vitB6": number, "vitB12": number, "vitC": number, "vitD": number,
  "calcium": number, "iron": number, "potassium": number,
  "caffeine": number, "water": number,
  "other": "short string of additional micros"
}

Units: g for fat/carbs/fiber/sugar/protein; mg for cholesterol/sodium/calcium/iron/potassium/caffeine/vitB6/vitC; mcg for vitA/vitB12/vitD; oz for water (drinks only, else 0). Use 0 when not applicable.

Return: [{...}, {...}, ...]`;
  const raw = await window.claude.complete(prompt);
  const parsed = extractJSON(raw);
  const arr = Array.isArray(parsed) ? parsed : (parsed.items || [parsed]);
  // Merge meal/date from summaries back in
  return arr.map((nutri, idx) => ({
    ...nutri,
    meal: items[idx] && items[idx].meal ? items[idx].meal : nutri.meal,
    date: items[idx] && items[idx].date ? items[idx].date : nutri.date
  }));
}

async function claudeInsight(todayTotals, weekAvg, goals, recentTrend) {
  const prompt = `You are a sports nutrition coach for a 150-lb mountain bike cyclist focused on athletic performance and health. Given today's nutrition, the past week's average, and goals, write ONE concise insight (2-4 short sentences, under 60 words). Focus on what to improve TODAY or tomorrow for cycling performance. Use a direct, coach-like tone — no greeting, no signoff, no markdown. Talk in first person to the athlete ("you").

TODAY (so far): ${JSON.stringify(todayTotals)}
WEEK AVG: ${JSON.stringify(weekAvg)}
GOALS: ${JSON.stringify(goals)}
14-DAY TREND (cal/protein/carbs/water by day, oldest first): ${JSON.stringify(recentTrend)}

Return ONLY a JSON object: {"headline": "5-7 words", "body": "your 2-4 sentence insight", "tags": ["1-3 short topic tags like 'carbs', 'hydration', 'protein timing'"]}`;
  const raw = await window.claude.complete(prompt);
  return extractJSON(raw);
}

async function claudeCoachAnswer(question, ctx, currentInsight, history) {
  const histStr = (history || []).slice(-4).map(t => `ATHLETE: ${t.q}\nCOACH: ${t.a}`).join('\n\n');
  const prompt = `You are a sports nutrition, performance, and training coach for a 150-lb mountain bike cyclist. Answer the athlete's question with practical, specific advice grounded in their actual data when relevant. Use a direct, coach-like tone. Keep it concise (3-6 sentences, under 130 words). Plain prose only — no markdown headers, no bullet lists unless truly needed, no greeting, no signoff. Talk to them as "you". If the question is outside nutrition/performance/training, answer briefly but steer back to what you can help with.

ATHLETE'S DATA:
- Today so far: ${JSON.stringify(ctx.today)}
- 7-day average: ${JSON.stringify(ctx.week)}
- 14-day average: ${JSON.stringify(ctx.twoWeek)}
- Daily goals: ${JSON.stringify(ctx.goals)}
- 14-day trend (cal/protein/carbs/water by day, oldest first): ${JSON.stringify(ctx.trend)}
- Recent meals today: ${JSON.stringify(ctx.todayMeals)}

CURRENT COACH INSIGHT FOR TODAY: ${currentInsight ? JSON.stringify({headline: currentInsight.headline, body: currentInsight.body}) : 'none yet'}
${histStr ? '\nCONVERSATION SO FAR:\n' + histStr + '\n' : ''}
ATHLETE'S QUESTION: ${question}

Respond with prose only. Do not return JSON or code fences.`;
  const raw = await window.claude.complete(prompt);
  return String(raw).trim().replace(/^```[a-z]*\s*/i, '').replace(/```\s*$/i, '').trim();
}

function extractJSON(raw) {
  if (!raw) throw new Error('Empty response');
  let s = String(raw).trim();
  // Strip ```json fences if any
  s = s.replace(/^```(?:json|JSON)?\s*/i, '').replace(/```\s*$/i, '').trim();
  // Try parse as-is
  try { return JSON.parse(s); } catch (e) {}
  // Try first array block (top-level [...])
  const aStart = s.indexOf('[');
  const aEnd = s.lastIndexOf(']');
  if (aStart !== -1 && aEnd !== -1 && aEnd > aStart) {
    try { return JSON.parse(s.slice(aStart, aEnd + 1)); } catch (e) {}
  }
  // Try first object block (top-level {...})
  const oStart = s.indexOf('{');
  const oEnd = s.lastIndexOf('}');
  if (oStart !== -1 && oEnd !== -1 && oEnd > oStart) {
    try { return JSON.parse(s.slice(oStart, oEnd + 1)); } catch (e) {}
  }
  // Repair: response was truncated mid-object. Try to recover complete objects.
  const repaired = tryRepairTruncatedArray(s);
  if (repaired) {
    try { return JSON.parse(repaired); } catch (e) {}
  }
  throw new Error('Could not parse JSON: ' + s.slice(0, 200));
}

// If response looks like "[{...},{...},{ // truncated" — keep complete objects
function tryRepairTruncatedArray(s) {
  const start = s.indexOf('[');
  if (start === -1) return null;
  let depth = 0, inStr = false, esc = false, lastCommaAfterClose = -1;
  for (let i = start; i < s.length; i++) {
    const c = s[i];
    if (inStr) {
      if (esc) { esc = false; }
      else if (c === '\\') { esc = true; }
      else if (c === '"') { inStr = false; }
      continue;
    }
    if (c === '"') inStr = true;
    else if (c === '{' || c === '[') depth++;
    else if (c === '}' || c === ']') {
      depth--;
      if (depth === 1 && c === '}') {
        // Just closed a top-level object inside the array
        // Look ahead for a comma
        let j = i + 1;
        while (j < s.length && /\s/.test(s[j])) j++;
        if (s[j] === ',') lastCommaAfterClose = j;
        else if (s[j] === ']') { /* finished naturally, no repair needed */ }
      }
    }
  }
  if (lastCommaAfterClose > 0) {
    return s.slice(start, lastCommaAfterClose) + ']';
  }
  return null;
}

/* ---------- App context ---------- */
const AppCtx = React.createContext(null);

Object.assign(window, {
  Storage, STORAGE_KEY, PREFS_KEY,
  tzNow, todayISO, todayHour, defaultMealForHour, isoOffset,
  formatDay, formatDayLong, relativeDayLabel,
  MEALS, GOAL_PROFILES,
  fmtNum, ozToMl, gToOz, convertVol, convertMass,
  SUMMABLE, emptyTotals, sumEntries, entriesForDay, totalsForDay, totalsForRange,
  uniqueDatesInRange, avgPerDay,
  Icon, Progress, ProgressRow, StatTile, Toast, ToastHost,
  claudeLookupSingle, claudeLookupMulti, claudeInsight, claudeCoachAnswer, extractJSON,
  AppCtx,
  React, useState, useEffect, useRef, useMemo, useCallback
});
