/* Nutri Smart Assistant — ported from Calories Analysis.html SmartAssistant IIFE.
   Logic is preserved verbatim (parseRange, parseIntent, renderers, answer).
   UI is rebuilt in React using Nutri's design tokens.

   Single-user Firebase mode: projects are now fixed labels rather than a
   per-user projectMeta[] list. */

(function(){
  const _FIXED_PROJECT_META = [
    { id: 'meals',   name: 'Calories', type: 'diet'    },
    { id: 'finance', name: 'Finance',  type: 'finance' },
    { id: 'goals',   name: 'Goals',    type: 'goal'    },
    { id: 'diary',   name: 'Diary',    type: 'diary'   },
  ];
  // ─── _esc ─── (used in EVERY renderer)
  const _esc = s => String(s ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  const _isISO = s => /^\d{4}-\d{2}-\d{2}$/.test(s||'');
  const _today = () => { const d = new Date(); return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); };
  const _addDays = (iso, n) => { const d = new Date(iso+'T00:00:00'); d.setDate(d.getDate()+n); return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); };

  // ─── DATE / RANGE PARSING ─────────────────────────────────────
  const MONTHS = {
    january:0,february:1,march:2,april:3,may:4,june:5,july:6,august:7,september:8,october:9,november:10,december:11,
    jan:0,feb:1,mar:2,apr:3,jun:5,jul:6,aug:7,sep:8,sept:8,oct:9,nov:10,dec:11,
  };
  function _fmt(d) { return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); }
  function _quarterRange(year, q) {
    const startMonth = (q-1)*3;
    return { from: _fmt(new Date(year, startMonth, 1)), to: _fmt(new Date(year, startMonth+3, 0)) };
  }
  function parseRange(text) {
    const q = (text||'').toLowerCase().trim();
    const today = _today();
    if (/\btoday\b/.test(q))     return { from: today, to: today, label: 'today' };
    if (/\byesterday\b/.test(q)) { const y = _addDays(today, -1); return { from: y, to: y, label: 'yesterday' }; }
    if (/\bthis week\b/.test(q)) return { from: _addDays(today, -6), to: today, label: 'this week' };
    if (/\blast week\b/.test(q)) return { from: _addDays(today, -13), to: _addDays(today, -7), label: 'last week' };
    if (/\bthis month\b/.test(q)) { const d = new Date(); return { from: _fmt(new Date(d.getFullYear(), d.getMonth(), 1)), to: _fmt(new Date(d.getFullYear(), d.getMonth()+1, 0)), label: 'this month' }; }
    if (/\blast month\b/.test(q)) { const d = new Date(); return { from: _fmt(new Date(d.getFullYear(), d.getMonth()-1, 1)), to: _fmt(new Date(d.getFullYear(), d.getMonth(), 0)), label: 'last month' }; }
    if (/\bthis quarter\b/.test(q)) { const d = new Date(); const q1 = Math.floor(d.getMonth()/3)+1; const r = _quarterRange(d.getFullYear(), q1); r.label = 'this quarter (Q'+q1+' '+d.getFullYear()+')'; return r; }
    if (/\blast quarter\b/.test(q)) { const d = new Date(); let q1 = Math.floor(d.getMonth()/3)+1; let yr = d.getFullYear(); if (q1 === 1) { q1 = 4; yr--; } else q1--; const r = _quarterRange(yr, q1); r.label = 'last quarter (Q'+q1+' '+yr+')'; return r; }
    if (/\bthis year\b/.test(q)) { const d = new Date(); return { from: d.getFullYear()+'-01-01', to: d.getFullYear()+'-12-31', label: 'this year' }; }
    if (/\blast year\b/.test(q)) { const d = new Date(); const y = d.getFullYear()-1; return { from: y+'-01-01', to: y+'-12-31', label: 'last year ('+y+')' }; }
    let m = q.match(/\bq([1-4])\s*(\d{4})\b/);
    if (m) { const r = _quarterRange(+m[2], +m[1]); r.label = 'Q'+m[1]+' '+m[2]; return r; }
    m = q.match(/\b(\d{4}-\d{2}-\d{2})\b/);
    if (m) return { from: m[1], to: m[1], label: m[1] };
    // "May 22, 2026" / "May 22 2026" / "may 22" — CRITICAL: negative lookahead (?![0-9]) so "May 2026" isn't read as day 20
    m = q.match(/\b(jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|sept|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\s+(\d{1,2})(?![0-9])(?:[\s,]+(\d{4}))?/);
    if (m) {
      const mn = MONTHS[m[1]];
      const day = +m[2];
      const yr = m[3] ? +m[3] : (new Date()).getFullYear();
      const iso = yr+'-'+String(mn+1).padStart(2,'0')+'-'+String(day).padStart(2,'0');
      return { from: iso, to: iso, label: iso };
    }
    m = q.match(/\b(jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|sept|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)(?:\s+(\d{4}))?\b/);
    if (m) {
      const mn = MONTHS[m[1]];
      const yr = m[2] ? +m[2] : (new Date()).getFullYear();
      return { from: _fmt(new Date(yr, mn, 1)), to: _fmt(new Date(yr, mn+1, 0)), label: m[1]+(m[2] ? (' '+m[2]) : '') };
    }
    return null;
  }

  // ─── INTENT CLASSIFIER ────────────────────────────────────────
  const STOP_NAMES = new Set([
    'Today','Yesterday','Tomorrow',
    'Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday',
    'January','February','March','April','May','June','July','August','September','October','November','December',
    'Jan','Feb','Mar','Apr','Jun','Jul','Aug','Sep','Sept','Oct','Nov','Dec',
    'Q1','Q2','Q3','Q4',
    'When','What','Where','Why','How','Which','Who','Whose','Did','Do','Does','Don','Doesn','Can','Could','Should','Would',
    'This','Last','Show','Find','Search','Summary','Summarize','Recap',
    'Quarter','Year','Week','Month','Day','Days','Was','Were','Are','Have','Had','Has','Will',
    'Calories','Spending','Income','Transfers','Meals','Goals','Memories','Notes',
    'Breakfast','Lunch','Dinner','Snack','Snacks',
  ]);
  function _extractNames(q) {
    const matches = (q||'').match(/\b[A-Z][a-zA-Z][a-zA-Z'-]{1,}\b/g) || [];
    const seen = new Set();
    return matches.filter(m => { if (STOP_NAMES.has(m) || seen.has(m)) return false; seen.add(m); return true; });
  }
  const _CAT_BLACK = /^(?:this|last|today|yesterday|monday|tuesday|wednesday|thursday|friday|saturday|sunday|january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|q[1-4]|\d{4}|week|month|year|quarter|day|days)\b/i;

  function parseIntent(q) {
    const text = (q||'').trim();
    const t = text.toLowerCase();
    const range = parseRange(q);
    let m;

    // ── Movie Night (checked first: "find a movie" must not fall into search).
    //    Guard against finance phrasing so "spend on movies" still routes to spending. ──
    const _financeCtx = /\b(spend|spent|spending|cost|costs?|paid|pay|budget|egp|money|expenses?)\b/.test(t);
    if (!_financeCtx && (
        /\b(movies?|films?|cinema|tmdb|watchlist)\b/.test(t)
        || /\bwhat (?:should|can|to) i watch\b/.test(t)
        || /\bwatch tonight\b/.test(t)
        || /\bwhy (?:did|do) you recommend\b/.test(t)
        || /\b(?:suggest|recommend)\s+(?:a |me )?(?:movie|film|something to watch)\b/.test(t))) {
      let movieView = 'overview';
      let subject = '';
      let mm;
      const genreM = t.match(/\b(action|comedy|comedies|drama|horror|thriller|romance|romantic|sci-?fi|science fiction|fantasy|animation|animated|documentary|documentaries|crime|mystery|adventure|family|war|western|musical|history)\b/);
      const ratingM = t.match(/\b(?:rating|rated|stars?|score)\s*(?:>|>=|over|above|greater than|at least)?\s*(\d+(?:\.\d+)?)/) || t.match(/[★]\s*(\d+(?:\.\d+)?)/) || t.match(/\b(?:over|above)\s+(\d+(?:\.\d+)?)\b/);
      const unwatched = /\b(?:haven'?t|not|never|yet to)\s+(?:watch|seen)/.test(t) || /\bunwatched\b/.test(t);
      const fiveStar = /\b(?:5|five)[\s-]?stars?\b/.test(t) || /\brated\s*5\b/.test(t) || /\b5\s*\/\s*5\b/.test(t);
      if (/\bwhy\b/.test(t) && /\brecommend/.test(t)) movieView = 'why';
      else if (/\bposters?\b/.test(t) && (/\bmissing\b/.test(t) || /\bhow many\b/.test(t) || /\bno\b/.test(t) || /\bwithout\b/.test(t))) movieView = 'missing-posters';
      else if (/\bimdb\b/.test(t) && (/\bsync|synced|missing|without|how many|not\b/.test(t))) movieView = 'imdb-status';
      else if (fiveStar) movieView = 'rated5';
      else if (/\bto rate\b/.test(t) || /\brate (?:my |some |\d+ )?(?:watched )?movies?\b/.test(t) || /\bmovies? to rate\b/.test(t)) movieView = 'rate-queue';
      else if (/\bunrated\b/.test(t) || /\bnot rated\b/.test(t) || /\bwithout (?:a )?rating\b/.test(t) || (/\bunrated\b/.test(t)) || (/\bhow many\b/.test(t) && /\brat(?:e|ed|ing)\b/.test(t))) movieView = 'unrated';
      else if (/\bsimilar to\b/.test(t)) { movieView = 'similar'; mm = text.match(/similar to\s+(.+?)[\?\.!]*$/i); if (mm) subject = mm[1].trim(); }
      else if (/\b(?:suggest|recommend|what (?:should|can|to) i watch|watch tonight|something to watch|pick (?:a )?(?:movie|film)|movie night)\b/.test(t)) movieView = 'suggest';
      else if (/\bconnected to (?:my )?memor|linked to (?:my )?memor|with (?:a )?memor|movies? .*memor\b/.test(t)) movieView = 'memories';
      else if (/\bdisliked?\b/.test(t)) movieView = 'disliked';
      else if (/\bliked?\b/.test(t)) movieView = 'liked';
      else if (/\bwatchlist|want to watch|to watch\b/.test(t)) movieView = 'watchlist';
      else if (genreM) { movieView = 'genre'; subject = genreM[1]; }
      else if (/\bwatch(?:ed)?\b/.test(t)) movieView = 'watched';
      return { type: 'movies', movieView, subject, range, ratingGte: ratingM ? +ratingM[1] : null, unwatched, confidence: 'high' };
    }

    // ── InBody / body composition (before calories, so "body fat" isn't caught by macros) ──
    if (/\b(?:inbody|in body|body composition|body fat|pbf|visceral fat|skeletal muscle|muscle mass|muscle|fat or muscle|losing fat|losing muscle)\b/.test(t)
        || /\b(?:focus on next|what should i focus|next focus)\b/.test(t)) {
      let view = 'overview';
      if (/\bvisceral\b/.test(t)) view = 'visceral';
      else if (/\bpbf\b|body fat %|body fat percent/.test(t)) view = 'pbf';
      else if (/\bcompare\b|last two|previous (?:scan|inbody)|vs\b/.test(t)) view = 'compare';
      else if (/\bpreserv\w*|losing muscle|muscle mass|protect\w* muscle\b/.test(t)) view = 'muscle';
      else if (/\bfocus|next|recommend|should i\b/.test(t)) view = 'focus';
      else if (/\bfat or muscle|lose fat|lose muscle|gain\b/.test(t)) view = 'fatmuscle';
      else if (/\bchang\w*|last (?:scan|inbody)|latest|recent\b/.test(t)) view = 'changed';
      return { type: 'inbody', inbodyView: view, range, confidence: 'high' };
    }

    if ((m = t.match(/\b(?:search for|search|find|look for|show (?:days|memories|entries) (?:that mention|about|with)|days that mention)\s+(.+?)[\?\.]?\s*$/))) {
      return { type: 'search', subject: m[1].trim().replace(/[?.!]+$/, ''), range, confidence: 'high' };
    }
    if (/\b(?:hang(?:ing|ed)? out|hung out|went out|hangout|going out|go out|spent time|hang)\b/.test(t)) {
      const names = _extractNames(text);
      return { type: 'people', people: names, subject: names.join(' & '), range, confidence: names.length ? 'high' : 'medium' };
    }
    if (/\b(?:with|mention(?:s|ed|ing)?)\b/.test(t)) {
      const names = _extractNames(text);
      if (names.length) return { type: 'people', people: names, subject: names.join(' & '), range, confidence: 'high' };
    }
    if (/\b(?:places?|visited?|visit|where (?:did|i|am|to)|location|locations|trip|trips|cities?|city)\b/.test(t)) {
      const names = _extractNames(text);
      return { type: 'places', subject: names[0] || '', range, confidence: 'high' };
    }
    // ── Projects (before generic spending so "spend on my car project" routes here) ──
    if (/\bprojects?\b/.test(t)) {
      let pname = '';
      let pm = t.match(/(?:my |the |this |that )?([a-z0-9][a-z0-9 &'\-]*?)\s+project\b/);
      if (pm) pname = pm[1];
      else { pm = t.match(/\bprojects?\s+(?:called |named )?([a-z0-9][a-z0-9 &'\-]+)/); if (pm) pname = pm[1]; }
      if (pname) pname = pname.replace(/\b(my|the|this|that|a|an|on|for|to|spend|spent|spending|expenses?|linked|show|how|much|did|i|pay|paid|in|of|budget|left)\b/g, '').replace(/\s+/g, ' ').trim();
      return { type: 'project', subject: pname, range, confidence: 'high' };
    }
    // ── Fixed expenses / installments ──
    if (/\b(?:fixed expenses?|fixed payments?|fixed commitments?|installments?|recurring|due (?:this|next|payment|payments|soon)|overdue|monthly commitment|commitments?|upcoming (?:payments?|bills?|fixed|installments?))\b/.test(t)) {
      let fixedView = 'all';
      if (/\boverdue\b/.test(t)) fixedView = 'overdue';
      else if (/\b(?:biggest|largest|top)\b/.test(t)) fixedView = 'biggest';
      else if (/\b(?:left|remaining|how much (?:is )?left)\b/.test(t)) fixedView = 'remaining';
      else if (/\b(?:due this week|this week|due soon|next week|upcoming|due)\b/.test(t)) fixedView = 'upcoming';
      return { type: 'fixed', fixedView, range, confidence: 'high' };
    }
    if (/\bfixing data\b/.test(t) || /\bbalance adjust|adjust(?:ed|ment)s?\b/.test(t) || /why (?:did|has|have) .*balance/.test(t) || /balance correction/.test(t) || /accounts? (?:were |was )?adjusted/.test(t)) {
      return { type: 'fixing', range, confidence: 'high' };
    }
    if (/\b(?:spend|spent|spending|expense|expenses|cost|costs|paid|pay)\b/.test(t)) {
      let subject = '';
      const catMatch = text.match(/\b(?:on|for)\s+([A-Za-z][\w'-]*(?:\s+[A-Za-z][\w'-]*)?)\b/i);
      if (catMatch) {
        let words = catMatch[1].trim().replace(/\s+/g, ' ').split(/\s/);
        while (words.length > 0 && _CAT_BLACK.test(words[words.length-1])) words.pop();
        const candidate = words.join(' ');
        if (candidate && !_CAT_BLACK.test(words[0])) subject = candidate;
      }
      return { type: 'spending', subject, range, confidence: 'high' };
    }
    if (/\b(?:balance[s]?|how much (?:cash|money)|(?:money|cash) (?:in|do i have)|wallet|bank balance|account balance)\b/.test(t)) {
      const acctM = t.match(/\b(cash|wallet|bank|card|savings)\b/);
      return { type: 'balance', accountHint: acctM ? acctM[1] : '', range, confidence: 'high' };
    }
    if (/\b(?:income|earned|earn|salary|received|revenue|freelance)\b/.test(t)) return { type: 'income', range, confidence: 'high' };
    if (/\btransfer/.test(t)) return { type: 'transfers', range, confidence: 'high' };
    if (/\b(?:gold|grams? of gold|gold value|gold profit|gold loss|gold holdings?|gold price|karat|24k|21k|18k|14k)\b/.test(t)) return { type: 'gold', range, confidence: 'high' };
    if (/\b(?:calori\w*|kcal|protein\w*|carbohydrate\w*|carbs?|macros?|fats?)\b/i.test(t)) return { type: 'calories', range, confidence: 'high' };
    if (/\b(?:ate|eat|food|meal|meals|menu|breakfast|lunch|dinner|snack|snacks)\b/.test(t)) return { type: 'diet', range, confidence: 'high' };
    if (/\b(?:goal|goals|habit|habits|complete|completed|streak|missed goal|miss goal)\b/.test(t)) {
      // Check for specific status sub-type
      let goalStatus = null;
      if (/\b(?:complet|done|finish|achiev)\w*/.test(t)) goalStatus = 'completed';
      else if (/\b(?:miss|fail|didn\'t|did not)\w*/.test(t)) goalStatus = 'missed';
      else if (/\bstreak\b/.test(t)) goalStatus = 'streak';
      return { type: 'goals', goalStatus, range, confidence: 'high' };
    }
    // ── On This Day ──
    if (/\b(?:on this day|this day in|memories on this day)\b/.test(t)) {
      let mmdd = null;
      const dm = t.match(/\b(jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|sept|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\s+(\d{1,2})\b/);
      if (dm) { const mn = MONTHS[dm[1]]; mmdd = String(mn+1).padStart(2,'0') + '-' + String(+dm[2]).padStart(2,'0'); }
      else { const d = new Date(); mmdd = String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0'); }
      return { type: 'diary-on-this-day', mmdd, range: null, confidence: 'high' };
    }
    // ── Diary Mood ──
    if (/\b(?:mood(?:s| breakdown| stats)?|days? (?:i )?(?:felt|feel|feeling)|how many days (?:did i )?(?:feel|felt)|how often (?:did i |was i )?feel|how (?:did i|was i) feel|feeling (?:this|last|in)\b)\b/.test(t)) {
      let moodKey = null;
      if (/\b(?:great|amazing|excellent|happy|fantastic)\b/.test(t)) moodKey = 'great';
      else if (/\b(?:good|well|fine|nice|solid)\b/.test(t)) moodKey = 'good';
      else if (/\b(?:okay|ok|average|neutral|alright|so-so)\b/.test(t)) moodKey = 'okay';
      else if (/\b(?:low|bad|sad|down|rough|not great|not good|terrible)\b/.test(t)) moodKey = 'low';
      return { type: 'diary-mood', moodKey, range, confidence: 'high' };
    }
    // ── Diary Tag ──
    if (/\b(?:tagged?|days (?:tagged|with tag)|entries (?:tagged|with tag)|tag overview|tag stats)\b/.test(t)) {
      // Capture single-word tag name right after "tagged"/"tag"
      const tagMatch = t.match(/\b(?:tagged?|with tag)\s+([\w؀-ۿ]+)/);
      const tagName = tagMatch ? tagMatch[1].trim() : '';
      return { type: 'diary-tag', tagName, range, confidence: 'high' };
    }
    if (/\b(?:memor|diary|remember|recall)\b/.test(t)) return { type: 'diary', range, confidence: 'high' };
    if (/\b(?:summariz|summary|recap|what happened|show everything|everything for|tell me about|the day i|on the day)\b/.test(t)) {
      return { type: (range && range.from === range.to) ? 'day-summary' : 'range-summary', range, confidence: 'high' };
    }
    if (range) return { type: (range.from === range.to) ? 'day-summary' : 'range-summary', range, confidence: 'medium' };
    const cap = _extractNames(text);
    if (cap.length) return { type: 'unknown', subject: cap.join(', '), range: null, confidence: 'low', clarification: `Did you mean to ask about people (${cap.join(', ')}), a place, or a keyword search?` };
    if (!t) return { type: 'unknown', range: null, confidence: 'low', clarification: 'Please ask a question.' };
    return { type: 'unknown', range: null, confidence: 'low', clarification: `I'm not sure what you're asking. Try a more specific question.` };
  }

  // ─── HELPERS for renderers ───────────────────────────────────
  function _inRange(rec, range) {
    if (!range) return true;
    if (!rec.date) return false;
    return rec.date >= range.from && rec.date <= range.to;
  }
  function _sumMacros(records) {
    return records.reduce((acc, r) => {
      const m = r.metrics || {};
      acc.calories += +m.calories || 0;
      acc.carbs_g  += +m.carbs_g  || 0;
      acc.protein_g+= +m.protein_g|| 0;
      acc.fat_g    += +m.fat_g    || 0;
      return acc;
    }, { calories:0, carbs_g:0, protein_g:0, fat_g:0 });
  }
  function _groupBy(arr, fn) {
    const m = {};
    arr.forEach(x => { const k = fn(x); (m[k] = m[k] || []).push(x); });
    return m;
  }
  function _textMatches(rec, query) {
    if (!query) return true;
    const q = query.toLowerCase();
    const hay = [rec.title||'', rec.text||'', rec.category||'', (rec.tags||[]).join(' ')].join(' ').toLowerCase();
    const words = q.split(/\s+/).filter(Boolean);
    return words.every(w => hay.includes(w));
  }

  // ─── RENDERERS (return sanitized HTML strings) ───────────────
  function renderEmpty() {
    return `<div class="sa-muted">I couldn’t find matching data for this user.</div>`;
  }

  function renderDaySummary(records, range) {
    if (!records.length) return renderEmpty();
    const byProj = _groupBy(records, r => r.projectId);
    const projects = _FIXED_PROJECT_META.filter(p => byProj[p.id]);
    const typeOrder = { diet:0, finance:1, goal:2, diary:3 };
    projects.sort((a,b) => (typeOrder[a.type] ?? 99) - (typeOrder[b.type] ?? 99));
    let html = `<h4>${_esc(range.label||'Period')}</h4>`;
    projects.forEach(p => {
      const recs = byProj[p.id] || [];
      html += `<div class="sa-sep"></div><strong>${_esc(p.name)}</strong> <span class="sa-muted">(${p.type})</span>`;
      if (p.type === 'diet') {
        const mealRecs = recs.filter(r => r.recordType === 'meal');
        const macros = _sumMacros(mealRecs);
        const mealNames = mealRecs.map(r => r.title).filter(Boolean);
        const items = [];
        mealRecs.forEach(r => { if (Array.isArray(r.tags) && r.tags.length) items.push(...r.tags); });
        const notes = recs.filter(r => r.recordType === 'note').map(r => r.text);
        html += `<ul>`+
          `<li><span class="sa-num">${macros.calories}</span> kcal · C ${macros.carbs_g}g · P ${macros.protein_g}g · F ${macros.fat_g}g</li>`+
          (mealNames.length ? `<li>Meals: ${_esc(mealNames.join(', '))}</li>` : '')+
          (items.length ? `<li>Items: ${_esc([...new Set(items)].slice(0, 12).join(', '))}${items.length > 12 ? '…' : ''}</li>` : '')+
          (notes.length ? `<li>Notes: ${_esc(notes.join(' | '))}</li>` : '')+
          `</ul>`;
      } else if (p.type === 'finance') {
        const expenses = recs.filter(r => r.recordType === 'expense');
        const income = recs.filter(r => r.recordType === 'income');
        const transfers = recs.filter(r => r.recordType === 'transfer');
        const expSum = expenses.reduce((s,r) => s + (+r.amount||0), 0);
        const incSum = income.reduce((s,r) => s + (+r.amount||0), 0);
        html += `<ul>`+
          (expSum ? `<li>Spending: <span class="sa-num">${expSum.toLocaleString()}</span> (${expenses.length} entries)</li>` : '')+
          (incSum ? `<li>Income: <span class="sa-num">${incSum.toLocaleString()}</span> (${income.length} entries)</li>` : '')+
          (transfers.length ? `<li>Transfers: ${transfers.length}</li>` : '')+
          (expenses.length + income.length + transfers.length === 0 ? '<li class="sa-muted">No entries.</li>' : '')+
          `</ul>`;
      } else if (p.type === 'goal') {
        const logs = recs.filter(r => r.recordType === 'goal-log');
        const totals = logs.reduce((s,r) => { s.done += +r.metrics.done||0; s.miss += +r.metrics.miss||0; s.total += +r.metrics.total||0; return s; }, { done:0, miss:0, total:0 });
        const goalNames = [...new Set(logs.map(l => l.title).filter(Boolean))];
        html += `<ul>`+
          `<li><span class="sa-num">${totals.done}</span>/${totals.total} done${totals.miss ? ', '+totals.miss+' missed' : ''} across ${logs.length} day${logs.length === 1 ? '' : 's'}</li>`+
          (goalNames.length ? `<li>Goals: ${_esc(goalNames.join(', '))}</li>` : '')+
          (logs.length === 0 ? '<li class="sa-muted">No goal activity in this range.</li>' : '')+
          `</ul>`;
      } else if (p.type === 'diary') {
        const mems = recs.filter(r => r.recordType === 'full_entry' || r.recordType === 'mood_tag_only');
        const MOOD = _MOOD_META();
        if (!mems.length) html += `<ul><li class="sa-muted">No diary entries in this range.</li></ul>`;
        else {
          html += `<ul>${mems.slice(0, 5).map(m => {
            const mm = MOOD[m.mood] || { emoji: '' };
            const snippet = m.recordType === 'full_entry' ? (m.text||'').slice(0, 140) : '';
            return `<li><strong>${_esc(m.date||'')}</strong> ${mm.emoji}${m.title && m.title !== 'Entry' ? ' · '+_esc(m.title) : ''}${snippet ? ': '+_esc(snippet)+(m.text.length > 140 ? '…' : '') : ''}${m.tags&&m.tags.length?' <span class="sa-muted">['+_esc(m.tags.join(', '))+']</span>':''}</li>`;
          }).join('')}${mems.length > 5 ? `<li class="sa-muted">…and ${mems.length-5} more</li>` : ''}</ul>`;
        }
      } else {
        html += `<ul>${recs.slice(0, 5).map(r => `<li><strong>${_esc(r.date||'')}</strong> · ${_esc(r.title||'')}${r.text ? ': '+_esc((r.text||'').slice(0, 100)) : ''}</li>`).join('')}${recs.length > 5 ? `<li class="sa-muted">…and ${recs.length-5} more</li>` : ''}</ul>`;
      }
    });
    return html;
  }

  function renderFixingData(records, range) {
    let adj = records.filter(r => r.projectType === 'finance' && r.fixingData);
    if (range) adj = adj.filter(r => r.date >= range.from && r.date <= range.to);
    if (!adj.length) return '<h4>🛠 Fixing Data</h4><p>No balance adjustments' + (range ? (' in ' + _esc(range.label || '')) : '') + ' yet. Open Finance → Accounts → an account’s <strong>Fix</strong> button to correct a balance.</p>';
    const byAcct = {}; let net = 0;
    adj.forEach(r => { const a = r.accountName || r.account || '—'; const signed = (r.recordType === 'income' ? r.amount : -r.amount); byAcct[a] = (byAcct[a] || 0) + signed; net += signed; });
    let html = '<h4>🛠 Balance adjustments (Fixing Data)</h4>';
    html += '<p>' + adj.length + ' adjustment' + (adj.length === 1 ? '' : 's') + (range ? (' in ' + _esc(range.label || '')) : '') + ' · net <span class="sa-num">' + (net >= 0 ? '+' : '') + Math.round(net).toLocaleString() + '</span></p>';
    html += '<strong>By account</strong><ul>' + Object.entries(byAcct).sort((a, b) => Math.abs(b[1]) - Math.abs(a[1])).map(([a, v]) => '<li><strong>' + _esc(a) + '</strong>: ' + (v >= 0 ? '+' : '') + Math.round(v).toLocaleString() + '</li>').join('') + '</ul>';
    html += '<div class="sa-sep"></div><strong>Recent</strong><ul>' + adj.sort((a, b) => (b.date || '').localeCompare(a.date || '')).slice(0, 12).map(r => '<li>' + _esc(r.date || '') + ' · <strong>' + _esc(r.accountName || r.account || '') + '</strong> · ' + (r.recordType === 'income' ? '+' : '−') + Math.round(r.amount).toLocaleString() + ' <span class="sa-muted">' + _esc(r.text || '') + '</span></li>').join('') + '</ul>';
    return html;
  }
  function renderSpending(records, range, subject) {
    // Exclude "Fixing Data" balance corrections from normal spending (ask "balance adjustments" for those).
    let expenses = records.filter(r => r.projectType === 'finance' && r.recordType === 'expense' && !r.fixingData);
    if (subject) {
      const s = subject.toLowerCase();
      expenses = expenses.filter(r => [
        r.category, r.categoryName, r.subcategory, r.subcategoryName,
        r.accountName, r.title, r.text, r.spendingType,
      ].some(v => v && v.toLowerCase().includes(s)));
    }
    if (!expenses.length) return renderEmpty();
    const total = expenses.reduce((s,r) => s + (+r.amount||0), 0);
    const headLabel = (subject ? 'on '+subject+' ' : '') + (range ? '— '+range.label : '');
    if (subject) {
      const bySubcat = _groupBy(expenses, r => r.subcategoryName || r.categoryName || r.category || 'Other');
      const subcats  = Object.keys(bySubcat).map(k => ({ key:k, sum:bySubcat[k].reduce((s,r) => s+(+r.amount||0),0), count:bySubcat[k].length })).sort((a,b)=>b.sum-a.sum);
      return `<h4>Spending ${_esc(headLabel)}</h4>`+
        `<ul><li><span class="sa-num">${total.toLocaleString()}</span> total across ${expenses.length} entr${expenses.length===1?'y':'ies'}</li></ul>`+
        `<div class="sa-sep"></div><strong>Matching entries</strong>`+
        `<ul>${expenses.slice(0,8).map(r => `<li><strong>${_esc(r.date||'')}</strong> · ${_esc(r.title||r.categoryName||r.category||'')}: <span class="sa-num">${(+r.amount||0).toLocaleString()}</span></li>`).join('')}${expenses.length>8?`<li class="sa-muted">…and ${expenses.length-8} more</li>`:''}</ul>`+
        (subcats.length > 1 ? `<div class="sa-sep"></div><strong>By subcategory</strong><ul>${subcats.map(k=>`<li>${_esc(k.key)}: <span class="sa-num">${k.sum.toLocaleString()}</span> <span class="sa-muted">(${k.count})</span></li>`).join('')}</ul>` : '');
    }
    const byCat = _groupBy(expenses, r => r.categoryName || r.category || 'Other');
    const cats  = Object.keys(byCat).map(k => ({ key:k, sum:byCat[k].reduce((s,r)=>s+(+r.amount||0),0), count:byCat[k].length })).sort((a,b)=>b.sum-a.sum);
    const byType= _groupBy(expenses, r => r.spendingType || '');
    const types = Object.keys(byType).filter(k=>k).map(k => ({ key:k, sum:byType[k].reduce((s,r)=>s+(+r.amount||0),0) })).sort((a,b)=>b.sum-a.sum);
    return `<h4>Spending ${_esc(headLabel)}</h4>`+
      `<ul><li><span class="sa-num">${total.toLocaleString()}</span> total across ${expenses.length} entr${expenses.length===1?'y':'ies'}</li></ul>`+
      `<div class="sa-sep"></div><strong>By category</strong>`+
      `<ul>${cats.slice(0,8).map(k=>`<li>${_esc(k.key)}: <span class="sa-num">${k.sum.toLocaleString()}</span> <span class="sa-muted">(${k.count})</span></li>`).join('')}${cats.length>8?`<li class="sa-muted">…and ${cats.length-8} more</li>`:''}</ul>`+
      (types.length ? `<div class="sa-sep"></div><strong>By type</strong><ul>${types.map(k=>`<li style="text-transform:capitalize">${_esc(k.key)}: <span class="sa-num">${k.sum.toLocaleString()}</span></li>`).join('')}</ul>` : '');
  }

  function renderIncome(records, range) {
    const income = records.filter(r => r.projectType === 'finance' && r.recordType === 'income');
    if (!income.length) return renderEmpty();
    const total = income.reduce((s,r) => s + (+r.amount||0), 0);
    const byType  = _groupBy(income, r => r.subcategoryName || r.subcategory || 'Other');
    const types   = Object.keys(byType).map(k => ({ key:k, sum:byType[k].reduce((s,r)=>s+(+r.amount||0),0), count:byType[k].length })).sort((a,b)=>b.sum-a.sum);
    const byMonth = _groupBy(income, r => (r.date||'').slice(0,7));
    const months  = Object.keys(byMonth).sort().map(m => ({ month:m, sum:byMonth[m].reduce((s,r)=>s+(+r.amount||0),0) }));
    const bestM   = months.reduce((best,m) => m.sum>(best?best.sum:-1)?m:best, null);
    const avgM    = months.length ? total/months.length : total;
    return `<h4>Income — ${_esc(range ? range.label : 'all time')}</h4>`+
      `<ul>`+
        `<li>Total: <span class="sa-num">${total.toLocaleString()}</span> across ${income.length} entr${income.length===1?'y':'ies'}</li>`+
        (months.length>1?`<li>Monthly avg: <span class="sa-num">${Math.round(avgM).toLocaleString()}</span></li>`:'')+
        (bestM?`<li>Best month: <strong>${_esc(bestM.month)}</strong> — <span class="sa-num">${Math.round(bestM.sum).toLocaleString()}</span></li>`:'')+
      `</ul>`+
      (types.length ? `<div class="sa-sep"></div><strong>By source</strong><ul>${types.map(k=>`<li>${_esc(k.key)}: <span class="sa-num">${k.sum.toLocaleString()}</span> <span class="sa-muted">(${k.count})</span></li>`).join('')}</ul>` : '');
  }

  function renderBalance(records, accountHint) {
    const acctRecs = records.filter(r => r.projectType === 'finance' && r.recordType === 'account');
    if (!acctRecs.length) return `<h4>Account Balances</h4><div class="sa-muted">No accounts found. Add accounts in Finance → Accounts.</div>`;
    const display = accountHint ? acctRecs.filter(r =>
      (r.accountName||'').toLowerCase().includes(accountHint) ||
      (r.tags||[]).some(t => t && t.toLowerCase().includes(accountHint))
    ) : acctRecs;
    const list  = display.length ? display : acctRecs;
    const total = list.reduce((s,r) => s + (+r.amount||0), 0);
    return `<h4>Account Balances</h4>`+
      `<ul><li>Total: <span class="sa-num">${total.toLocaleString(undefined,{minimumFractionDigits:0,maximumFractionDigits:2})}</span> across ${list.length} account${list.length===1?'':'s'}</li></ul>`+
      `<div class="sa-sep"></div>`+
      `<ul>${list.map(r => `<li><strong>${_esc(r.accountName||r.title)}</strong>: <span class="sa-num">${(+r.amount||0).toLocaleString(undefined,{minimumFractionDigits:0,maximumFractionDigits:2})}</span></li>`).join('')}</ul>`;
  }

  function renderTransfers(records, range) {
    const transfers = records.filter(r => r.projectType === 'finance' && r.recordType === 'transfer');
    if (!transfers.length) return renderEmpty();
    const total = transfers.reduce((s,r) => s + (+r.amount||0), 0);
    return `<h4>Transfers — ${_esc(range ? range.label : 'all time')}</h4>`+
      `<ul><li>${transfers.length} transfer${transfers.length === 1 ? '' : 's'}, total <span class="sa-num">${total.toLocaleString()}</span></li>`+
      transfers.slice(0, 8).map(t => `<li>${_esc(t.date||'?')} · ${_esc(t.title||'')} · <span class="sa-num">${(+t.amount||0).toLocaleString()}</span></li>`).join('')+
      (transfers.length > 8 ? `<li class="sa-muted">…and ${transfers.length-8} more</li>` : '')+
      `</ul>`;
  }

  function renderCalories(records, range) {
    const meals = records.filter(r => r.projectType === 'diet' && r.recordType === 'meal');
    if (!meals.length) return renderEmpty();
    const macros = _sumMacros(meals);
    const mealNames = meals.map(m => m.title).filter(Boolean);
    const foodItems = [];
    meals.forEach(m => { if (Array.isArray(m.tags) && m.tags.length) foodItems.push(...m.tags); });
    const uniqItems = [...new Set(foodItems)];
    const uniqDays = new Set(meals.map(m => m.date).filter(Boolean)).size;
    return `<h4>Diet — ${_esc(range ? range.label : 'all time')}</h4>`+
      `<ul>`+
        `<li><span class="sa-num">${macros.calories}</span> kcal · C ${macros.carbs_g}g · P ${macros.protein_g}g · F ${macros.fat_g}g</li>`+
        `<li>${meals.length} meal${meals.length === 1 ? '' : 's'} across ${uniqDays} day${uniqDays === 1 ? '' : 's'}</li>`+
        (mealNames.length ? `<li>Meals: ${_esc(mealNames.slice(0, 12).join(', '))}${mealNames.length > 12 ? '…' : ''}</li>` : '')+
        (uniqItems.length ? `<li>Items: ${_esc(uniqItems.slice(0, 12).join(', '))}${uniqItems.length > 12 ? '…' : ''}</li>` : '')+
      `</ul>`;
  }

  function renderPeople(records, range, names) {
    if (!names || !names.length) {
      return `<h4>People question</h4>`+
        `<div class="sa-muted">I didn't catch a person's name. Try "How many days mention Kareem?" or "When did I hang out with Yasser?".</div>`;
    }
    const memories = _diaryRecs(records);
    const matching = memories.filter(m => {
      const hay = ((m.title||'')+' '+(m.text||'')+' '+(m.tags||[]).join(' ')).toLowerCase();
      return names.every(n => hay.includes(n.toLowerCase()));
    });
    if (!matching.length) {
      return `<h4>${_esc(names.join(' & '))}${range ? ' — '+_esc(range.label) : ''}</h4>`+
        `<div class="sa-muted">I couldn't find any matching diary entries for this user.</div>`;
    }
    const MOOD = _MOOD_META();
    const uniqDays = new Set(matching.map(m => m.date).filter(Boolean));
    return `<h4>${_esc(names.join(' & '))}${range ? ' — '+_esc(range.label) : ''}</h4>`+
      `<ul><li><span class="sa-num">${uniqDays.size}</span> day${uniqDays.size === 1 ? '' : 's'} with ${matching.length} entr${matching.length === 1 ? 'y' : 'ies'} mentioning ${_esc(names.join(' and '))}</li></ul>`+
      `<div class="sa-sep"></div><ul>${matching.slice(0, 8).map(m => {
        const mm = MOOD[m.mood] || { emoji: '' };
        const snippet = (m.text||'').slice(0, 160);
        return `<li><strong>${_esc(m.date||'')}</strong> ${mm.emoji}${m.title && m.title !== 'Entry' ? ' · '+_esc(m.title) : ''}${snippet ? ': '+_esc(snippet)+(m.text.length > 160 ? '…' : '') : ''}</li>`;
      }).join('')}${matching.length > 8 ? `<li class="sa-muted">…and ${matching.length-8} more</li>` : ''}</ul>`;
  }

  function renderPlaces(records, range, place) {
    const memories = _diaryRecs(records);
    if (!memories.length) return renderEmpty();
    if (place) {
      const ps = place.toLowerCase();
      const matching = memories.filter(m => ((m.title||'')+' '+(m.text||'')).toLowerCase().includes(ps));
      if (!matching.length) return renderEmpty();
      const uniqDays = new Set(matching.map(m => m.date).filter(Boolean));
      return `<h4>${_esc(place)}${range ? ' — '+_esc(range.label) : ''}</h4>`+
        `<ul><li><span class="sa-num">${uniqDays.size}</span> day${uniqDays.size === 1 ? '' : 's'} mention ${_esc(place)}</li></ul>`+
        `<div class="sa-sep"></div><ul>${matching.slice(0, 8).map(m => `<li><strong>${_esc(m.date||'')}</strong>: ${_esc((m.text||'').slice(0, 160))}${(m.text||'').length > 160 ? '…' : ''}</li>`).join('')}${matching.length > 8 ? `<li class="sa-muted">…and ${matching.length-8} more</li>` : ''}</ul>`;
    }
    const counts = new Map();
    memories.forEach(m => {
      const cap = ((m.text||'')+' '+(m.title||'')).match(/\b[A-Z][a-z]{2,}\b/g) || [];
      cap.forEach(w => { if (!STOP_NAMES.has(w)) counts.set(w, (counts.get(w)||0) + 1); });
    });
    if (counts.size === 0) return renderEmpty();
    const sorted = [...counts.entries()].sort((a,b) => b[1] - a[1]).slice(0, 15);
    return `<h4>Places &amp; names mentioned${range ? ' — '+_esc(range.label) : ''}</h4>`+
      `<p class="sa-muted">Top capitalized words in your diary (likely places or people).</p>`+
      `<ul>${sorted.map(([w, c]) => `<li>${_esc(w)}: <span class="sa-num">${c}</span></li>`).join('')}</ul>`;
  }

  function renderSearch(records, range, keyword) {
    if (!keyword) return renderEmpty();
    const k = keyword.toLowerCase();
    const matching = records.filter(r => {
      const hay = ((r.title||'')+' '+(r.text||'')+' '+(r.category||'')+' '+(r.tags||[]).join(' ')).toLowerCase();
      return hay.includes(k);
    });
    if (!matching.length) {
      return `<h4>Search: "${_esc(keyword)}"${range ? ' — '+_esc(range.label) : ''}</h4>`+
        `<div class="sa-muted">I couldn't find matching data for this user.</div>`;
    }
    const byProj = _groupBy(matching, r => r.projectId);
    const projIds = Object.keys(byProj);
    let html = `<h4>Search: "${_esc(keyword)}"${range ? ' — '+_esc(range.label) : ''}</h4>`+
      `<ul><li><span class="sa-num">${matching.length}</span> match${matching.length === 1 ? '' : 'es'} in ${projIds.length} project${projIds.length === 1 ? '' : 's'}</li></ul>`;
    projIds.forEach(pid => {
      const proj = _FIXED_PROJECT_META.find(p => p.id === pid);
      const recs = byProj[pid];
      html += `<div class="sa-sep"></div><strong>${_esc(proj ? proj.name : pid)}</strong> <span class="sa-muted">(${recs.length})</span>`;
      html += `<ul>${recs.slice(0, 5).map(r => {
        const snippet = (r.text||'').slice(0, 120);
        return `<li><strong>${_esc(r.date||'?')}</strong> · ${_esc(r.title||r.recordType)}${snippet ? ': '+_esc(snippet)+(r.text.length > 120 ? '…' : '') : ''}</li>`;
      }).join('')}${recs.length > 5 ? `<li class="sa-muted">…and ${recs.length-5} more</li>` : ''}</ul>`;
    });
    return html;
  }

  function _diaryRecs(records) {
    return records.filter(r => r.projectType === 'diary' && (r.recordType === 'full_entry' || r.recordType === 'mood_tag_only'));
  }
  const _MOOD_META = () => window.ND_MOOD_META || { great:{label:'Great',emoji:'😄'}, good:{label:'Good',emoji:'🙂'}, okay:{label:'Okay',emoji:'😐'}, low:{label:'Low',emoji:'😕'} };

  function renderDiary(records, range, query) {
    const diaryAll = _diaryRecs(records);
    let filtered = diaryAll;
    let searchTerm = '';
    const m = (query||'').match(/\b(?:mention|mentions|about|with|of)\s+(.+?)(?:\?|$)/i);
    if (m) searchTerm = m[1].replace(/[,.;?!]$/, '').trim();
    if (!searchTerm) {
      const names = (query||'').match(/\b[A-Z][a-z]{2,}/g);
      if (names && names.length) searchTerm = names.join(' ');
    }
    if (searchTerm) filtered = diaryAll.filter(r => _textMatches(r, searchTerm));
    if (!filtered.length) return renderEmpty();
    const MOOD = _MOOD_META();
    const uniqDays = new Set(filtered.map(r => r.date).filter(Boolean)).size;
    let html = `<h4>Diary${searchTerm ? ' mentioning “'+_esc(searchTerm)+'”' : ''}${range ? ' — '+_esc(range.label) : ''}</h4>`+
      `<ul><li>${filtered.length} entr${filtered.length === 1 ? 'y' : 'ies'} across ${uniqDays} day${uniqDays === 1 ? '' : 's'}</li></ul>`;
    html += `<div class=”sa-sep”></div>`+
      `<ul>${filtered.slice(0, 8).map(r => {
        const moodMeta = MOOD[r.mood] || { emoji: '' };
        const snippet = r.recordType === 'full_entry' ? (r.text||'').slice(0, 180) : '';
        return `<li><strong>${_esc(r.date||'')}</strong> ${moodMeta.emoji}${r.title && r.title !== 'Entry' ? ' · '+_esc(r.title) : ''}${snippet ? ': '+_esc(snippet)+(r.text.length > 180 ? '…' : '') : ''}${r.tags&&r.tags.length ? ' <span class=”sa-muted”>['+_esc(r.tags.join(', '))+']</span>' : ''}</li>`;
      }).join('')}${filtered.length > 8 ? `<li class=”sa-muted”>…and ${filtered.length-8} more</li>` : ''}</ul>`;
    return html;
  }

  function renderDiaryMood(records, range, moodKey) {
    const MOOD = _MOOD_META();
    const diaryAll = _diaryRecs(records);
    if (!diaryAll.length) return renderEmpty();
    if (moodKey) {
      const matching = diaryAll.filter(r => r.mood === moodKey);
      const uniqDays = new Set(matching.map(r => r.date).filter(Boolean)).size;
      const meta = MOOD[moodKey] || { label: moodKey, emoji: '' };
      if (!matching.length) return `<h4>Mood: ${meta.emoji} ${_esc(meta.label||moodKey)}</h4><div class=”sa-muted”>No entries with this mood${range ? ' in '+_esc(range.label) : ''}.</div>`;
      return `<h4>Mood: ${meta.emoji} ${_esc(meta.label||moodKey)}${range ? ' — '+_esc(range.label) : ''}</h4>`+
        `<ul><li><span class=”sa-num”>${uniqDays}</span> day${uniqDays===1?'':'s'} with ${_esc(meta.label||moodKey)} mood (${matching.length} entr${matching.length===1?'y':'ies'})</li></ul>`+
        `<div class=”sa-sep”></div>`+
        `<ul>${matching.slice(0, 8).map(r => {
          const mm = MOOD[r.mood] || { emoji: '' };
          const snippet = (r.text||'').slice(0, 120);
          return `<li><strong>${_esc(r.date||'')}</strong> ${mm.emoji}${r.title&&r.title!=='Entry'?' · '+_esc(r.title):''}${snippet?': '+_esc(snippet)+(r.text.length>120?'…':''):''}</li>`;
        }).join('')}${matching.length>8?`<li class=”sa-muted”>…and ${matching.length-8} more</li>`:''}</ul>`;
    }
    // Mood breakdown
    const counts = {}, days = {};
    diaryAll.forEach(r => {
      const mk = r.mood || 'unknown';
      counts[mk] = (counts[mk]||0) + 1;
      (days[mk] = days[mk] || new Set()).add(r.date);
    });
    const total = diaryAll.length;
    const lines = Object.entries(counts).sort((a,b) => b[1]-a[1]).map(([mk, cnt]) => {
      const meta = MOOD[mk] || { label: mk, emoji: '' };
      const d = days[mk].size;
      const pct = total ? Math.round(cnt/total*100) : 0;
      return `<li>${meta.emoji} <strong>${_esc(meta.label||mk)}</strong>: <span class=”sa-num”>${d}</span> day${d===1?'':'s'} (${pct}%)</li>`;
    }).join('');
    return `<h4>Mood breakdown${range?' — '+_esc(range.label):''}</h4>`+
      `<ul><li>${total} total entr${total===1?'y':'ies'}</li></ul>`+
      `<div class=”sa-sep”></div><ul>${lines}</ul>`;
  }

  function renderDiaryTags(records, range, tagName) {
    const MOOD = _MOOD_META();
    const diaryAll = _diaryRecs(records);
    if (!diaryAll.length) return renderEmpty();
    if (tagName) {
      const tl = tagName.toLowerCase();
      const matching = diaryAll.filter(r => (r.tags||[]).some(t => t.toLowerCase().includes(tl)));
      if (!matching.length) return `<h4>Tag: “${_esc(tagName)}”${range?' — '+_esc(range.label):''}</h4><div class=”sa-muted”>No entries tagged “${_esc(tagName)}”${range?' in '+_esc(range.label):''}.</div>`;
      const uniqDays = new Set(matching.map(r => r.date).filter(Boolean)).size;
      return `<h4>Tag: “${_esc(tagName)}”${range?' — '+_esc(range.label):''}</h4>`+
        `<ul><li><span class=”sa-num”>${uniqDays}</span> day${uniqDays===1?'':'s'} (${matching.length} entr${matching.length===1?'y':'ies'})</li></ul>`+
        `<div class=”sa-sep”></div>`+
        `<ul>${matching.slice(0, 8).map(r => {
          const mm = MOOD[r.mood] || { emoji: '' };
          const snippet = (r.text||'').slice(0, 120);
          return `<li><strong>${_esc(r.date||'')}</strong> ${mm.emoji}${r.title&&r.title!=='Entry'?' · '+_esc(r.title):''}${r.tags&&r.tags.length?' <span class=”sa-muted”>['+_esc(r.tags.join(', '))+']</span>':''}${snippet?': '+_esc(snippet)+(r.text.length>120?'…':''):''}</li>`;
        }).join('')}${matching.length>8?`<li class=”sa-muted”>…and ${matching.length-8} more</li>`:''}</ul>`;
    }
    // Tag frequency overview
    const tagCounts = {}, tagDays = {};
    diaryAll.forEach(r => { (r.tags||[]).forEach(t => { tagCounts[t]=(tagCounts[t]||0)+1; (tagDays[t]=tagDays[t]||new Set()).add(r.date); }); });
    if (!Object.keys(tagCounts).length) return `<h4>Tags${range?' — '+_esc(range.label):''}</h4><div class=”sa-muted”>No tagged entries found.</div>`;
    const sorted = Object.entries(tagCounts).sort((a,b) => b[1]-a[1]).slice(0, 12);
    return `<h4>Tags${range?' — '+_esc(range.label):''}</h4>`+
      `<ul>${sorted.map(([tag, cnt]) => { const d=tagDays[tag].size; return `<li><strong>${_esc(tag)}</strong>: <span class=”sa-num”>${d}</span> day${d===1?'':'s'} (${cnt} ${cnt===1?'entry':'entries'})</li>`; }).join('')}</ul>`+
      (Object.keys(tagCounts).length>12?`<div class=”sa-muted”>…and ${Object.keys(tagCounts).length-12} more tags</div>`:'');
  }

  function renderDiaryOnThisDay(records, mmdd) {
    const MOOD = _MOOD_META();
    const fullEntries = records.filter(r => r.projectType === 'diary' && r.recordType === 'full_entry');
    const matching = fullEntries.filter(r => r.date && r.date.slice(5) === mmdd);
    const [mm, dd] = (mmdd||'01-01').split('-');
    const dateLabel = new Date(2000, +mm-1, +dd).toLocaleDateString('en-US', { month: 'long', day: 'numeric' });
    if (!matching.length) return `<h4>On This Day — ${_esc(dateLabel)}</h4><div class=”sa-muted”>No diary entries found for ${_esc(dateLabel)} in any year.</div>`;
    matching.sort((a,b) => b.date.localeCompare(a.date));
    const currentYear = new Date().getFullYear();
    const uniqYears = new Set(matching.map(r => r.date.slice(0,4))).size;
    return `<h4>On This Day — ${_esc(dateLabel)}</h4>`+
      `<ul><li><span class=”sa-num”>${matching.length}</span> memor${matching.length===1?'y':'ies'} across ${uniqYears} year${uniqYears===1?'':'s'}</li></ul>`+
      `<div class=”sa-sep”></div>`+
      `<ul>${matching.slice(0, 8).map(r => {
        const yr = +r.date.slice(0,4);
        const ago = currentYear - yr;
        const mm2 = MOOD[r.mood] || { emoji: '' };
        const snippet = (r.text||'').slice(0, 160);
        const agoLabel = ago===0?'this year':ago===1?'1 year ago':ago+' years ago';
        return `<li><strong>${_esc(r.date)}</strong> ${mm2.emoji} <span class=”sa-muted”>${agoLabel}</span>${r.title&&r.title!=='Entry'?' · '+_esc(r.title):''}${snippet?': '+_esc(snippet)+(r.text.length>160?'…':''):''}</li>`;
      }).join('')}${matching.length>8?`<li class=”sa-muted”>…and ${matching.length-8} more</li>`:''}</ul>`;
  }

  function renderGoals(records, range, goalStatus) {
    const allLogs = records.filter(r => r.projectType === 'goal' && r.recordType === 'goal-log');
    const defs    = records.filter(r => r.projectType === 'goal' && r.recordType === 'goal-def');

    // Streak-specific answer
    if (goalStatus === 'streak') {
      if (!defs.length) return renderEmpty();
      const streakLines = defs
        .map(d => ({ title: d.title, streak: +d.metrics.streak || 0 }))
        .sort((a,b) => b.streak - a.streak);
      const best = streakLines[0];
      return `<h4>Goal streaks${range ? ' — '+_esc(range.label) : ''}</h4>`+
        `<ul><li>Best: <strong>${_esc(best.title)}</strong> — <span class="sa-num">${best.streak}</span> day streak 🔥</li></ul>`+
        `<div class="sa-sep"></div><strong>All goals</strong>`+
        `<ul>${streakLines.map(s => `<li>${_esc(s.title)}: <span class="sa-num">${s.streak}</span> 🔥</li>`).join('')}</ul>`;
    }

    if (!allLogs.length) return renderEmpty();

    // Filter by status if goalStatus specified
    let logs = allLogs;
    if (goalStatus === 'completed') logs = allLogs.filter(r => (r.metrics.status || '') === 'completed' || (+r.metrics.done||0) >= (+r.metrics.total||1));
    if (goalStatus === 'missed')    logs = allLogs.filter(r => (r.metrics.status || '') === 'missed'    || ((+r.metrics.done||0) === 0 && (+r.metrics.miss||0) > 0));

    if (!logs.length) {
      const label = goalStatus === 'completed' ? 'completed goals' : goalStatus === 'missed' ? 'missed goals' : 'goal activity';
      return `<h4>${_esc(range ? range.label : 'All time')} — No ${label} found</h4><div class="sa-muted">No matching goal records for this period.</div>`;
    }

    const total    = logs.reduce((s,r) => s + (+r.metrics.total||0), 0);
    const done     = logs.reduce((s,r) => s + (+r.metrics.done||0), 0);
    const miss     = logs.reduce((s,r) => s + (+r.metrics.miss||0), 0);
    const uniqDays = new Set(logs.map(l => l.date).filter(Boolean)).size;
    const rate     = total ? Math.round(done/total*100) : 0;
    const byGoal   = _groupBy(logs, l => l.title);
    const lines    = Object.keys(byGoal).map(name => {
      const arr = byGoal[name];
      const d   = arr.reduce((s,r) => s + (+r.metrics.done||0), 0);
      const t   = arr.reduce((s,r) => s + (+r.metrics.total||0), 0);
      const mm  = arr.reduce((s,r) => s + (+r.metrics.miss||0), 0);
      const def = defs.find(x => x.title === name);
      const sk  = def ? (+(def.metrics.streak)||0) : 0;
      return `<li><strong>${_esc(name)}</strong>: ${d}/${t} done (${t ? Math.round(d/t*100) : 0}%)${mm ? ', '+mm+' missed' : ''}${sk ? ' · 🔥'+sk+' streak' : ''}</li>`;
    }).join('');

    const header = goalStatus === 'completed' ? 'Completed goals'
      : goalStatus === 'missed' ? 'Missed goals' : 'Goals';

    return `<h4>${header} — ${_esc(range ? range.label : 'all time')}</h4>`+
      `<ul>`+
        `<li><span class="sa-num">${done}</span>/${total} done · <span class="sa-num">${rate}%</span> completion rate</li>`+
        `<li>Across <span class="sa-num">${uniqDays}</span> day${uniqDays===1?'':'s'}${miss ? ', '+miss+' missed' : ''}</li>`+
      `</ul>`+
      `<div class="sa-sep"></div><strong>By goal</strong><ul>${lines}</ul>`;
  }

  function renderGold() {
    const settings = Store.settings() || {};
    const holdings = settings.goldHoldings || [];
    const prices = settings.goldPrices || null;
    const updatedAt = settings.goldPricesUpdatedAt || null;
    const updatedDisplay = updatedAt ? new Date(updatedAt).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : null;

    if (!holdings.length && !prices) {
      return '<p>You have no gold holdings recorded yet. Go to Finance → Accounts tab → Gold Holdings to add some.</p>';
    }

    const stats = typeof goldTotalStats === 'function' ? goldTotalStats(holdings, prices) : null;
    let html = '<h3>🥇 Gold Holdings</h3>';

    // Price table
    if (prices && Object.keys(prices).length > 0) {
      html += '<p><strong>Current gold prices:</strong></p><ul>';
      const KARATS = ['24k','21k','18k','14k'];
      KARATS.forEach(k => {
        if (prices[k]) html += `<li>${_esc(k)}: <strong>${Math.round(prices[k]).toLocaleString()} EGP/gram</strong></li>`;
      });
      html += '</ul>';
      if (updatedDisplay) html += `<p style="font-size:0.85em;opacity:0.7">Last updated: ${_esc(updatedDisplay)}</p>`;
    }

    // Stats
    if (stats) {
      html += '<p><strong>Summary:</strong></p><ul>';
      html += `<li>Total weight: <strong>${stats.totalWeight.toFixed(2)} grams</strong></li>`;
      if (stats.totalCurrentValue != null)  html += `<li>Current value: <strong>${Math.round(stats.totalCurrentValue).toLocaleString()} EGP</strong></li>`;
      if (stats.totalPurchaseValue != null) html += `<li>Purchase value: <strong>${Math.round(stats.totalPurchaseValue).toLocaleString()} EGP</strong></li>`;
      if (stats.profitLoss != null) {
        const pl = stats.profitLoss;
        const sign = pl >= 0 ? '+' : '';
        html += `<li>Profit/Loss: <strong style="color:${pl >= 0 ? '#4FA862' : '#C25A4E'}">${sign}${Math.round(pl).toLocaleString()} EGP</strong></li>`;
      }
      html += '</ul>';
    }

    // Holdings breakdown
    if (holdings.length > 0) {
      html += '<p><strong>Holdings:</strong></p><ul>';
      [...holdings].sort((a, b) => (b.date||'').localeCompare(a.date||'')).forEach(h => {
        const cv = (prices && prices[h.karat]) ? +(prices[h.karat] * (h.weightGrams||0)).toFixed(0) : null;
        html += `<li>${_esc(h.karat||'?')} — ${(h.weightGrams||0).toFixed(2)}g`;
        if (cv != null) html += ` · ${cv.toLocaleString()} EGP`;
        if (h.date) html += ` (${_esc(h.date)})`;
        if (h.notes) html += ` — <em>${_esc(h.notes)}</em>`;
        html += '</li>';
      });
      html += '</ul>';
    }

    return html;
  }

  // Raw finance txns with EGP-equivalents resolved (active user only)
  function _finTxnsEgp() {
    const raw = Store.finance ? Store.finance() : [];
    const usd = (Store.settings() || {}).usdEgpRate || null;
    return raw.map(e => {
      const kind = e.kind || 'expense';
      const amt = Math.abs(+e.amount || 0);
      let egp = null;
      if (kind !== 'transfer') {
        if (!e.currency || e.currency === 'EGP') egp = amt;
        else if (e.egpEquivalent) egp = +e.egpEquivalent;
        else if (e.exchangeRate > 0) egp = +(amt * e.exchangeRate).toFixed(2);
        else if (e.currency === 'USD' && usd) egp = +(amt * usd).toFixed(2);
        else egp = amt;
      }
      return { ...e, kind, egpEquivalent: egp };
    });
  }

  function renderProject(subject) {
    const projects = (Store.financeProjects && Store.financeProjects()) || [];
    if (!projects.length) return '<h4>📁 Projects</h4><p>You have no projects yet. Go to Finance → Plan → Projects to add one.</p>';
    const txns = _finTxnsEgp();
    const _m = n => Math.round(n).toLocaleString();
    if (subject) {
      const s = subject.toLowerCase();
      const proj = projects.find(p => (p.name || '').toLowerCase().includes(s));
      if (!proj) return `<h4>📁 Projects</h4><p>I couldn't find a project matching "${_esc(subject)}". Your projects: ${projects.map(p => _esc(p.name)).join(', ')}.</p>`;
      const st = window.financeProjectStats(proj, txns);
      const _pids = t => (Array.isArray(t.projectIds) ? t.projectIds : (t.project ? [t.project] : []));
      const linked = txns.filter(t => _pids(t).indexOf(proj.id) >= 0 && t.kind === 'expense').sort((a, b) => (b.date || '').localeCompare(a.date || ''));
      let html = `<h4>📁 ${_esc(proj.name)}</h4><p class="sa-muted">All linked expenses (project lifetime)</p><ul>`;
      html += `<li>Spent: <span class="sa-num">${_m(st.spent)}</span> EGP across ${st.count} expense${st.count === 1 ? '' : 's'}</li>`;
      if (st.income > 0) html += `<li>Income: <span class="sa-num">${_m(st.income)}</span> EGP</li>`;
      if (st.budget != null) {
        html += `<li>Budget: <span class="sa-num">${_m(st.budget)}</span> EGP</li>`;
        html += `<li>${st.remaining < 0 ? 'Over budget by' : 'Remaining'}: <strong style="color:${st.remaining < 0 ? '#C25A4E' : '#4FA862'}">${_m(Math.abs(st.remaining))} EGP</strong></li>`;
      }
      html += '</ul>';
      if (linked.length) html += `<div class="sa-sep"></div><strong>Matching expenses</strong><ul>${linked.slice(0, 8).map(t => `<li><strong>${_esc(t.date || '')}</strong> · ${_esc(t.title || '')}: <span class="sa-num">${_m(t.egpEquivalent || Math.abs(t.amount))}</span></li>`).join('')}${linked.length > 8 ? `<li class="sa-muted">…and ${linked.length - 8} more</li>` : ''}</ul>`;
      return html;
    }
    const rows = projects.map(p => ({ p, st: window.financeProjectStats(p, txns) })).sort((a, b) => b.st.spent - a.st.spent);
    let html = `<h4>📁 Projects</h4><p class="sa-muted">Spending per project (lifetime)</p><ul>`;
    rows.forEach(({ p, st }) => {
      html += `<li><strong>${_esc(p.name)}</strong>: <span class="sa-num">${_m(st.spent)}</span> EGP`;
      if (st.budget != null) html += ` <span class="sa-muted">/ ${_m(st.budget)} budget · ${st.remaining < 0 ? 'over' : _m(st.remaining) + ' left'}</span>`;
      html += ` <span class="sa-muted">(${st.count})</span></li>`;
    });
    html += '</ul>';
    if (rows.length && rows[0].st.spent > 0) html += `<p>Top project: <strong>${_esc(rows[0].p.name)}</strong></p>`;
    return html;
  }

  function renderFixed(range, view) {
    const fixed = (Store.financeFixed && Store.financeFixed()) || [];
    if (!fixed.length) return '<h4>🧾 Fixed expenses &amp; installments</h4><p>You have no fixed expenses yet. Go to Finance → Plan → Fixed &amp; Installments to add one.</p>';
    const usd = (Store.settings() || {}).usdEgpRate || null;
    const today = window._finIso(new Date());
    const stats = window.fixedExpenseStats(fixed, { todayISO: today, usdRate: usd });
    const schedule = window.fixedScheduleList(fixed, { todayISO: today });
    const _m = n => Math.round(n).toLocaleString();

    if (view === 'overdue') {
      const od = schedule.filter(r => r.status === 'overdue');
      let html = `<h4>Overdue fixed payments</h4>`;
      if (!od.length) return html + '<p>Nothing overdue right now. 🎉</p>';
      html += `<ul><li>Total overdue: <strong style="color:#C25A4E">${_m(stats.overdue)} EGP</strong> across ${od.length} payment${od.length === 1 ? '' : 's'}</li></ul>`;
      html += `<div class="sa-sep"></div><strong>Payments</strong><ul>${od.slice(0, 10).map(r => `<li><strong>${_esc(r.date)}</strong> · ${_esc(r.name)}: <span class="sa-num">${_m(+r.amount || 0)}</span>${r.currency && r.currency !== 'EGP' ? ' ' + _esc(r.currency) : ''}</li>`).join('')}</ul>`;
      return html;
    }
    if (view === 'upcoming') {
      const to = window._finAddByFreq(today, 'daily', null, 7);
      const up = schedule.filter(r => r.status !== 'paid' && r.date >= today && r.date <= to);
      let html = `<h4>Fixed payments due this week</h4><p class="sa-muted">${_esc(today)} → ${_esc(to)}</p>`;
      if (!up.length) return html + '<p>Nothing due in the next 7 days. 🎉</p>';
      const total = up.reduce((s, r) => s + window.fixedToEgp(r.amount, r.currency, usd), 0);
      html += `<ul><li>Total due: <span class="sa-num">${_m(total)}</span> EGP across ${up.length}</li></ul>`;
      html += `<div class="sa-sep"></div><ul>${up.map(r => `<li><strong>${_esc(r.date)}</strong> · ${_esc(r.name)}: <span class="sa-num">${_m(+r.amount || 0)}</span></li>`).join('')}</ul>`;
      return html;
    }
    if (view === 'remaining') {
      let html = `<h4>Remaining installments</h4>`;
      html += `<ul><li>Total remaining value: <strong>${_m(stats.remainingInstallments)} EGP</strong></li></ul>`;
      const withInst = fixed.filter(f => f.installmentsTotal);
      if (withInst.length) html += `<div class="sa-sep"></div><strong>By plan</strong><ul>${withInst.map(f => { const rc = window.fixedRemainingCount(f); const paid = window.fixedPaidCount(f); return `<li>${_esc(f.name)}: <span class="sa-num">${paid}/${f.installmentsTotal} paid</span>, ${rc} left · ${_m((rc || 0) * window.fixedToEgp(f.amount, f.currency, usd))} EGP</li>`; }).join('')}</ul>`;
      return html;
    }
    if (view === 'biggest') {
      const rows = fixed.filter(f => (f.status || 'active') === 'active').map(f => ({ f, mo: window.fixedMonthlyEgp(f, usd) })).sort((a, b) => b.mo - a.mo).slice(0, 8);
      let html = `<h4>Biggest fixed commitments</h4><p class="sa-muted">Normalized to monthly</p><ul>`;
      rows.forEach(({ f, mo }) => { html += `<li><strong>${_esc(f.name)}</strong>: <span class="sa-num">${_m(mo)}</span> EGP/mo <span class="sa-muted">(${_esc(f.frequency || 'monthly')})</span></li>`; });
      html += `</ul><p>Total monthly commitment: <strong>${_m(stats.monthlyCommitment)} EGP</strong></p>`;
      return html;
    }
    // default — this month overview
    let html = `<h4>Fixed expenses — ${_esc(range ? range.label : 'this month')}</h4><ul>`;
    html += `<li>Due this month: <span class="sa-num">${_m(stats.monthTotal)}</span> EGP</li>`;
    html += `<li>Paid: <span class="sa-num" style="color:#4FA862">${_m(stats.monthPaid)}</span> · Unpaid: <span class="sa-num" style="color:#E89B3C">${_m(stats.monthUnpaid)}</span></li>`;
    if (stats.overdue > 0) html += `<li>Overdue: <strong style="color:#C25A4E">${_m(stats.overdue)} EGP</strong> (${stats.overdueCount})</li>`;
    html += `<li>Monthly commitment: <span class="sa-num">${_m(stats.monthlyCommitment)}</span> EGP</li>`;
    if (stats.remainingInstallments > 0) html += `<li>Remaining installments: <span class="sa-num">${_m(stats.remainingInstallments)}</span> EGP</li>`;
    html += '</ul>';
    const catEntries = Object.entries(stats.byCategory).sort((a, b) => b[1] - a[1]);
    if (catEntries.length) {
      const cats = (Store.financeCategories && Store.financeCategories()) || [];
      const nameOf = id => (cats.find(c => c.id === id) || {}).name || id;
      html += `<div class="sa-sep"></div><strong>By category</strong><ul>${catEntries.map(([id, v]) => `<li>${_esc(nameOf(id))}: <span class="sa-num">${_m(v)}</span></li>`).join('')}</ul>`;
    }
    const up = schedule.filter(r => r.status !== 'paid').slice(0, 6);
    if (up.length) html += `<div class="sa-sep"></div><strong>Next payments</strong><ul>${up.map(r => `<li><strong>${_esc(r.date)}</strong> · ${_esc(r.name)}: <span class="sa-num">${_m(+r.amount || 0)}</span> <span class="sa-muted">${_esc(r.status)}</span></li>`).join('')}</ul>`;
    return html;
  }

  function renderMovies(intent) {
    const movies = (Store.movies && Store.movies()) || [];
    const range = intent.range;
    if (!movies.length) {
      return '<h4>🎬 Movie Night</h4><p>Your movie library is empty. Open Movie Night → Browse to discover films, or add one manually.</p>';
    }
    // Self-contained helpers (userRating may be legacy 1–10 or new 1–5 → 1–5 star value).
    const _star = m => { const r = m && m.userRating; if (r == null || r === '') return 0; const n = +r; if (isNaN(n)) return 0; return n > 5 ? Math.max(1, Math.min(5, Math.round(n / 2))) : Math.max(1, Math.min(5, Math.round(n))); };
    const _liked = m => !!(m && (m.liked || _star(m) === 5));
    const _rate = m => m.imdbRating != null ? ' · <span style="color:#E0A911">IMDb ' + (+m.imdbRating).toFixed(1) + '</span>' : (m.tmdbRating != null ? ' · ★ ' + (+m.tmdbRating).toFixed(1) : '');
    const _line = m => { const s = _star(m); return `<li><strong>${_esc(m.title || 'Untitled')}</strong>${m.year ? ' <span class="sa-muted">(' + m.year + ')</span>' : ''}${_rate(m)}${s ? ' · <span style="color:#E89B3C">' + s + '★</span>' : ''}${m.watchedDate ? ' · <span style="color:#4FA862">✓ ' + _esc(m.watchedDate) + '</span>' : ''}</li>`; };
    const watchedAll = movies.filter(m => m.watchedDate);
    const likedAll = movies.filter(_liked);
    const watchlistAll = movies.filter(m => m.watchlist && !m.watchedDate);
    const fiveStarAll = movies.filter(m => _star(m) === 5);
    const unratedWatched = movies.filter(m => m.watchedDate && _star(m) === 0);
    const view = intent.movieView || 'overview';

    // Date-scoped: "what did I watch / add on <date>"
    if (range && (view === 'overview' || view === 'watched')) {
      const watchedIn = watchedAll.filter(m => m.watchedDate >= range.from && m.watchedDate <= range.to).sort((a, b) => (b.watchedDate || '').localeCompare(a.watchedDate || ''));
      const addedIn = movies.filter(m => { const d = m.addedDate || m.importedDate || ''; return d && d >= range.from && d <= range.to; });
      let html = `<h4>🎬 Movies — ${_esc(range.label || '')}</h4><ul><li>Watched: <span class="sa-num">${watchedIn.length}</span></li>`;
      if (addedIn.length) html += `<li>Added/imported: <span class="sa-num">${addedIn.length}</span></li>`;
      html += '</ul>';
      if (watchedIn.length) html += `<div class="sa-sep"></div><strong>Watched</strong><ul>${watchedIn.slice(0, 12).map(_line).join('')}</ul>`;
      else html += '<p class="sa-muted">No movies watched in this period.</p>';
      return html;
    }
    if (view === 'rated5') return `<h4>⭐ Your 5★ movies</h4><p class="sa-muted">${fiveStarAll.length} rated 5 stars</p><ul>${fiveStarAll.slice(0, 25).map(_line).join('') || '<li class="sa-muted">No 5★ ratings yet.</li>'}</ul>`;
    if (view === 'unrated') {
      let html = `<h4>⭐ Watched movies to rate</h4><p>You have <strong>${unratedWatched.length}</strong> watched movie${unratedWatched.length === 1 ? '' : 's'} without a rating.</p>`;
      if (unratedWatched.length) html += `<p class="sa-muted">Open Movie Night → Watched to rate them quickly.</p><ul>${unratedWatched.slice(0, 10).map(_line).join('')}</ul>`;
      return html;
    }
    if (view === 'rate-queue') {
      const queue = [...unratedWatched].sort((a, b) => ((a.watchedDate || a.importedDate || a.addedDate || '').localeCompare(b.watchedDate || b.importedDate || b.addedDate || ''))).slice(0, 5);
      if (!queue.length) return '<h4>⭐ Nothing to rate</h4><p>Every watched movie already has a rating. 🎉</p>';
      return `<h4>⭐ 5 watched movies to rate</h4><p class="sa-muted">Rate these in Movie Night → Watched:</p><ul>${queue.map(_line).join('')}</ul>`;
    }
    if (view === 'liked') return `<h4>❤️ Liked movies</h4><p class="sa-muted">${likedAll.length} liked (incl. 5★)</p><ul>${likedAll.slice(0, 25).map(_line).join('') || '<li class="sa-muted">None yet.</li>'}</ul>`;
    if (view === 'watchlist') return `<h4>🔖 Watchlist</h4><p class="sa-muted">${watchlistAll.length} to watch</p><ul>${watchlistAll.slice(0, 20).map(_line).join('') || '<li class="sa-muted">Empty.</li>'}</ul>`;
    if (view === 'watched') { const recent = [...watchedAll].sort((a, b) => (b.watchedDate || '').localeCompare(a.watchedDate || '')); return `<h4>👁️ Watched movies</h4><p>You've watched <strong>${watchedAll.length}</strong> movie${watchedAll.length === 1 ? '' : 's'} (${unratedWatched.length} unrated).</p><ul>${recent.slice(0, 15).map(_line).join('')}</ul>`; }
    if (view === 'disliked') { const d = movies.filter(m => m.disliked); return `<h4>👎 Disliked</h4><p>${d.length} disliked.</p><ul>${d.slice(0, 15).map(_line).join('')}</ul>`; }
    if (view === 'missing-posters') {
      const miss = movies.filter(m => !m.posterUrl && !m.posterPath);
      let html = `<h4>🖼️ Movies missing posters</h4><p><strong>${miss.length}</strong> of ${movies.length} movie${movies.length === 1 ? '' : 's'} ${miss.length === 1 ? 'has' : 'have'} no poster.</p>`;
      if (miss.length) html += `<p class="sa-muted">Fix them in Movie Night → Settings → Repair Missing Posters.</p><ul>${miss.slice(0, 15).map(_line).join('')}</ul>`;
      return html;
    }
    if (view === 'imdb-status') {
      const withR = movies.filter(m => m.imdbRating != null).length;
      const noId = movies.filter(m => !m.imdbId).length;
      return `<h4>⭐ IMDb rating sync</h4><ul><li>With IMDb rating: <span class="sa-num">${withR}</span></li><li>Missing IMDb rating: <span class="sa-num">${movies.length - withR}</span></li><li>No IMDb id yet: <span class="sa-num">${noId}</span></li></ul><p class="sa-muted">Sync them in Movie Night → Settings → Sync IMDb Ratings.</p>`;
    }
    if (view === 'memories') {
      const linkCount = m => (Store.movieDiaryLinks ? Store.movieDiaryLinks(m) : (m.linkedDiaryMemoryIds || [])).length;
      const linked = movies.filter(m => linkCount(m) > 0);
      if (!linked.length) return '<h4>🎬📓 Movies &amp; memories</h4><p>No watched movies match a diary memory date yet.</p>';
      return `<h4>🎬📓 Movies connected to memories</h4><ul>${linked.slice(0, 15).map(m => { const n = linkCount(m); return `<li><strong>${_esc(m.title)}</strong> · ${_esc(m.watchedDate || '')} · <span class="sa-muted">${n} memor${n === 1 ? 'y' : 'ies'}</span></li>`; }).join('')}</ul>`;
    }
    if (view === 'why') {
      const seeds = [...fiveStarAll, ...likedAll].filter((m, i, a) => a.indexOf(m) === i).slice(0, 5);
      const gcount = {}; movies.filter(m => _liked(m) || m.watchedDate).forEach(m => (m.genres || []).forEach(g => gcount[g] = (gcount[g] || 0) + 1));
      const topG = Object.entries(gcount).sort((a, b) => b[1] - a[1]).slice(0, 3).map(x => x[0]);
      let html = `<h4>🎬 How recommendations work</h4><p>For You is built from your strongest taste signals:</p><ul>`;
      html += `<li><strong>Liked</strong> & <strong>5★</strong> movies → TMDb “similar” + “recommended”</li>`;
      html += `<li><strong>4★</strong> and watched movies → general taste profile</li>`;
      html += `<li>Always excludes movies you’ve <strong>watched</strong> or <strong>disliked</strong></li></ul>`;
      if (seeds.length) html += `<div class="sa-sep"></div><strong>Top seed movies</strong><ul>${seeds.map(_line).join('')}</ul>`;
      if (topG.length) html += `<p class="sa-muted">Your favourite genres: ${topG.map(_esc).join(', ')}.</p>`;
      return html;
    }
    if (intent.ratingGte != null) {
      const g = intent.ratingGte;
      // > 5 → TMDb score filter; ≤ 5 → personal star filter.
      const pool = g > 5
        ? movies.filter(m => m.tmdbRating != null && m.tmdbRating >= g).sort((a, b) => (b.tmdbRating || 0) - (a.tmdbRating || 0))
        : movies.filter(m => _star(m) >= g).sort((a, b) => _star(b) - _star(a));
      const label = g > 5 ? ('TMDb ★ ' + g + '+') : ('your ' + g + '★+');
      return `<h4>🎬 Rated ${_esc(label)}</h4><ul>${pool.slice(0, 20).map(_line).join('') || '<li class="sa-muted">None at that rating.</li>'}</ul>`;
    }
    if (view === 'genre' && intent.subject) {
      const g = intent.subject.toLowerCase().replace(/comedies/, 'comedy').replace(/documentaries/, 'documentary').replace(/animated/, 'animation').replace(/romantic/, 'romance').replace(/sci-?fi|science fiction/, 'science fiction');
      let pool = movies.filter(m => (m.genres || []).some(x => x.toLowerCase().includes(g)));
      if (intent.unwatched) pool = pool.filter(m => !m.watchedDate);
      return `<h4>🎬 ${_esc(intent.subject)}${intent.unwatched ? " you haven't watched" : ''}</h4><p class="sa-muted">${pool.length} in your library</p><ul>${pool.slice(0, 20).map(_line).join('') || '<li class="sa-muted">None in your library — try Browse with this genre filter.</li>'}</ul>`;
    }
    if (view === 'similar' && intent.subject) {
      const seed = movies.find(m => (m.title || '').toLowerCase().includes(intent.subject.toLowerCase()));
      if (seed && (seed.genres || []).length) {
        const gset = new Set(seed.genres);
        const pool = movies.filter(m => m.id !== seed.id && !m.watchedDate && !m.disliked && (m.genres || []).some(x => gset.has(x))).sort((a, b) => (b.tmdbRating || 0) - (a.tmdbRating || 0));
        return `<h4>🎬 Similar to ${_esc(seed.title)}</h4><p class="sa-muted">From your library — open ${_esc(seed.title)} in Movie Night for full TMDb picks.</p><ul>${pool.slice(0, 8).map(_line).join('') || '<li class="sa-muted">Nothing similar in your library yet — Browse for more.</li>'}</ul>`;
      }
      return `<h4>🎬 Similar movies</h4><p>I couldn't find "${_esc(intent.subject)}" in your library. Open it in Movie Night → Browse to see TMDb's similar picks.</p>`;
    }
    if (view === 'suggest') {
      let pool = movies.filter(m => !m.watchedDate && !m.disliked);
      const watch = pool.filter(m => m.watchlist);
      const base = (watch.length ? watch : pool).slice().sort((a, b) => (b.tmdbRating || 0) - (a.tmdbRating || 0));
      if (!base.length) return "<h4>🍿 Tonight's pick</h4><p>No unwatched movies in your library. Open Movie Night → Browse or Cards to find something new.</p>";
      const pick = base[0];
      let html = `<h4>🍿 Tonight, try: ${_esc(pick.title)}</h4><p>${pick.year ? pick.year + ' · ' : ''}${(pick.genres || []).slice(0, 3).join(', ')}${pick.tmdbRating != null ? ' · ★ ' + (+pick.tmdbRating).toFixed(1) : ''}</p>`;
      if (pick.overview) html += `<p class="sa-muted">${_esc(pick.overview.slice(0, 180))}${pick.overview.length > 180 ? '…' : ''}</p>`;
      if (base.length > 1) html += `<div class="sa-sep"></div><strong>More ideas</strong><ul>${base.slice(1, 6).map(_line).join('')}</ul>`;
      return html;
    }
    // overview
    const topGenre = {};
    movies.forEach(m => (m.genres || []).forEach(g => topGenre[g] = (topGenre[g] || 0) + 1));
    const genres = Object.entries(topGenre).sort((a, b) => b[1] - a[1]).slice(0, 5);
    let html = `<h4>🎬 Movie Night</h4><ul><li>Library: <span class="sa-num">${movies.length}</span> movies</li>`;
    html += `<li>Watched: <span class="sa-num">${watchedAll.length}</span> · Liked: <span class="sa-num">${likedAll.length}</span> · 5★: <span class="sa-num">${fiveStarAll.length}</span> · Watchlist: <span class="sa-num">${watchlistAll.length}</span></li>`;
    if (unratedWatched.length) html += `<li><span class="sa-num">${unratedWatched.length}</span> watched movies need a rating</li>`;
    html += '</ul>';
    if (genres.length) html += `<div class="sa-sep"></div><strong>Top genres</strong><ul>${genres.map(([g, c]) => `<li>${_esc(g)}: <span class="sa-num">${c}</span></li>`).join('')}</ul>`;
    return html;
  }

  function _ibHistPrev(e) { return { weight: e.previousWeight, skeletalMuscleMass: e.previousSkeletalMuscleMass, bodyFatMass: e.previousBodyFatMass, pbf: e.previousPBF, visceralFatLevel: e.previousVisceralFatLevel }; }
  function renderInBody(intent) {
    const entries = (Store.inBodyEntries ? Store.inBodyEntries() : []).slice().sort((a, b) => (a.date || '').localeCompare(b.date || ''));
    if (!entries.length) return '<h4>🧬 InBody</h4><p>No InBody scans saved yet. Open Calories → InBody → Add to paste your first scan.</p>';
    const latest = entries[entries.length - 1];
    const prev = entries.length > 1 ? entries[entries.length - 2] : null;
    const view = intent.inbodyView || 'overview';
    const _d = iso => { try { return new Date(iso + 'T00:00:00').toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); } catch (_) { return iso; } };

    if (view === 'pbf') return `<h4>🧬 Body fat %</h4><p>Your latest PBF (${_esc(_d(latest.date))}) is <strong>${latest.pbf != null ? latest.pbf + '%' : 'not recorded'}</strong>.${(prev && prev.pbf != null && latest.pbf != null) ? ' Previous: ' + prev.pbf + '% (' + ((latest.pbf - prev.pbf) >= 0 ? '+' : '') + (+(latest.pbf - prev.pbf).toFixed(1)) + ').' : ''}</p>`;
    if (view === 'visceral') return `<h4>🧬 Visceral fat</h4><p>Latest visceral fat level: <strong>${latest.visceralFatLevel != null ? latest.visceralFatLevel : '—'}</strong> (${_esc(_d(latest.date))}).${(latest.previousVisceralFatLevel != null && latest.currentVisceralFatLevel != null) ? ' Changed ' + latest.previousVisceralFatLevel + ' → ' + latest.currentVisceralFatLevel + '.' : (prev && prev.visceralFatLevel != null ? ' Previous: ' + prev.visceralFatLevel + '.' : '')}</p>`;
    if (view === 'compare' || view === 'changed' || view === 'fatmuscle') {
      const base = prev || _ibHistPrev(latest);
      const rows = (window.inBodyCompare ? window.inBodyCompare(base, latest) : []).filter(r => r.prev != null && r.cur != null);
      if (!rows.length) return '<h4>🧬 InBody</h4><p>Only one scan and no built-in history — add another scan to compare.</p>';
      let html = `<h4>🧬 InBody change</h4><p class="sa-muted">${prev ? _esc(_d(prev.date)) + ' → ' + _esc(_d(latest.date)) : 'vs previous (from scan history)'}</p><ul>`;
      rows.forEach(r => { const col = r.direction === 'improved' ? '#4FA862' : r.direction === 'worse' ? '#C25A4E' : 'var(--muted)'; html += `<li>${_esc(r.label)}: ${r.prev} → <strong>${r.cur}</strong>${r.unit ? ' ' + r.unit : ''} <span style="color:${col}">(${r.change > 0 ? '+' : ''}${r.change}${r.unit ? ' ' + r.unit : ''})</span></li>`; });
      html += '</ul>';
      if (view === 'fatmuscle') { const fm = rows.find(r => r.key === 'bodyFatMass'), mm = rows.find(r => r.key === 'skeletalMuscleMass'); html += `<p>${fm ? (fm.change < 0 ? 'You lost <strong>' + Math.abs(fm.change) + ' kg</strong> of fat' : 'Fat changed ' + (fm.change > 0 ? '+' : '') + fm.change + ' kg') : ''}${mm ? ' · muscle ' + (mm.change > 0 ? '+' : '') + mm.change + ' kg' : ''}.</p>`; }
      return html;
    }
    if (view === 'muscle') {
      const base = prev || _ibHistPrev(latest);
      const r = (window.inBodyCompare ? window.inBodyCompare(base, latest) : []).find(x => x.key === 'skeletalMuscleMass');
      if (!r || r.cur == null || r.prev == null) return `<h4>🧬 Muscle</h4><p>Latest skeletal muscle mass: <strong>${latest.skeletalMuscleMass != null ? latest.skeletalMuscleMass + ' kg' : '—'}</strong>.</p>`;
      const ok = r.change >= -0.5;
      return `<h4>🧬 Muscle preservation</h4><p>Skeletal muscle ${r.prev} → <strong>${r.cur}</strong> kg (${r.change > 0 ? '+' : ''}${r.change}). ${ok ? "You're preserving muscle well." : 'Muscle dropped — prioritise protein and resistance training to rebuild it.'}</p>`;
    }
    if (view === 'focus') {
      const items = window.inBodyAnalysisText ? window.inBodyAnalysisText(latest, null) : [];
      if (!items.length) return '<h4>🧬 Focus</h4><p>Add an InBody scan to get tailored guidance.</p>';
      return `<h4>🧬 What to focus on</h4><ul>${items.map(it => `<li><strong>${_esc(it.t)}:</strong> ${_esc(it.body)}</li>`).join('')}</ul>`;
    }
    let html = `<h4>🧬 Latest InBody — ${_esc(_d(latest.date))}</h4><ul>`;
    if (latest.weight != null) html += `<li>Weight: <span class="sa-num">${latest.weight}</span> kg</li>`;
    if (latest.pbf != null) html += `<li>Body fat: <span class="sa-num">${latest.pbf}%</span></li>`;
    if (latest.skeletalMuscleMass != null) html += `<li>Skeletal muscle: <span class="sa-num">${latest.skeletalMuscleMass}</span> kg</li>`;
    if (latest.bodyFatMass != null) html += `<li>Body fat mass: <span class="sa-num">${latest.bodyFatMass}</span> kg</li>`;
    if (latest.visceralFatLevel != null) html += `<li>Visceral fat: <span class="sa-num">${latest.visceralFatLevel}</span></li>`;
    if (latest.inBodyScore != null) html += `<li>InBody score: <span class="sa-num">${latest.inBodyScore}</span></li>`;
    html += '</ul>';
    return html;
  }

  function renderClarification(intent) {
    return `<h4>I'm not sure what you're asking</h4>`+
      `<p>${_esc(intent.clarification || 'Could you rephrase your question?')}</p>`+
      `<p class="sa-muted">Try one of the quick chips above, or ask something like:</p>`+
      `<ul>`+
        `<li>"What happened on May 22, 2026?"</li>`+
        `<li>"Calories today"</li>`+
        `<li>"How much did I spend on food this month?"</li>`+
        `<li>"How many days did I feel good this month?"</li>`+
        `<li>"Days tagged gym this month"</li>`+
        `<li>"On this day"</li>`+
        `<li>"How many days mention Kareem?"</li>`+
        `<li>"Search for pizza"</li>`+
      `</ul>`;
  }

  // ─── MAIN ENTRY ──────────────────────────────────────────────
  function answer(query, opts) {
    const activeUid = Store.uid();
    const records = AssistantAdapter.getRecordsForUser(activeUid);
    const intent = (opts && opts.intent) || parseIntent(query);
    const range = (opts && opts.range) || intent.range || null;
    const scoped = range ? records.filter(r => _inRange(r, range)) : records;
    switch (intent.type) {
      case 'spending':       return renderSpending(scoped, range, intent.subject);
      case 'income':         return renderIncome(scoped, range);
      case 'fixing':         return renderFixingData(records, range);
      case 'balance':        return renderBalance(records, (intent.accountHint||'').toLowerCase());
      case 'transfers':      return renderTransfers(scoped, range);
      case 'project':        return renderProject(intent.subject);
      case 'fixed':          return renderFixed(range, intent.fixedView);
      case 'gold':           return renderGold();
      case 'inbody':         return renderInBody(intent);
      case 'movies':         return renderMovies(intent);
      case 'calories':
      case 'diet':           return renderCalories(scoped, range);
      case 'goals':          return renderGoals(scoped, range, intent.goalStatus);
      case 'people':              return renderPeople(scoped, range, (intent.people && intent.people.length) ? intent.people : (intent.subject ? [intent.subject] : []));
      case 'places':              return renderPlaces(scoped, range, intent.subject);
      case 'search':              return renderSearch(scoped, range, intent.subject);
      case 'diary':               return renderDiary(scoped, range, query);
      case 'diary-mood':          return renderDiaryMood(scoped, range, intent.moodKey);
      case 'diary-tag':           return renderDiaryTags(scoped, range, intent.tagName);
      case 'diary-on-this-day':   return renderDiaryOnThisDay(records, intent.mmdd); // all years, no range scope
      case 'day-summary':
      case 'range-summary':
      case 'summary':        return renderDaySummary(scoped, range || { label: 'all time', from: '', to: '' });
      case 'unknown':
      default:               return renderClarification(intent);
    }
  }

  // Expose the logic globally so the UI module can use it
  window.SmartAssistant = { answer, parseRange, parseIntent };
})();

/* ════════════════════════════════════════════════════════════════
   React UI — the Assistant screen
   ════════════════════════════════════════════════════════════════ */

const ASSISTANT_QUICK = [
  { label: 'Today summary',        q: 'summary today' },
  { label: 'Choose date',          q: '__pick__' },
  { label: 'This month summary',   q: 'summary this month' },
  { label: 'Calories today',       q: 'calories today' },
  { label: 'Spending this month',  q: 'spending this month' },
  { label: 'Income this month',    q: 'income this month' },
  { label: 'Balance adjustments',  q: 'show balance adjustments' },
  { label: 'Account balances',     q: 'account balances' },
  { label: 'Fixed this month',     q: 'fixed expenses this month' },
  { label: 'Installments due',     q: 'installments due this week' },
  { label: 'Project spending',     q: 'project spending' },
  { label: 'Gold holdings',        q: 'gold holdings' },
  { label: 'Need vs Want',         q: 'spending on need this month' },
  { label: 'Diary this month',     q: 'diary this month' },
  { label: 'On this day',          q: 'on this day' },
  { label: 'Mood breakdown',       q: 'mood breakdown this month' },
  { label: 'Goals today',          q: 'goals today' },
  { label: 'Streaks',              q: 'streak' },
  { label: 'Movie tonight',        q: 'suggest a movie to watch tonight' },
  { label: 'Movies watched',       q: 'how many movies have I watched' },
  { label: 'My 5★ movies',         q: 'show movies I rated 5 stars' },
  { label: 'Movies to rate',       q: 'show me 5 watched movies to rate' },
  { label: 'Liked movies',         q: 'my liked movies' },
  { label: 'Last InBody change',   q: 'what changed in my last inbody' },
  { label: 'Fat or muscle?',       q: 'did I lose fat or muscle' },
  { label: 'My PBF',               q: 'what is my current pbf' },
];

function AssistantScreen() {
  const store = useStore();
  const profile = store.profile() || {};
  const displayName = profile.displayName || 'you';
  const chat = store.assistantChat();

  const [input, setInput] = React.useState('');
  const [showPicker, setShowPicker] = React.useState(false);
  const today = todayISO(); // local timezone — never toISOString() which shifts in UTC+N
  const [dateFrom, setDateFrom] = React.useState(today);
  const [dateTo, setDateTo] = React.useState(today);
  const historyRef = React.useRef(null);

  React.useEffect(() => {
    if (historyRef.current) historyRef.current.scrollTop = historyRef.current.scrollHeight;
  }, [chat.length]);

  function send(query, opts) {
    if (!query || !query.trim()) return;
    store.appendAssistantChat({ role: 'user', text: query.trim(), at: Date.now() });
    const html = SmartAssistant.answer(query.trim(), opts || {});
    store.appendAssistantChat({ role: 'assistant', text: '', html, at: Date.now() });
  }

  function onChipClick(c) {
    if (c.q === '__pick__') { setShowPicker(v => !v); return; }
    send(c.q);
  }

  function runDateRange() {
    if (!dateFrom && !dateTo) return;
    const f = dateFrom || dateTo, t = dateTo || dateFrom;
    const range = { from: f, to: t, label: (f === t || !t) ? (f || t) : (f + ' to ' + t) };
    send('summary ' + range.label, { range });
    setShowPicker(false);
  }

  return (
    <div style={{ padding: '12px 18px 24px', display: 'flex', flexDirection: 'column', gap: 14 }}>
      {/* Header */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', paddingTop: 4, gap: 12 }}>
        <div>
          <div style={{ fontSize: 22, fontWeight: 700, letterSpacing: '-0.02em' }}>Smart Assistant</div>
          <div style={{ fontSize: 12.5, color: 'var(--muted)', marginTop: 2, fontWeight: 500 }}>
            Searching across your data
          </div>
        </div>
        {chat.length > 0 && (
          <button onClick={() => store.clearAssistantChat()} style={{
            padding: '6px 12px', borderRadius: 999,
            background: 'var(--surface)', color: 'var(--muted)',
            border: '1px solid var(--border-2)', cursor: 'pointer', font: 'inherit',
            fontSize: 12, fontWeight: 500,
          }}>Clear chat</button>
        )}
      </div>

      {/* Quick chips */}
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
        {ASSISTANT_QUICK.map((c, i) => (
          <button key={i} onClick={() => onChipClick(c)} style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            padding: '7px 13px', borderRadius: 999,
            background: (c.q === '__pick__' && showPicker) ? 'var(--text)' : 'var(--surface-2)',
            color: (c.q === '__pick__' && showPicker) ? 'var(--bg)' : 'var(--text)',
            border: '1px solid var(--border)',
            font: 'inherit', fontSize: 12, fontWeight: 500, cursor: 'pointer',
            whiteSpace: 'nowrap',
          }}>
            {c.label}
          </button>
        ))}
      </div>

      {/* Date picker row (collapsible) */}
      {showPicker && (
        <div style={{
          ...nutriStyles.card, padding: 14,
          display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap',
        }}>
          <DateField label="From" value={dateFrom} onChange={setDateFrom}/>
          <DateField label="To"   value={dateTo}   onChange={setDateTo}/>
          <button onClick={runDateRange} style={{
            marginLeft: 'auto', padding: '8px 16px', borderRadius: 999,
            background: 'var(--accent)', color: 'var(--on-accent)',
            border: '1px solid var(--accent)', cursor: 'pointer', font: 'inherit',
            fontSize: 12.5, fontWeight: 600,
          }}>Run</button>
        </div>
      )}

      {/* Chat history */}
      <div ref={historyRef} style={{
        ...nutriStyles.card,
        minHeight: 280, maxHeight: 520, overflow: 'auto',
        padding: 14,
        display: 'flex', flexDirection: 'column', gap: 10,
        background: 'var(--surface)',
      }}>
        {chat.length === 0 ? (
          <div style={{ flex: 1, display: 'grid', placeItems: 'center', color: 'var(--muted)', fontSize: 13, textAlign: 'center', padding: 24 }}>
            <div>
              <div style={{ fontSize: 28, marginBottom: 8 }}>✨</div>
              <div style={{ fontWeight: 600, color: 'var(--text)', marginBottom: 4 }}>Ask anything about your data{displayName !== 'you' ? ', ' + displayName : ''}</div>
              <div>Try one of the chips above, or type a question below.</div>
            </div>
          </div>
        ) : chat.map((m, i) => <ChatBubble key={i} message={m}/>)}
      </div>

      {/* Input */}
      <div style={{
        ...nutriStyles.card, padding: 6,
        display: 'flex', alignItems: 'center', gap: 6,
      }}>
        <input value={input} onChange={e => setInput(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter') { send(input); setInput(''); } }}
          placeholder="Ask anything…"
          style={{
            flex: 1, padding: '10px 14px',
            border: 0, background: 'transparent', outline: 'none', font: 'inherit',
            fontSize: 14, color: 'var(--text)',
          }}/>
        <button onClick={() => { send(input); setInput(''); }} style={{
          padding: '10px 18px', borderRadius: 999,
          background: 'var(--accent)', color: 'var(--on-accent)',
          border: '1px solid var(--accent)', cursor: 'pointer', font: 'inherit',
          fontSize: 13, fontWeight: 600,
        }}>Send</button>
      </div>

      <div style={{ height: 60 }}/>
    </div>
  );
}

function DateField({ label, value, onChange }) {
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
      <span style={{ fontSize: 10, color: 'var(--muted)', fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase' }}>{label}</span>
      <input type="date" value={value} onChange={e => onChange(e.target.value)} style={{
        padding: '6px 10px', borderRadius: 8,
        background: 'var(--surface-2)', border: '1px solid var(--border)',
        color: 'var(--text)', font: 'inherit', fontSize: 13,
      }}/>
    </label>
  );
}

function ChatBubble({ message }) {
  const isUser = message.role === 'user';
  if (isUser) {
    return (
      <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
        <div style={{
          maxWidth: '85%',
          padding: '8px 12px', borderRadius: 14,
          background: 'var(--text)', color: 'var(--bg)',
          fontSize: 13.5, lineHeight: 1.45,
          borderBottomRightRadius: 4,
        }}>{message.text}</div>
      </div>
    );
  }
  return (
    <div style={{ display: 'flex', justifyContent: 'flex-start' }}>
      <div className="nutri-assistant-bubble" style={{
        maxWidth: '90%',
        padding: '10px 14px', borderRadius: 14,
        background: 'var(--surface-2)', color: 'var(--text)',
        fontSize: 13, lineHeight: 1.55,
        borderBottomLeftRadius: 4,
        border: '1px solid var(--border)',
      }} dangerouslySetInnerHTML={{ __html: message.html || message.text }}/>
    </div>
  );
}

// Styles for assistant HTML output (h4, ul, sa-num, sa-muted, sa-sep)
(function injectAssistantStyles() {
  if (document.getElementById('nutri-assistant-styles')) return;
  const s = document.createElement('style');
  s.id = 'nutri-assistant-styles';
  s.textContent = `
    .nutri-assistant-bubble h4 { margin: 0 0 6px; font-size: 13.5px; font-weight: 700; letter-spacing: -0.01em; color: var(--text); }
    .nutri-assistant-bubble strong { color: var(--text); font-weight: 600; }
    .nutri-assistant-bubble ul { margin: 4px 0 8px; padding-left: 18px; }
    .nutri-assistant-bubble li { margin: 2px 0; }
    .nutri-assistant-bubble p { margin: 4px 0; }
    .nutri-assistant-bubble .sa-num { color: var(--accent); font-weight: 700; font-variant-numeric: tabular-nums; }
    [data-theme="dark"] .nutri-assistant-bubble .sa-num { color: var(--c-protein); }
    .nutri-assistant-bubble .sa-muted { color: var(--muted); font-size: 12px; }
    .nutri-assistant-bubble .sa-sep { height: 1px; background: var(--border); margin: 8px 0; }
  `;
  document.head.appendChild(s);
})();

Object.assign(window, { AssistantScreen }); // UserSwitcher removed — was never defined in this file
