/* Nutri Paste & Parse — one shared screen, four parsers.
   Designed to accept the simple ChatGPT-style output people typically copy.

   Diet format (auto-detected via "kcal" / "calories" / meal name):
     May 22, 2026
     Breakfast: 420 kcal | carbs 45 | protein 30 | fat 12
       - Oatmeal — 80g — 150 cal | carbs 25g | protein 5g | fat 3g
     Lunch — 650 cal
     Dinner: 580 kcal, 55g carbs, 38g protein, 18g fat
     Note: felt strong today

   Finance format (auto-detected via $ / currency / "spent" / "income"):
     2026-05-22 Lunch out 50 food
     2026-05-22 +5000 salary income
     2026-05-23 Uber 80 transportation
     2026-05-23 Coffee 35 food

   Diary format (auto-detected via "Diary:" prefix or paragraph):
     2026-05-22 — Went out with Kareem and Yasser to Maadi for dinner.
     2026-05-23 — Coffee with Kareem in the morning.

   Goals format (auto-detected via "Goal:" or "habit"):
     Goal: Exercise — 1x/day — done 2026-05-22 — miss 2026-05-23
*/

// ── Template persistence — cloud-backed (settings.pasteTemplates) + localStorage cache ──
// Read merges both (cloud wins, local-only kept) so nothing is lost; the first save pushes
// the merged map up, migrating any old local-only templates to Firebase.
function _tmplKey(uid) { return 'nutri_tmpl_' + (uid || 'anon'); }
function _loadTmpls(uid) {
  let local = {};
  try { const r = localStorage.getItem(_tmplKey(uid)); local = r ? JSON.parse(r) : {}; } catch(_) {}
  let cloud = {};
  try { cloud = (window.Store && Store.pasteTemplates && Store.pasteTemplates()) || {}; } catch(_) {}
  return { ...local, ...cloud };
}
function _saveTmpls(uid, customs) {
  try { localStorage.setItem(_tmplKey(uid), JSON.stringify(customs)); } catch(_) {}
  try { if (window.Store && Store.setPasteTemplates) Store.setPasteTemplates(customs); } catch(_) {}
}

function PasteParseScreen({ projectType }) {
  const Store = window.Store;
  const useStore = window.useStore;
  const DuplicateEntryDialog = window.DuplicateEntryDialog;
  useStore();

  const uid = Store.uid ? Store.uid() : null;

  const [raw, setRaw] = React.useState('');
  const [preview, setPreview] = React.useState(null);
  const [parseSeq, setParseSeq] = React.useState(0); // bumps each parse → remounts editable previews
  const [msg, setMsg] = React.useState(null);
  const [dupPending, setDupPending] = React.useState(null); // { byDate, note, dateLabel, existingCount }

  // Custom (user-edited) templates, keyed by template title
  const [customs, setCustoms] = React.useState(() => _loadTmpls(uid));
  // Reload from localStorage whenever uid changes (e.g. after sign-in)
  React.useEffect(() => { setCustoms(_loadTmpls(uid)); }, [uid]);

  // Template editor state
  const [editingTitle, setEditingTitle] = React.useState(null); // null = closed
  const [editorDraft, setEditorDraft] = React.useState('');

  function openEditor(title, currentText) {
    setEditorDraft(currentText);
    setEditingTitle(title);
  }
  function closeEditor() { setEditingTitle(null); setEditorDraft(''); }
  function saveEditorDraft() {
    const next = { ...customs, [editingTitle]: editorDraft };
    setCustoms(next);
    _saveTmpls(uid, next);
    closeEditor();
  }
  function resetTemplate(title, defaultText) {
    if (!confirm('Reset "' + title + '" to the original default template?')) return;
    const next = { ...customs };
    delete next[title];
    setCustoms(next);
    _saveTmpls(uid, next);
    setEditorDraft(defaultText);
  }

  function flash(kind, text) {
    setMsg({ kind, text });
    setTimeout(() => setMsg(null), 4500);
  }

  function parseNow(forcedSubType) {
    const result = parseFor(projectType, raw, forcedSubType || undefined);
    if (!result.rows || !result.rows.length) {
      flash('err', result.error || 'Could not parse anything. Check the example format below.');
      setPreview(null);
      return;
    }
    setPreview(result);
    setParseSeq(s => s + 1); // force a fresh editable preview (resets edited fields)
  }

  function saveNow() {
    if (!preview || !preview.rows.length) return;
    // Structured finance entries confirm/save through FinanceStructuredPreview.
    if (preview.type === 'income-entry' || preview.type === 'expense-entry') return;
    try {
      if (projectType === 'diet') {
        if (preview.type === 'local-product') {
          const { _warnings, ...item } = preview.data;
          Store.addLocalItem(item);
          flash('ok', `"${item.name}" saved to local library.`);
          setRaw(''); setPreview(null); return;
        }
        if (preview.type === 'recipe') {
          const { _warnings, ...item } = preview.data;
          Store.addLocalItem(item);
          flash('ok', `Recipe "${item.name}" saved to Menu/Recipes.`);
          setRaw(''); setPreview(null); return;
        }
        // Daily diet entry — check for conflicts first
        const byDate = {};
        preview.rows.forEach(r => { (byDate[r.date] = byDate[r.date] || []).push(r); });
        const conflictDates = Object.keys(byDate).filter(date => {
          const ex = Store.meals ? Store.meals().filter(m => m.date === date) : [];
          return ex.length > 0;
        });
        if (conflictDates.length > 0) {
          const totalExisting = conflictDates.reduce((s, d) => {
            return s + (Store.meals ? Store.meals().filter(m => m.date === d).length : 0);
          }, 0);
          const dateLabel = conflictDates.length === 1
            ? (() => { try { return new Date(conflictDates[0] + 'T00:00:00').toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); } catch { return conflictDates[0]; } })()
            : `${conflictDates.length} dates`;
          setDupPending({ byDate, note: preview.note, dateLabel, existingCount: totalExisting });
          return; // pause — wait for user choice
        }
        Object.entries(byDate).forEach(([date, rows]) => Store.replaceMealsForDate(date, rows));
        if (preview.note && preview.note.date) Store.setDayNote(preview.note.date, preview.note.text);
      } else if (projectType === 'finance') {
        Store.addTransactions(preview.rows);
      } else if (projectType === 'diary') {
        Store.addDiaryEntries(preview.rows);
      } else if (projectType === 'goal') {
        // For goals, each "row" is a complete goal record; merge with existing
        const existing = Store.goals() || [];
        Store.setGoals(existing.concat(preview.rows));
      }
      flash('ok', `Saved ${preview.rows.length} entr${preview.rows.length===1?'y':'ies'} to ${projectType}.`);
      setRaw(''); setPreview(null);
    } catch (e) {
      flash('err', 'Save failed: ' + (e.message || e));
    }
  }

  function clearAll() { setRaw(''); setPreview(null); setMsg(null); }

  // ── styles ──
  const card = {
    background: 'var(--surface)', border: '1px solid var(--border)',
    borderRadius: 16, padding: 16, marginBottom: 12,
    boxShadow: 'var(--shadow-sm)',
  };
  const btn = {
    display: 'inline-flex', alignItems: 'center', gap: 6,
    padding: '10px 16px', borderRadius: 10,
    background: 'var(--text)', color: 'var(--bg)',
    border: 'none', fontSize: 13, fontWeight: 600, cursor: 'pointer',
    font: 'inherit',
  };
  const btnGhost = { ...btn, background: 'var(--surface-2)', color: 'var(--text)', border: '1px solid var(--border-2)' };

  // Structured finance entries own their own editable preview + Confirm button.
  const isStructuredFin = projectType === 'finance' && preview &&
    (preview.type === 'income-entry' || preview.type === 'expense-entry');

  return (
    <div style={{ padding: 16, paddingBottom: 100 }}>
      <div style={{ marginBottom: 14 }}>
        <h2 style={{ fontSize: 18, fontWeight: 700, letterSpacing: '-0.02em', margin: '0 0 4px' }}>Paste &amp; Parse</h2>
        <p style={{ fontSize: 12.5, color: 'var(--muted)', margin: 0 }}>{getHint(projectType)}</p>
      </div>

      {msg && (
        <div style={{
          ...card, padding: '10px 14px', marginBottom: 12,
          borderColor: msg.kind === 'ok' ? 'rgba(79,168,98,.35)' : 'rgba(170,51,51,.35)',
          background: msg.kind === 'ok' ? 'var(--c-protein-soft)' : 'rgba(170,51,51,.07)',
          color: msg.kind === 'ok' ? 'var(--text)' : '#A33',
          fontSize: 12.5,
        }}>{msg.text}</div>
      )}

      <div style={card}>
        <textarea value={raw} onChange={(e) => setRaw(e.target.value)}
          placeholder={getPlaceholder(projectType)}
          style={{
            width: '100%', minHeight: 180, padding: 12, borderRadius: 10,
            border: '1px solid var(--border)', background: 'var(--surface-2)',
            color: 'var(--text)', font: 'inherit', fontSize: 13,
            fontFamily: '"Geist Mono", ui-monospace, monospace',
            resize: 'vertical', boxSizing: 'border-box', outline: 'none',
          }}/>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 12 }}>
          <button style={btn} onClick={() => parseNow()} disabled={!raw.trim()}>Parse</button>
          {preview && !isStructuredFin && (
            <button style={btn} onClick={saveNow}>
              {preview.type === 'local-product' ? 'Save to Library'
               : preview.type === 'recipe'      ? 'Save Recipe'
               : `Save ${preview.rows.length} entr${preview.rows.length===1?'y':'ies'}`}
            </button>
          )}
          <button style={btnGhost} onClick={clearAll}>Clear</button>
        </div>
      </div>

      {preview && (
        <div style={card}>
          {/* Preview header: title + detected-type badge */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, flexWrap: 'wrap' }}>
            <span style={{ fontSize: 13, fontWeight: 600 }}>Preview</span>
            {projectType === 'diet' && (() => {
              const badgeCfg = {
                'diet':          { label: 'Daily Entry',   bg: 'rgba(59,130,246,.12)', color: '#3B82F6' },
                'local-product': { label: 'Local Product', bg: 'rgba(79,168,98,.15)',  color: 'var(--c-protein)' },
                'recipe':        { label: 'Recipe',        bg: 'rgba(245,158,11,.12)', color: '#D97706' },
              }[preview.type] || {};
              return (
                <span style={{
                  fontSize: 10, fontWeight: 700, letterSpacing: '0.05em', textTransform: 'uppercase',
                  padding: '2px 8px', borderRadius: 999,
                  background: badgeCfg.bg, color: badgeCfg.color,
                }}>{badgeCfg.label}</span>
              );
            })()}
          </div>
          {/* Type override pills */}
          {projectType === 'diet' && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10, flexWrap: 'wrap' }}>
              <span style={{ fontSize: 11, color: 'var(--muted)' }}>Not right?</span>
              {[
                { key: 'diet',          label: 'Daily Entry' },
                { key: 'local-product', label: 'Local Product' },
                { key: 'recipe',        label: 'Recipe' },
              ].map(opt => (
                <button key={opt.key} onClick={() => parseNow(opt.key)} style={{
                  fontSize: 11, fontWeight: 600, padding: '3px 10px', borderRadius: 999,
                  border: '1px solid ' + (preview.type === opt.key ? 'var(--text)' : 'var(--border-2)'),
                  background: preview.type === opt.key ? 'var(--text)' : 'transparent',
                  color:      preview.type === opt.key ? 'var(--bg)'  : 'var(--muted)',
                  cursor: 'pointer', font: 'inherit',
                }}>{opt.label}</button>
              ))}
            </div>
          )}
          {/* Warnings (non-structured previews; structured shows its own live warnings) */}
          {!isStructuredFin && preview.warnings && preview.warnings.length > 0 && (
            <div style={{
              padding: '8px 12px', borderRadius: 8, marginBottom: 8,
              background: 'rgba(245,158,11,.08)', border: '1px solid rgba(245,158,11,.28)',
              fontSize: 12, color: '#D97706',
            }}>
              {preview.warnings.map((w, i) => <div key={i}>⚠ {w}</div>)}
            </div>
          )}
          {isStructuredFin ? (
            <FinanceStructuredPreview
              key={'fin-' + parseSeq}
              entry={preview.entry}
              onParseAs={(t) => parseNow(t)}
              onSaved={(m) => { flash('ok', m); setRaw(''); setPreview(null); }}
            />
          ) : (
            <Preview projectType={projectType} result={preview}/>
          )}
        </div>
      )}

      <div style={card}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>Templates</div>
        <div style={{ fontSize: 11.5, color: 'var(--muted)', marginBottom: 10 }}>
          Tap <b>Edit</b> to customise a template. Tap <b>Copy</b> to copy it, then paste above and hit <b>Parse</b>.
        </div>
        {getExamples(projectType).map((ex) => {
          const saved = customs[ex.title] !== undefined ? customs[ex.title] : ex.text;
          const isCustom = customs[ex.title] !== undefined;
          return (
            <TemplateCard
              key={ex.title}
              title={ex.title}
              text={saved}
              isCustom={isCustom}
              onEdit={() => openEditor(ex.title, saved)}
              onCopy={saved}
            />
          );
        })}
      </div>

      {/* Template editor overlay */}
      {editingTitle !== null && (
        <TemplateEditorSheet
          title={editingTitle}
          draft={editorDraft}
          isCustom={customs[editingTitle] !== undefined}
          defaultText={getExamples(projectType).find(e => e.title === editingTitle)?.text || ''}
          onChange={setEditorDraft}
          onSave={saveEditorDraft}
          onReset={() => resetTemplate(editingTitle, getExamples(projectType).find(e => e.title === editingTitle)?.text || '')}
          onClose={closeEditor}
        />
      )}

      {/* Duplicate-date dialog for Paste & Parse */}
      {dupPending && DuplicateEntryDialog && (
        <div style={{ position: 'fixed', inset: 0, zIndex: 200 }}>
          <DuplicateEntryDialog
            date={dupPending.dateLabel}
            existingCount={dupPending.existingCount}
            onReplace={() => {
              Object.entries(dupPending.byDate).forEach(([date, rows]) => Store.replaceMealsForDate(date, rows));
              if (dupPending.note && dupPending.note.date) Store.setDayNote(dupPending.note.date, dupPending.note.text);
              flash('ok', `Replaced entries for ${Object.keys(dupPending.byDate).length} date(s).`);
              setDupPending(null); setRaw(''); setPreview(null);
            }}
            onMerge={() => {
              let addedCount = 0;
              Object.entries(dupPending.byDate).forEach(([date, rows]) => {
                const existing = Store.meals ? Store.meals().filter(m => m.date === date) : [];
                const fp = new Set(existing.map(m => `${m.meal}:${m.name}:${Math.round(m.calories)}`));
                const toAdd = rows.filter(r => !fp.has(`${r.meal}:${r.name}:${Math.round(r.calories)}`));
                if (toAdd.length) { Store.addMeals(toAdd); addedCount += toAdd.length; }
              });
              if (dupPending.note && dupPending.note.date) Store.setDayNote(dupPending.note.date, dupPending.note.text);
              flash('ok', `Merged ${addedCount} new entr${addedCount === 1 ? 'y' : 'ies'}.`);
              setDupPending(null); setRaw(''); setPreview(null);
            }}
            onKeep={() => {
              flash('ok', 'Existing entries kept. New data discarded.');
              setDupPending(null);
            }}
            onClose={() => setDupPending(null)}
          />
        </div>
      )}
    </div>
  );
}

// ── Template card — Edit + Copy only ────────────────────────────────
function TemplateCard({ title, text, isCustom, onEdit, onCopy }) {
  const [copied, setCopied] = React.useState(false);

  async function doCopy() {
    const t = typeof onCopy === 'string' ? onCopy : text;
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(t);
      } else {
        const ta = document.createElement('textarea');
        ta.value = t; ta.style.position = 'fixed'; ta.style.opacity = '0';
        document.body.appendChild(ta); ta.focus(); ta.select();
        document.execCommand('copy'); ta.remove();
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    } catch(_) {}
  }

  const chipStyle = {
    padding: '5px 11px', borderRadius: 999,
    font: 'inherit', fontSize: 11, fontWeight: 600, cursor: 'pointer',
    display: 'inline-flex', alignItems: 'center', gap: 4,
    border: '1px solid var(--border-2)',
  };

  return (
    <div style={{
      marginBottom: 10, background: 'var(--surface-2)',
      border: '1px solid ' + (isCustom ? 'rgba(79,168,98,.35)' : 'var(--border)'),
      borderRadius: 12, overflow: 'hidden',
    }}>
      {/* Card header */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '8px 12px', background: 'var(--surface-3)',
        borderBottom: '1px solid var(--border)',
        gap: 8,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
          <span style={{ fontSize: 12, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{title}</span>
          {isCustom && (
            <span style={{
              fontSize: 9.5, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase',
              background: 'rgba(79,168,98,.18)', color: 'var(--c-protein)',
              padding: '2px 6px', borderRadius: 999, flexShrink: 0,
            }}>Edited</span>
          )}
        </div>
        <div style={{ display: 'flex', gap: 6, flexShrink: 0 }}>
          {/* Edit button */}
          <button onClick={onEdit} style={{
            ...chipStyle,
            background: 'var(--surface)', color: 'var(--text)',
          }}>
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
            Edit
          </button>
          {/* Copy button */}
          <button onClick={doCopy} style={{
            ...chipStyle,
            background: copied ? 'var(--c-protein)' : 'var(--text)',
            color: copied ? '#fff' : 'var(--bg)',
            borderColor: copied ? 'var(--c-protein)' : 'var(--text)',
          }}>
            {copied ? (
              <><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>Copied</>
            ) : (
              <><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>Copy</>
            )}
          </button>
        </div>
      </div>
      {/* Template body */}
      <pre style={{
        margin: 0, padding: 12,
        fontSize: 11.5, lineHeight: 1.5,
        fontFamily: '"Geist Mono", ui-monospace, monospace',
        color: 'var(--text-2)',
        whiteSpace: 'pre-wrap', wordBreak: 'break-word',
      }}>{text}</pre>
    </div>
  );
}

// ── Template editor sheet (full-screen overlay) ──────────────────────
function TemplateEditorSheet({ title, draft, isCustom, defaultText, onChange, onSave, onReset, onClose }) {
  const safeTop = 'env(safe-area-inset-top, 0px)';
  const safeBottom = 'env(safe-area-inset-bottom, 0px)';

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 900,
      background: 'var(--bg)',
      display: 'flex', flexDirection: 'column',
      paddingTop: safeTop,
    }}>
      {/* Header bar */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '12px 16px',
        borderBottom: '1px solid var(--border)',
        background: 'var(--surface)',
        flexShrink: 0,
      }}>
        <button onClick={onClose} style={{
          background: 'none', border: 'none', cursor: 'pointer',
          color: 'var(--muted)', font: 'inherit', fontSize: 13, fontWeight: 600,
          padding: '4px 0', display: 'inline-flex', alignItems: 'center', gap: 4,
        }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
          Cancel
        </button>
        <div style={{ fontSize: 13, fontWeight: 700, letterSpacing: '-0.01em', textAlign: 'center', flex: 1, padding: '0 8px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
          Edit Template
        </div>
        <button onClick={onSave} style={{
          background: 'var(--accent)', color: 'var(--on-accent)',
          border: 'none', borderRadius: 10, padding: '7px 16px',
          font: 'inherit', fontSize: 13, fontWeight: 700, cursor: 'pointer',
        }}>Save</button>
      </div>

      {/* Title chip */}
      <div style={{ padding: '10px 16px 0', flexShrink: 0 }}>
        <div style={{
          fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em',
          color: 'var(--muted)', marginBottom: 6,
        }}>Template</div>
        <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', marginBottom: 10 }}>{title}</div>
      </div>

      {/* Editable textarea */}
      <div style={{ flex: 1, overflow: 'auto', padding: '0 16px' }}>
        <textarea
          autoFocus
          value={draft}
          onChange={e => onChange(e.target.value)}
          style={{
            width: '100%', height: '100%', minHeight: 260,
            padding: 12, borderRadius: 12,
            border: '1px solid var(--border-2)', background: 'var(--surface)',
            color: 'var(--text)', font: 'inherit', fontSize: 13,
            fontFamily: '"Geist Mono", ui-monospace, monospace',
            lineHeight: 1.55, resize: 'none', boxSizing: 'border-box', outline: 'none',
          }}
        />
      </div>

      {/* Footer — reset */}
      <div style={{
        padding: '12px 16px', paddingBottom: 'calc(12px + ' + safeBottom + ')',
        borderTop: '1px solid var(--border)', background: 'var(--surface)',
        flexShrink: 0,
      }}>
        {isCustom ? (
          <button onClick={onReset} style={{
            background: 'none', border: '1px solid rgba(170,51,51,.3)',
            borderRadius: 10, padding: '9px 16px', width: '100%',
            font: 'inherit', fontSize: 13, fontWeight: 600, cursor: 'pointer',
            color: '#A33',
          }}>Reset to default template</button>
        ) : (
          <div style={{ fontSize: 11.5, color: 'var(--muted)', textAlign: 'center', padding: '4px 0' }}>
            Edit the template above, then tap <strong>Save</strong>.
          </div>
        )}
      </div>
    </div>
  );
}

function Preview({ projectType, result }) {
  // ── Local Product preview ──────────────────────────────────────────
  if (result.type === 'local-product') {
    const d = result.data;
    const nf = d.nutritionFacts || {};
    const row = (label, val, unit, color, indent) => val === '' || val === undefined ? null : (
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
        padding: '4px 0', borderBottom: '1px solid var(--border)', fontSize: 12.5,
        paddingLeft: indent ? 16 : 0,
      }}>
        <span style={{ color: indent ? 'var(--muted)' : 'var(--text)', fontSize: indent ? 11.5 : 12.5 }}>{label}</span>
        <span style={{ fontWeight: color ? 600 : 400, color: color || 'var(--text)' }}>{val}{unit || ''}</span>
      </div>
    );
    const hasFullNF = nf.totalFat_g !== '' || nf.totalCarbs_g !== '' || nf.sodium_mg !== '';
    return (
      <div>
        {/* Header */}
        <div style={{ marginBottom: 10 }}>
          <div style={{ fontSize: 15, fontWeight: 700, lineHeight: 1.3 }}>{d.name || '—'}</div>
          {d.brand && <div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 2 }}>{d.brand}</div>}
          {d.categoryLabel && <div style={{ fontSize: 11.5, color: 'var(--muted)' }}>{d.categoryLabel}</div>}
        </div>
        {/* Package meta */}
        {(d.packageSize || d.servingSizeText || d.servingsPerPackage) && (
          <div style={{
            display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 10,
            padding: '7px 10px', borderRadius: 8, background: 'var(--surface-2)',
            border: '1px solid var(--border)', fontSize: 11.5,
          }}>
            {d.servingsPerPackage && <span><strong>{d.servingsPerPackage}</strong> servings per package</span>}
            {d.servingSizeText
              ? <span>Serving size <strong>{d.servingSizeText}</strong></span>
              : <span>Per <strong>{d.serving}{d.unit}</strong> serving</span>}
            {d.packageSize && <span>Package <strong>{d.packageSize}</strong></span>}
          </div>
        )}
        {/* Nutrition facts */}
        <div style={{ borderRadius: 10, overflow: 'hidden', border: '1px solid var(--border)', marginBottom: 10 }}>
          <div style={{ background: 'var(--surface-3)', padding: '6px 12px',
            display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
            borderBottom: '1px solid var(--border)' }}>
            <span style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--muted)' }}>Nutrition Facts</span>
            {(!d.servingSizeText && !d.servingsPerPackage) && (
              <span style={{ fontSize: 10.5, color: 'var(--muted)' }}>Per {d.serving}{d.unit}</span>
            )}
          </div>
          <div style={{ padding: '0 12px', background: 'var(--surface-2)' }}>
            {/* Calories — always shown large */}
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
              padding: '6px 0', borderBottom: '2px solid var(--border)', marginBottom: 2 }}>
              <span style={{ fontWeight: 700, fontSize: 14 }}>Calories</span>
              <span style={{ fontWeight: 800, fontSize: 20 }}>{d.cal}</span>
            </div>
            {hasFullNF ? (
              <>
                {row('Total Fat',         nf.totalFat_g,    'g', 'var(--c-fat)')}
                {row('Saturated Fat',     nf.saturatedFat_g,'g', null, true)}
                {row('Trans Fat',         nf.transFat_g,    'g', null, true)}
                {row('Cholesterol',       nf.cholesterol_mg,'mg')}
                {row('Total Carbohydrate',nf.totalCarbs_g,  'g', 'var(--c-carbs)')}
                {row('Dietary Fiber',     nf.dietaryFiber_g,'g', null, true)}
                {row('Total Sugars',      nf.totalSugars_g, 'g', null, true)}
                {row('Added Sugars',      nf.addedSugars_g, 'g', null, true)}
                {row('Protein',           nf.protein_g,     'g', 'var(--c-protein)')}
                {nf.sodium_mg    !== '' && row('Sodium',    nf.sodium_mg,   'mg')}
                {nf.potassium_mg !== '' && row('Potassium', nf.potassium_mg,'mg')}
                {nf.calcium_mg   !== '' && row('Calcium',   nf.calcium_mg,  'mg')}
                {nf.iron_mg      !== '' && <div style={{
                  display: 'flex', justifyContent: 'space-between', padding: '4px 0', fontSize: 12.5 }}>
                  <span>Iron</span><span>{nf.iron_mg}mg</span>
                </div>}
              </>
            ) : (
              <>
                {row('Protein',       d.protein_g,'g', 'var(--c-protein)')}
                {row('Carbohydrates', d.carbs_g,  'g', 'var(--c-carbs)')}
                <div style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', fontSize: 12.5 }}>
                  <span>Fat</span><span style={{ color: 'var(--c-fat)', fontWeight: 600 }}>{d.fat_g}g</span>
                </div>
              </>
            )}
          </div>
        </div>
        {/* Ingredients */}
        {d.ingredients && (
          <div style={{ marginBottom: 8 }}>
            <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em',
              color: 'var(--muted)', marginBottom: 3 }}>Ingredients</div>
            <div style={{ fontSize: 11.5, color: 'var(--text-2)', lineHeight: 1.6 }}>{d.ingredients}</div>
          </div>
        )}
        {/* Storage */}
        {d.storageInstructions && (
          <div style={{ marginBottom: 8 }}>
            <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em',
              color: 'var(--muted)', marginBottom: 3 }}>Storage</div>
            <div style={{ fontSize: 11.5, color: 'var(--text-2)' }}>{d.storageInstructions}</div>
          </div>
        )}
        {/* Allergens */}
        {d.allergens && (
          <div style={{ marginBottom: 8 }}>
            <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em',
              color: 'var(--muted)', marginBottom: 3 }}>Allergens</div>
            <div style={{ fontSize: 11.5, color: '#D97706' }}>{d.allergens}</div>
          </div>
        )}
        {/* Notes */}
        {d.notes && (
          <div style={{ padding: '8px 12px', borderRadius: 8, background: 'var(--surface-2)',
            border: '1px solid var(--border)', fontSize: 12, color: 'var(--text-2)', lineHeight: 1.55 }}>
            <div style={{ fontWeight: 700, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.05em',
              color: 'var(--muted)', marginBottom: 4 }}>Notes</div>
            {d.notes}
          </div>
        )}
      </div>
    );
  }

  // ── Recipe preview ─────────────────────────────────────────────────
  if (result.type === 'recipe') {
    const d = result.data;
    const cell  = { padding: '4px 6px', fontSize: 11.5, borderBottom: '1px solid var(--border)' };
    const hcell = { ...cell, fontWeight: 700, color: 'var(--muted)', textTransform: 'uppercase',
      letterSpacing: '0.04em', fontSize: 10, background: 'var(--surface-3)' };
    return (
      <div>
        <div style={{ marginBottom: 10 }}>
          <div style={{ fontSize: 15, fontWeight: 700 }}>{d.name || '—'}</div>
          {d.category && <div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 2 }}>{d.category}</div>}
          {!(d.serving === 1 && d.unit === 'serving') && (
            <div style={{ fontSize: 11.5, color: 'var(--muted)', marginTop: 2 }}>
              {d.serving} {d.unit}
            </div>
          )}
        </div>
        {/* Total macros pill */}
        <div style={{
          padding: '8px 12px', borderRadius: 10, marginBottom: 10,
          background: 'var(--surface)', border: '1px solid var(--border-2)',
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          flexWrap: 'wrap', gap: 4, fontSize: 12.5,
        }}>
          <span style={{ fontWeight: 700 }}>Per serving</span>
          <span style={{ color: 'var(--muted)', fontSize: 12 }}>
            <strong style={{ color: 'var(--text)' }}>{d.cal}</strong> kcal
            {d.protein_g ? <span> · P <strong style={{ color: 'var(--c-protein)' }}>{d.protein_g}g</strong></span> : ''}
            {d.carbs_g   ? <span> · C <strong style={{ color: 'var(--c-carbs)'   }}>{d.carbs_g}g</strong></span>   : ''}
            {d.fat_g     ? <span> · F <strong style={{ color: 'var(--c-fat)'     }}>{d.fat_g}g</strong></span>     : ''}
          </span>
        </div>
        {/* Ingredients table */}
        {d.items && d.items.length > 0 && (
          <div style={{ borderRadius: 10, overflow: 'hidden', border: '1px solid var(--border)', marginBottom: 10 }}>
            <div style={{ background: 'var(--surface-3)', padding: '6px 12px', fontSize: 10, fontWeight: 700,
              textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--muted)',
              borderBottom: '1px solid var(--border)' }}>Ingredients</div>
            <div style={{ overflowX: 'auto', background: 'var(--surface-2)' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                <thead>
                  <tr>
                    {['Item','Amount','kcal','C','P','F'].map(h => (
                      <th key={h} style={{ ...hcell, textAlign: h === 'Item' || h === 'Amount' ? 'left' : 'right' }}>{h}</th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {d.items.map((it, ii) => (
                    <tr key={ii}>
                      <td style={{ ...cell, textAlign: 'left', maxWidth: 120,
                        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.name}</td>
                      <td style={{ ...cell, textAlign: 'left', color: 'var(--muted)', maxWidth: 90,
                        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.amount || '—'}</td>
                      <td style={{ ...cell, textAlign: 'right', fontWeight: 600 }}>{it.calories}</td>
                      <td style={{ ...cell, textAlign: 'right', color: 'var(--c-carbs)' }}>{it.carbs_g !== '' && it.carbs_g != null ? it.carbs_g : '—'}</td>
                      <td style={{ ...cell, textAlign: 'right', color: 'var(--c-protein)' }}>{it.protein_g !== '' && it.protein_g != null ? it.protein_g : '—'}</td>
                      <td style={{ ...cell, textAlign: 'right', color: 'var(--c-fat)' }}>{it.fat_g !== '' && it.fat_g != null ? it.fat_g : '—'}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        )}
        {/* Serving options */}
        {d.servingOptions && d.servingOptions.length > 0 && (
          <div style={{ marginBottom: 10 }}>
            <div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em',
              color: 'var(--muted)', marginBottom: 6 }}>Serving Options</div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
              {d.servingOptions.map((opt, i) => (
                <div key={i} style={{
                  padding: '5px 12px', borderRadius: 999, fontSize: 11.5, fontWeight: 600,
                  background: 'var(--surface-2)', border: '1px solid var(--border)',
                }}>
                  {opt.label} · <strong>{opt.calories}</strong> kcal
                </div>
              ))}
            </div>
          </div>
        )}
        {d.note && (
          <div style={{ padding: '8px 12px', borderRadius: 8, background: 'var(--surface-2)',
            border: '1px solid var(--border)', fontSize: 12, color: 'var(--text-2)' }}>
            <span style={{ fontWeight: 700, color: 'var(--muted)' }}>Note: </span>{d.note}
          </div>
        )}
      </div>
    );
  }

  // ── Diet / Daily Entry preview ──────────────────────────────────────
  if (projectType === 'diet') {
    const dateLabel = result.rows.length ? result.rows[0].date : '';
    const cell  = { padding: '4px 6px', fontSize: 11.5, borderBottom: '1px solid var(--border)' };
    const hcell = { ...cell, fontWeight: 700, color: 'var(--muted)', textTransform: 'uppercase',
      letterSpacing: '0.04em', fontSize: 10, background: 'var(--surface-3)' };
    return (
      <div>
        {dateLabel && (
          <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--muted)',
            textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 8 }}>
            {dateLabel}
          </div>
        )}
        {result.rows.map((r, ri) => (
          <div key={ri} style={{
            marginBottom: 10, borderRadius: 10, overflow: 'hidden',
            border: '1px solid var(--border)',
          }}>
            {/* Meal header row */}
            <div style={{
              padding: '7px 10px', background: 'var(--surface-3)',
              borderBottom: r.items && r.items.length ? '1px solid var(--border)' : 'none',
              display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8,
            }}>
              <span style={{ fontWeight: 700, fontSize: 13 }}>{r.name}</span>
              <span style={{ fontSize: 11.5, color: 'var(--muted)', flexShrink: 0 }}>
                {r.calories ? r.calories + ' kcal' : ''}
                {r.carbs_g   !== '' && r.carbs_g   != null ? ' · C ' + r.carbs_g   : ''}
                {r.protein_g !== '' && r.protein_g != null ? ' · P ' + r.protein_g : ''}
                {r.fat_g     !== '' && r.fat_g     != null ? ' · F ' + r.fat_g     : ''}
              </span>
            </div>
            {/* Item table */}
            {r.items && r.items.length > 0 && (
              <div style={{ overflowX: 'auto', background: 'var(--surface-2)' }}>
                <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                  <thead>
                    <tr>
                      {['Item','Amount','kcal','C','P','F'].map(h => (
                        <th key={h} style={{ ...hcell, textAlign: h === 'Item' || h === 'Amount' ? 'left' : 'right' }}>{h}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {r.items.map((it, ii) => (
                      <tr key={ii}>
                        <td style={{ ...cell, textAlign: 'left', maxWidth: 120,
                          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.name}</td>
                        <td style={{ ...cell, textAlign: 'left', color: 'var(--muted)', maxWidth: 90,
                          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.amount || '—'}</td>
                        <td style={{ ...cell, textAlign: 'right', fontWeight: 600 }}>{it.calories}</td>
                        <td style={{ ...cell, textAlign: 'right', color: 'var(--c-carbs)' }}>{it.carbs_g !== '' && it.carbs_g != null ? it.carbs_g : '—'}</td>
                        <td style={{ ...cell, textAlign: 'right', color: 'var(--c-protein)' }}>{it.protein_g !== '' && it.protein_g != null ? it.protein_g : '—'}</td>
                        <td style={{ ...cell, textAlign: 'right', color: 'var(--c-fat)' }}>{it.fat_g !== '' && it.fat_g != null ? it.fat_g : '—'}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            )}
          </div>
        ))}
        {/* Daily total */}
        {result.dailyTotal && (
          <div style={{
            padding: '8px 12px', borderRadius: 10, marginBottom: 8,
            background: 'var(--surface)', border: '1px solid var(--border-2)',
            display: 'flex', justifyContent: 'space-between', alignItems: 'center',
            flexWrap: 'wrap', gap: 4, fontSize: 12.5,
          }}>
            <span style={{ fontWeight: 700 }}>Daily Total</span>
            <span style={{ color: 'var(--muted)', fontSize: 12 }}>
              <strong style={{ color: 'var(--text)' }}>{result.dailyTotal.calories}</strong> kcal
              {result.dailyTotal.carbs_g   !== '' ? <span> · C <strong style={{ color: 'var(--c-carbs)' }}>{result.dailyTotal.carbs_g}</strong></span>   : ''}
              {result.dailyTotal.protein_g !== '' ? <span> · P <strong style={{ color: 'var(--c-protein)' }}>{result.dailyTotal.protein_g}</strong></span> : ''}
              {result.dailyTotal.fat_g     !== '' ? <span> · F <strong style={{ color: 'var(--c-fat)' }}>{result.dailyTotal.fat_g}</strong></span>         : ''}
            </span>
          </div>
        )}
        {/* Note */}
        {result.note && (
          <div style={{
            padding: '8px 12px', borderRadius: 10,
            background: 'var(--surface-2)', border: '1px solid var(--border)', fontSize: 12,
          }}>
            <div style={{ fontWeight: 700, fontSize: 11, color: 'var(--muted)',
              textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>
              Note · {result.note.date}
            </div>
            <div style={{ whiteSpace: 'pre-wrap', lineHeight: 1.6, color: 'var(--text-2)' }}>{result.note.text}</div>
          </div>
        )}
      </div>
    );
  }
  if (projectType === 'finance') {
    return (
      <div>
        {result.rows.map((r, i) => (
          <div key={i} style={{
            padding: '8px 10px', borderRadius: 8, marginBottom: 6,
            background: 'var(--surface-2)', fontSize: 12.5,
            display: 'flex', justifyContent: 'space-between', gap: 8,
          }}>
            <div>
              <div style={{ fontWeight: 600 }}>{r.date} · {r.title || r.category}</div>
              <div style={{ color: 'var(--muted)', fontSize: 11.5 }}>{r.kind} · {r.category}</div>
            </div>
            <div style={{ fontWeight: 700, color: r.kind === 'income' ? '#4FA862' : 'var(--text)' }}>
              {r.kind === 'income' ? '+' : '−'}{Math.abs(r.amount).toLocaleString()}
            </div>
          </div>
        ))}
      </div>
    );
  }
  if (projectType === 'diary') {
    return (
      <div>
        {result.rows.map((r, i) => (
          <div key={i} style={{
            padding: '8px 10px', borderRadius: 8, marginBottom: 6,
            background: 'var(--surface-2)', fontSize: 12.5,
          }}>
            <div style={{ fontWeight: 600 }}>{r.dateKey}</div>
            <div style={{ color: 'var(--muted)', fontSize: 11.5, marginTop: 2 }}>{r.eventText.slice(0, 140)}{r.eventText.length > 140 ? '…' : ''}</div>
          </div>
        ))}
      </div>
    );
  }
  if (projectType === 'goal') {
    return (
      <div>
        {result.rows.map((r, i) => (
          <div key={i} style={{
            padding: '8px 10px', borderRadius: 8, marginBottom: 6,
            background: 'var(--surface-2)', fontSize: 12.5,
          }}>
            <div style={{ fontWeight: 600 }}>{r.name}</div>
            <div style={{ color: 'var(--muted)', fontSize: 11.5, marginTop: 2 }}>
              {r.timesPerDay}x/day · {Object.keys(r.log || {}).length} logged day{Object.keys(r.log || {}).length===1?'':'s'}
            </div>
          </div>
        ))}
      </div>
    );
  }
  return null;
}

// ── Finance structured preview (editable) — Income / Expense Entry ────
const _finInputStyle = {
  width: '100%', border: '1px solid var(--border)', background: 'var(--surface-2)',
  borderRadius: 10, padding: '8px 12px', outline: 'none', font: 'inherit', fontSize: 14,
  color: 'var(--text)', boxSizing: 'border-box',
};

function FinField({ label, optional, children }) {
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
      <span style={{ fontSize: 11, color: 'var(--muted)', fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase' }}>
        {label}{optional ? <span style={{ fontWeight: 400, textTransform: 'none', fontSize: 10 }}> (optional)</span> : null}
      </span>
      {children}
    </label>
  );
}

// Editable entity field with live "matches existing / will create" status + a
// quick-pick dropdown of existing entities (satisfies "create OR choose existing").
function FinEntityField({ label, optional, name, setName, existing, matchedObj, createLabel, emptyNote }) {
  const matched = !!matchedObj;
  const trimmed = (name || '').trim();
  return (
    <div>
      <FinField label={label} optional={optional}>
        <div style={{ display: 'flex', gap: 6 }}>
          <input value={name} onChange={e => setName(e.target.value)} placeholder="—" style={{ ..._finInputStyle, flex: 1 }}/>
          {existing.length > 0 && (
            <select value="" onChange={e => { if (e.target.value) setName(e.target.value); }}
              style={{ ..._finInputStyle, width: 54, flex: '0 0 auto', padding: '8px 6px', cursor: 'pointer' }}
              title="Pick existing">
              <option value="">▾</option>
              {existing.map(x => <option key={x.id} value={x.name}>{x.name}</option>)}
            </select>
          )}
        </div>
      </FinField>
      <div style={{ marginTop: 4, fontSize: 11 }}>
        {trimmed === ''
          ? <span style={{ color: 'var(--muted)' }}>{emptyNote || 'Left empty'}</span>
          : matched
            ? <span style={{ color: 'var(--c-protein)', fontWeight: 600 }}>✓ Matches existing — will link</span>
            : <span style={{ color: '#D97706', fontWeight: 600 }}>➕ {createLabel || 'New'} “{trimmed}” — created on confirm</span>}
      </div>
    </div>
  );
}

function FinanceStructuredPreview({ entry, onParseAs, onSaved }) {
  const Store = window.Store;
  const cats  = (Store.financeCategories && Store.financeCategories()) || window.NF_CATEGORIES || [];
  const accts = (Store.financeAccounts && Store.financeAccounts()) || window.NF_ACCOUNTS || [];
  const settings = (Store.settings && Store.settings()) || {};
  const usdRate = settings.usdEgpRate ? +settings.usdEgpRate : null;

  const isIncome = entry.kind === 'income';

  const [date, setDate]         = React.useState(entry.date || _finToday());
  const [title, setTitle]       = React.useState(entry.title || (isIncome ? 'Income' : 'Expense'));
  const [amount, setAmount]     = React.useState(entry.amount ? String(entry.amount) : '');
  const [currency, setCurrency] = React.useState(entry.currency || 'EGP');
  const [rate, setRate]         = React.useState(entry.exchangeRate != null ? String(entry.exchangeRate) : '');
  const [egp, setEgp]           = React.useState(entry.egpEquivalent != null ? String(entry.egpEquivalent) : '');
  const [notes, setNotes]       = React.useState(entry.notes || '');
  const [sourceName, setSourceName]   = React.useState(entry.sourceName || '');
  const [catName, setCatName]         = React.useState(entry.categoryName || '');
  const [subName, setSubName]         = React.useState(entry.subcategoryName || '');
  const [acctName, setAcctName]       = React.useState(entry.accountName || '');
  const [spendingType, setSpendingType] = React.useState(entry.spendingType || '');
  const [paymentMethod, setPaymentMethod] = React.useState(entry.paymentMethod || '');
  const [saving, setSaving] = React.useState(false);

  const CURRENCIES = ['EGP', 'USD', 'EUR', 'GBP', 'SAR', 'AED'];
  const isForeign  = currency && currency !== 'EGP';
  const numAmount  = parseFloat(amount) || 0;
  const numRate    = parseFloat(rate) || 0;

  // Pre-fill USD rate from the stored global rate (does not change it globally)
  React.useEffect(() => {
    if (currency === 'USD' && usdRate && !rate) setRate(String(usdRate));
  }, [currency]);

  const computedEgp = (() => {
    if (!isForeign) return numAmount;
    if (egp.trim() !== '') return parseFloat(egp) || 0;
    if (numRate > 0) return +(numAmount * numRate).toFixed(2);
    if (currency === 'USD' && usdRate) return +(numAmount * usdRate).toFixed(2);
    return null;
  })();

  const norm   = s => (s || '').trim().toLowerCase();
  const incCat = cats.find(c => c.id === 'income');
  const incomeSources = (incCat && incCat.subcategories) || [];
  const expenseCats   = cats.filter(c => c.id !== 'income');

  const matchedSource = isIncome && sourceName.trim()
    ? incomeSources.find(s => norm(s.name) === norm(sourceName) || norm(s.id) === norm(sourceName)) : null;
  const matchedCat = !isIncome && catName.trim()
    ? expenseCats.find(c => norm(c.name) === norm(catName) || norm(c.id) === norm(catName)) : null;
  const subParentSubs = matchedCat ? (matchedCat.subcategories || []) : [];
  const matchedSub = !isIncome && subName.trim() && matchedCat
    ? subParentSubs.find(s => norm(s.name) === norm(subName) || norm(s.id) === norm(subName)) : null;
  const matchedAcct = acctName.trim()
    ? accts.find(a => norm(a.name) === norm(acctName) || norm(a.id) === norm(acctName)) : null;

  const warnings = [];
  if (!numAmount) warnings.push('Amount is missing or zero — set it before saving.');
  if (isForeign && computedEgp == null) warnings.push('Foreign currency: enter an exchange rate or EGP equivalent.');

  const canSave = numAmount > 0 && !saving;

  function confirm() {
    if (!canSave) return;
    setSaving(true);
    const amt = Math.abs(parseFloat(amount) || 0);
    const rnd = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
    const cleanAcct = a => ({
      id: a.id, name: a.name, kind: a.kind || 'cash', icon: a.icon || '💵',
      startingBalance: +a.startingBalance || 0, currency: a.currency || 'EGP',
      ...(a.notes ? { notes: a.notes } : {}), ...(a.archived ? { archived: true } : {}),
    });

    let categoryId = '', subcategoryId = '', accountId = '';

    if (isIncome) {
      categoryId = 'income';
      if (sourceName.trim()) {
        if (matchedSource) subcategoryId = matchedSource.id;
        else {
          const newId = 'sub_' + rnd();
          const updated = incCat
            ? cats.map(c => c.id === 'income'
                ? { ...c, subcategories: [...(c.subcategories || []), { id: newId, name: sourceName.trim() }] } : c)
            : [...cats, { id: 'income', name: 'Income', color: '#4FA862', icon: '💰',
                subcategories: [{ id: newId, name: sourceName.trim() }] }];
          Store.setFinanceCategories(updated);
          subcategoryId = newId;
        }
      }
    } else {
      if (catName.trim()) {
        if (matchedCat) categoryId = matchedCat.id;
        else {
          const newId = 'cat_' + rnd();
          Store.setFinanceCategories([...cats, {
            id: newId, name: catName.trim(), color: '#7A766C', icon: '📦',
            subcategories: [], ...(spendingType ? { spendingType } : {}),
          }]);
          categoryId = newId;
        }
      }
      if (subName.trim() && categoryId) {
        if (matchedSub) subcategoryId = matchedSub.id;
        else {
          const newSubId = 'sub_' + rnd();
          const curCats = (Store.financeCategories && Store.financeCategories()) || cats;
          Store.setFinanceCategories(curCats.map(c => c.id === categoryId
            ? { ...c, subcategories: [...(c.subcategories || []), { id: newSubId, name: subName.trim() }] } : c));
          subcategoryId = newSubId;
        }
      }
    }

    if (acctName.trim()) {
      if (matchedAcct) accountId = matchedAcct.id;
      else {
        const slug = acctName.trim().toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '');
        const newId = (slug || 'acct') + '_' + Date.now().toString(36).slice(-4);
        const curAccts = ((Store.financeAccounts && Store.financeAccounts()) || window.NF_ACCOUNTS || []).map(cleanAcct);
        Store.setFinanceAccounts([...curAccts, {
          id: newId, name: acctName.trim(), kind: 'cash', icon: '💵',
          startingBalance: 0, currency: currency || 'EGP',
        }]);
        accountId = newId;
      }
    } else {
      accountId = (accts[0] || {}).id || 'cash';
    }

    const row = {
      id: 'fin_' + rnd(),
      date: date || _finToday(),
      kind: isIncome ? 'income' : 'expense',
      amount: amt,
      category: categoryId,
      subcategory: subcategoryId,
      title: (title || '').trim() || (isIncome ? 'Income' : 'Expense'),
      account: accountId,
      time: new Date().toTimeString().slice(0, 5),
      notes: notes || '',
      currency: currency || 'EGP',
      ...(isForeign && numRate > 0 ? { exchangeRate: numRate } : {}),
      ...(isForeign && computedEgp != null ? { egpEquivalent: computedEgp } : {}),
      ...(!isIncome && paymentMethod ? { paymentMethod } : {}),
      ...(isIncome && entry.incomeType ? { incomeType: entry.incomeType } : {}),
      source: 'paste',
    };
    Store.addTransactions([row]);
    setSaving(false);
    onSaved && onSaved('Saved ' + (isIncome ? 'income' : 'expense') + ' — ' + row.title + ' ✓');
  }

  const accent = isIncome ? 'var(--c-protein)' : 'var(--text)';

  return (
    <div>
      {/* Detected type + override pills */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
        <span style={{
          fontSize: 10, fontWeight: 700, letterSpacing: '0.05em', textTransform: 'uppercase',
          padding: '3px 10px', borderRadius: 999,
          background: isIncome ? 'rgba(79,168,98,.15)' : 'rgba(87,132,216,.12)',
          color: isIncome ? 'var(--c-protein)' : '#5784D8',
        }}>{isIncome ? 'Income' : 'Expense'} Entry</span>
        <span style={{ fontSize: 11, color: 'var(--muted)' }}>Not right?</span>
        {[{ k: 'income-entry', label: 'Income' }, { k: 'expense-entry', label: 'Expense' }].map(opt => {
          const active = (isIncome ? 'income-entry' : 'expense-entry') === opt.k;
          return (
            <button key={opt.k} onClick={() => onParseAs && onParseAs(opt.k)} style={{
              fontSize: 11, fontWeight: 600, padding: '3px 10px', borderRadius: 999,
              border: '1px solid ' + (active ? 'var(--text)' : 'var(--border-2)'),
              background: active ? 'var(--text)' : 'transparent',
              color: active ? 'var(--bg)' : 'var(--muted)', cursor: 'pointer', font: 'inherit',
            }}>{opt.label}</button>
          );
        })}
      </div>

      {warnings.length > 0 && (
        <div style={{
          padding: '8px 12px', borderRadius: 8, marginBottom: 12,
          background: 'rgba(245,158,11,.08)', border: '1px solid rgba(245,158,11,.28)',
          fontSize: 12, color: '#D97706',
        }}>
          {warnings.map((w, i) => <div key={i}>⚠ {w}</div>)}
        </div>
      )}

      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <FinField label="Amount">
          <input type="number" inputMode="decimal" value={amount} onChange={e => setAmount(e.target.value)}
            placeholder="0" style={{ ..._finInputStyle, fontSize: 18, fontWeight: 700 }}/>
        </FinField>

        <div>
          <div style={{ fontSize: 11, color: 'var(--muted)', fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase', marginBottom: 6 }}>Currency</div>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {CURRENCIES.map(c => (
              <button key={c} onClick={() => setCurrency(c)} style={{
                padding: '4px 10px', borderRadius: 999, font: 'inherit', fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
                background: currency === c ? 'var(--text)' : 'var(--surface-2)',
                color: currency === c ? 'var(--bg)' : 'var(--muted)',
                border: '1px solid ' + (currency === c ? 'var(--text)' : 'var(--border)'),
              }}>{c}</button>
            ))}
          </div>
        </div>

        {isForeign && (
          <div style={{ display: 'flex', gap: 8 }}>
            <FinField label={'Rate (1 ' + currency + ' = ? EGP)'}>
              <input type="number" inputMode="decimal" value={rate} onChange={e => setRate(e.target.value)}
                placeholder="e.g. 50" style={_finInputStyle}/>
            </FinField>
            <FinField label="EGP equivalent" optional>
              <input type="number" inputMode="decimal" value={egp} onChange={e => setEgp(e.target.value)}
                placeholder={computedEgp != null ? String(computedEgp) : 'auto'} style={_finInputStyle}/>
            </FinField>
          </div>
        )}
        {isForeign && computedEgp != null && (
          <div style={{ padding: '6px 12px', borderRadius: 8, background: 'var(--c-fat-soft)', border: '1px solid rgba(87,132,216,0.2)', fontSize: 12.5 }}>
            ≈ <strong>{Math.round(computedEgp).toLocaleString()} EGP</strong>
          </div>
        )}

        <FinField label={isIncome ? 'Title' : 'Item'}>
          <input value={title} onChange={e => setTitle(e.target.value)}
            placeholder={isIncome ? 'e.g. May salary' : 'e.g. Coffee at Costa'} style={_finInputStyle}/>
        </FinField>

        <FinField label="Date">
          <input type="date" value={date} onChange={e => setDate(e.target.value)} style={_finInputStyle}/>
        </FinField>

        {isIncome ? (
          <FinEntityField label="Income Source" optional name={sourceName} setName={setSourceName}
            existing={incomeSources} matchedObj={matchedSource} createLabel="New source"
            emptyNote="No source — saved without one"/>
        ) : (
          <>
            <FinEntityField label="Category" name={catName} setName={setCatName}
              existing={expenseCats} matchedObj={matchedCat} createLabel="New category"
              emptyNote="No category set"/>
            <FinEntityField label="Subcategory" optional name={subName} setName={setSubName}
              existing={subParentSubs} matchedObj={matchedSub} createLabel="New subcategory"
              emptyNote={matchedCat ? 'No subcategory' : 'Match/confirm a category first'}/>
            <div>
              <div style={{ fontSize: 11, color: 'var(--muted)', fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase', marginBottom: 6 }}>
                Type {matchedCat ? <span style={{ fontWeight: 400, textTransform: 'none', fontSize: 10 }}>(existing category keeps its own)</span> : null}
              </div>
              <div style={{ display: 'flex', gap: 6 }}>
                {[{ k: '', label: '—' }, { k: 'need', label: 'Need' }, { k: 'want', label: 'Want' }].map(t => (
                  <button key={t.k} onClick={() => !matchedCat && setSpendingType(t.k)} disabled={!!matchedCat} style={{
                    padding: '5px 14px', borderRadius: 999, font: 'inherit', fontSize: 12, fontWeight: 600,
                    cursor: matchedCat ? 'default' : 'pointer', opacity: matchedCat ? 0.45 : 1,
                    background: spendingType === t.k ? 'var(--text)' : 'var(--surface-2)',
                    color: spendingType === t.k ? 'var(--bg)' : 'var(--muted)',
                    border: '1px solid ' + (spendingType === t.k ? 'var(--text)' : 'var(--border)'),
                  }}>{t.label}</button>
                ))}
              </div>
            </div>
          </>
        )}

        <FinEntityField label={isIncome ? 'Receiving Account' : 'Account'} name={acctName} setName={setAcctName}
          existing={accts} matchedObj={matchedAcct} createLabel="New account"
          emptyNote={'Empty → uses ' + ((accts[0] || {}).name || 'first account')}/>

        {!isIncome && (
          <FinField label="Payment Method" optional>
            <input value={paymentMethod} onChange={e => setPaymentMethod(e.target.value)}
              placeholder="e.g. Card, Cash" style={_finInputStyle}/>
          </FinField>
        )}

        <FinField label="Notes" optional>
          <textarea value={notes} onChange={e => setNotes(e.target.value)} rows={2} placeholder="Optional"
            style={{ ..._finInputStyle, resize: 'vertical' }}/>
        </FinField>

        <button onClick={confirm} disabled={!canSave} style={{
          marginTop: 4, width: '100%', padding: '12px 16px', borderRadius: 12,
          background: canSave ? accent : 'var(--surface-2)',
          color: canSave ? (isIncome ? '#fff' : 'var(--bg)') : 'var(--muted)',
          border: 'none', font: 'inherit', fontSize: 14, fontWeight: 700,
          cursor: canSave ? 'pointer' : 'default',
        }}>
          {saving ? 'Saving…' : 'Confirm & Save ' + (isIncome ? 'Income' : 'Expense')}
        </button>
        <div style={{ fontSize: 11, color: 'var(--muted)', textAlign: 'center' }}>
          Nothing is saved until you tap Confirm.
        </div>
      </div>
    </div>
  );
}

// ── Hints, placeholders, examples ────────────────────────────────────

function getHint(t) {
  if (t === 'diet')    return 'Paste a day of meals — calories, macros, optional items and notes — and we\'ll save them all at once.';
  if (t === 'finance') return 'Paste one Income Entry or Expense Entry. Start with the heading, then one field per line — you\'ll preview and edit everything before saving.';
  if (t === 'diary')   return 'Paste memories. One per line, prefix with date or include "YYYY-MM-DD" anywhere.';
  if (t === 'goal')    return 'Paste goals with their daily check-ins.';
  return '';
}

function getPlaceholder(t) {
  if (t === 'diet') return `e.g.
May 22, 2026
Breakfast: 420 kcal | carbs 45 | protein 30 | fat 12
Lunch — 650 cal | carbs 70 | protein 40 | fat 22
Dinner: 580 kcal, 55g carbs, 38g protein, 18g fat`;
  if (t === 'finance') return `Expense Entry
Date: ${_finToday()}
Item: Coffee at Costa
Category: Food
Subcategory: Coffee & Drinks
Type: want
Amount: 85
Currency: EGP
Account: Cash
Payment Method: Card
Notes: Morning coffee`;
  if (t === 'diary') return `e.g.
2026-05-22 — Went out with Kareem and Yasser to Maadi for dinner.
2026-05-23 — Coffee with Kareem in the morning.`;
  if (t === 'goal') return `e.g.
Goal: Exercise — 1x/day — done 2026-05-22 — miss 2026-05-23 — done 2026-05-24
Goal: Read — 1x/day — done 2026-05-22`;
  return '';
}

function getExample(t) {
  return getExamples(t).map(e => e.text).join('\n\n');
}

function getExamples(t) {
  if (t === 'diet') return [
    { title: 'Full Day with Items and Item Macros', text:
`May 27, 2026
Breakfast: 420 kcal | carbs 45 | protein 30 | fat 12
  - Oatmeal — 80g — 150 cal | carbs 25g | protein 5g | fat 3g
  - Banana — 120g — 110 cal | carbs 28g | protein 1g | fat 0g
  - Greek yogurt — 150g — 140 cal | carbs 9g | protein 15g | fat 4g
Lunch: 680 kcal | carbs 72 | protein 45 | fat 22
  - Grilled chicken breast — 150g — 248 cal | protein 42g | fat 5g
  - Brown rice — 160g — 175 cal | carbs 37g | protein 4g
  - Mixed salad — 100g — 35 cal | carbs 6g
Dinner: 540 kcal | carbs 50 | protein 38 | fat 18
  - Salmon fillet — 180g — 297 cal | protein 37g | fat 16g
  - Roasted vegetables — 200g — 120 cal | carbs 22g | fat 4g
Snack: 160 kcal | carbs 20 | protein 8 | fat 5
  - Almonds — 28g — 160 cal | carbs 6g | protein 6g | fat 14g
Note: Good energy today, hit all macros` },
    { title: 'Add Product to Local Library', text:
`إضافة منتج جديد

Product Name: تورتيلا شوفان / عيش تورتيلا
Brand: غير واضح من الصورة
Barcode: غير واضح
Category: Bakery / Tortilla
Package Size: 250 جرام
Serving Size: 35 جرام / 1 رغيف
Servings Per Package: 7

Nutrition Facts Per Serving:
- calories: 95 kcal
- total fat: 1.5g
- saturated fat: 0.33g
- trans fat: 0g
- cholesterol: 0mg
- total carbohydrate: 17.1g
- dietary fiber: 1.6g
- total sugars: unknown
- added sugars: 0g
- protein: 3.1g
- sodium: 423mg
- potassium: 53.6mg
- calcium: 22.2mg
- iron: 1.4mg

Ingredients:
- دقيق الشوفان الكامل
- دقيق القمح الكامل
- ماء
- ملح بحري قليل
- خميرة طبيعية
- خالي تمامًا من المواد الحافظة

Storage Instructions:
- يحفظ في مكان جاف وبارد وبعيدًا عن أشعة الشمس

Allergens:
- يحتوي على جلوتين / قمح

Notes:
- استخدم هذا المنتج كـ تورتيلا في حسابات الوجبات اليومية.
- لو المستخدم كتب "تورتيلا" أو "عيش تورتيلا" بدون تفاصيل، استخدم هذا المنتج كافتراضي.
- القيم الغذائية مأخوذة من العبوة مباشرة.` },
    { title: 'Add Recipe / Menu Item', text:
`Menu Item: بيتزا جبنة قريش 200 جرام

Category: Dinner

Ingredients:
- جبنة قريش — 200 جرام — 184 كالوري | carbs 8g | protein 32g | fat 4g
- جبنة موتزاريلا — 90 جرام — 266 كالوري | carbs 2g | protein 19.5g | fat 19.5g
- بيضة — 1 — 70 كالوري | carbs 1g | protein 6g | fat 5g
- دقيق شوفان — 30 جرام — 116 كالوري | carbs 19.8g | protein 7.5g | fat 0.7g
- مشروم — 30 جرام — 25 كالوري | carbs 1.5g | protein 1.5g | fat 0.5g
- صلصة — 2 معلقة كبيرة — 12 كالوري | carbs 4g | protein 0g | fat 0g
- طماطم وفلفل — 100 جرام — 27 كالوري | carbs 6g | protein 1g | fat 0g
- زيت زيتون — 3 نقط — 6 كالوري | carbs 0g | protein 0g | fat 0.7g

Item Total:
- calories: 706 kcal
- carbs: 42.3g
- protein: 67.5g
- fat: 30.4g

Serving:
- Full Pizza = 706 kcal
- Half Pizza = 353 kcal

Note:
- High protein pizza.
- Very filling.
- Good for dinner.
- Can remove mushroom if needed.` },
  ];
  if (t === 'finance') return [
    { title: 'Income Entry', text:
`Income Entry
Date: 2026-05-28
Source: Acme Corp
Income Type: Salary
Amount: 25000
Currency: EGP
Exchange Rate:
EGP Equivalent:
Receiving Account: Bank Account
Notes: Monthly salary` },
    { title: 'Expense Entry', text:
`Expense Entry
Date: 2026-05-28
Item: Coffee at Costa
Category: Food
Subcategory: Coffee & Drinks
Type: want
Amount: 85
Currency: EGP
Exchange Rate:
EGP Equivalent:
Account: Cash
Payment Method: Card
Notes: Morning coffee` },
  ];
  if (t === 'diary') return [
    { title: 'Single memory', text:
`2026-05-22 — Went out with Kareem and Yasser to Maadi for dinner. Great evening.` },
    { title: 'Multiple memories', text:
`2026-05-22 — Went out with Kareem and Yasser to Maadi for dinner. Great evening.
2026-05-23 — Coffee with Kareem in the morning. Quiet afternoon.
2026-05-24 — Read a book at home. Felt restful.` },
  ];
  if (t === 'goal') return [
    { title: 'One goal with check-ins', text:
`Goal: Exercise — 1x/day — done 2026-05-22 — miss 2026-05-23 — done 2026-05-24` },
    { title: 'Multiple goals', text:
`Goal: Exercise — 1x/day — done 2026-05-22 — miss 2026-05-23 — done 2026-05-24
Goal: Read — 1x/day — done 2026-05-22 — done 2026-05-23
Goal: Drink water — 8x/day — done 2026-05-22 — done 2026-05-23` },
  ];
  return [];
}

// ── Parsers ──────────────────────────────────────────────────────────

function detectInputType(raw) {
  const isProduct = /^product(?:\s+name)?\s*:/im.test(raw) || /^local food\s*:/im.test(raw)
    || /^brand\s*:/im.test(raw) || /nutrition facts/i.test(raw);
  const isRecipe = /^recipe\s*:/im.test(raw) || /^menu item\s*:/im.test(raw)
    || /serving options?\s*:/i.test(raw) || /^item total\s*:/im.test(raw);
  if (isProduct) return 'local-product';
  if (isRecipe)  return 'recipe';
  return 'diet';
}

function parseLocalProduct(raw) {
  const lines = raw.split(/\r?\n/);
  const _id = () => 'loc_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5);

  let name = '', brand = '', barcode = '', categoryText = '', packageSize = '';
  let servingSizeText = '', serving = 100, unit = 'g', servingsPerPackage = '';
  let cal = '', protein_g = '', carbs_g = '', fat_g = '';
  let totalFat_g = '', saturatedFat_g = '', transFat_g = '', cholesterol_mg = '';
  let totalCarbs_g = '', dietaryFiber_g = '', totalSugars_g = '', addedSugars_g = '';
  let sodium_mg = '', potassium_mg = '', calcium_mg = '', iron_mg = '';
  let ingredientsList = [], storageList = [], allergensList = [], notesList = [];
  const warnings = [];
  let section = null; // null | 'nutrition' | 'ingredients' | 'storage' | 'allergens' | 'notes'

  function parseNutritionLine(text) {
    const val = text.match(/:\s*(\d+(?:\.\d+)?)/);
    const isUnknown = /unknown|غير معروف/i.test(text);
    const v = (val && !isUnknown) ? +val[1] : '';
    if      (/^calories/i.test(text))        { if (cal === '')        cal          = v; }
    else if (/^total fat/i.test(text))       { totalFat_g    = v; if (fat_g   === '') fat_g   = v; }
    else if (/^saturated fat/i.test(text))   { saturatedFat_g = v; }
    else if (/^trans fat/i.test(text))       { transFat_g     = v; }
    else if (/^cholesterol/i.test(text))     { cholesterol_mg  = v; }
    else if (/^total carb/i.test(text))      { totalCarbs_g  = v; if (carbs_g === '') carbs_g = v; }
    else if (/^dietary fiber/i.test(text))   { dietaryFiber_g = v; }
    else if (/^total sugars/i.test(text))    { totalSugars_g  = v; }
    else if (/^added sugars/i.test(text))    { addedSugars_g  = v; }
    else if (/^protein/i.test(text))         { if (protein_g === '') protein_g = v; }
    else if (/^sodium/i.test(text))          { sodium_mg      = v; }
    else if (/^potassium/i.test(text))       { potassium_mg   = v; }
    else if (/^calcium/i.test(text))         { calcium_mg     = v; }
    else if (/^iron/i.test(text))            { iron_mg        = v; }
  }

  for (const rawLine of lines) {
    const line = rawLine.trim();
    if (!line) { section = null; continue; }

    // Section headers (highest priority)
    if (/^nutrition facts/i.test(line))           { section = 'nutrition';   continue; }
    if (/^ingredients?\s*:\s*$/i.test(line))      { section = 'ingredients'; continue; }
    if (/^storage instructions?\s*:\s*$/i.test(line)) { section = 'storage'; continue; }
    if (/^allergens?\s*:\s*$/i.test(line))        { section = 'allergens';   continue; }
    if (/^notes?\s*:\s*$/i.test(line))            { section = 'notes';       continue; }

    // Bullet line inside a section
    if (section && /^\s*[-•*]\s/.test(rawLine)) {
      const content = line.replace(/^\s*[-•*]\s*/, '').trim();
      if      (section === 'nutrition')    parseNutritionLine(content);
      else if (section === 'ingredients') ingredientsList.push(content);
      else if (section === 'storage')     storageList.push(content);
      else if (section === 'allergens')   allergensList.push(content);
      else if (section === 'notes')       notesList.push(content);
      continue;
    }

    // Non-bullet, non-header → product fields (resets section)
    section = null;
    let m;
    if ((m = line.match(/^(?:product name|product|local food)\s*:\s*(.+)/i))) { name = m[1].trim(); continue; }
    if ((m = line.match(/^brand\s*:\s*(.+)/i)))   { brand    = m[1].trim(); continue; }
    if ((m = line.match(/^barcode\s*:\s*(.+)/i))) { barcode  = m[1].trim(); continue; }
    if ((m = line.match(/^category\s*:\s*(.+)/i))) { categoryText = m[1].trim(); continue; }
    if ((m = line.match(/^package size\s*:\s*(.+)/i))) { packageSize = m[1].trim(); continue; }
    if ((m = line.match(/^serving size\s*:\s*(.+)/i))) {
      servingSizeText = m[1].trim();
      const numM = servingSizeText.match(/(\d+(?:\.\d+)?)\s*(جرام|g|ml|piece|tbsp|tsp|cup|slice)?/i);
      if (numM) { serving = +numM[1]; unit = (numM[2] === 'جرام') ? 'g' : (numM[2] || 'g').toLowerCase(); }
      continue;
    }
    if ((m = line.match(/^per serving\s*:\s*(\d+(?:\.\d+)?)\s*(g|ml|piece|tbsp|tsp|cup|slice)?/i))) {
      serving = +m[1]; if (m[2]) unit = m[2].toLowerCase(); continue;
    }
    if ((m = line.match(/^servings? per package\s*:\s*(.+)/i))) { servingsPerPackage = m[1].trim(); continue; }
    // Inline sections (old simple format)
    if ((m = line.match(/^ingredients?\s*:\s*(.+)/i))) { ingredientsList = [m[1].trim()]; continue; }
    if ((m = line.match(/^allergens?\s*:\s*(.+)/i)))   { allergensList  = [m[1].trim()]; continue; }
    if ((m = line.match(/^notes?\s*:\s*(.+)/i)))       { notesList      = [m[1].trim()]; continue; }
    // Remaining: inline nutrition values ("Calories: 120" / "Protein: 25g | Carbs: 2g | Fat: 1g")
    const calM = line.match(/^calories?\s*:\s*(\d+(?:\.\d+)?)/i) || line.match(/(\d+(?:\.\d+)?)\s*kcal\b/i);
    if (calM && cal === '') cal = +(calM[1] || calM[2]);
    const pM = line.match(/protein\s*:\s*(\d+(?:\.\d+)?)/i);
    const cM = line.match(/carbs?\s*:\s*(\d+(?:\.\d+)?)/i);
    const fM = line.match(/fat\s*:\s*(\d+(?:\.\d+)?)/i);
    if (pM && protein_g === '') protein_g = +pM[1];
    if (cM && carbs_g   === '') carbs_g   = +cM[1];
    if (fM && fat_g     === '') fat_g     = +fM[1];
  }

  if (fat_g   === '' && totalFat_g   !== '') fat_g   = totalFat_g;
  if (carbs_g === '' && totalCarbs_g !== '') carbs_g = totalCarbs_g;
  if (!name)      warnings.push('Product name is missing.');
  if (cal === '') warnings.push('Calories are missing.');

  const hasArabic = /[؀-ۿ]/.test(name);
  return {
    id: _id(),
    name, nameAr: hasArabic ? name : '',
    brand, barcode, category: 'other', categoryLabel: categoryText,
    serving: +serving || 100, unit: unit || 'g',
    servingSizeText, packageSize, servingsPerPackage,
    cal: +cal || 0,
    protein_g: +protein_g || 0, carbs_g: +carbs_g || 0, fat_g: +fat_g || 0,
    p: +protein_g || 0, c: +carbs_g || 0, f: +fat_g || 0,
    qty: `${+serving || 100}${unit || 'g'}`,
    nutritionFacts: {
      totalFat_g, saturatedFat_g, transFat_g, cholesterol_mg,
      totalCarbs_g, dietaryFiber_g, totalSugars_g, addedSugars_g,
      protein_g: +protein_g || 0,
      sodium_mg, potassium_mg, calcium_mg, iron_mg,
    },
    ingredients: ingredientsList.join(', '),
    storageInstructions: storageList.join('. '),
    allergens: allergensList.join(', '),
    notes: notesList.join(' '),
    tags: [], emoji: '⭐',
    source: 'paste', type: 'local-product',
    createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
    _warnings: warnings,
  };
}

function parseRecipe(raw) {
  const lines = raw.split(/\r?\n/);
  const _id = () => 'loc_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
  let name = '', category = '', serving = 1, unit = 'serving';
  let cal = '', protein_g = '', carbs_g = '', fat_g = '';
  let items = [], servingOptions = [], noteLines = [];
  const warnings = [];
  let section = null; // null | 'ingredients' | 'itemtotal' | 'serving' | 'note'

  for (const rawLine of lines) {
    const line = rawLine.trim();
    if (!line) { section = null; continue; }

    // ── Section headers (require nothing after colon) ─────────────────────
    if (/^ingredients?\s*:\s*$/i.test(line))  { section = 'ingredients'; continue; }
    if (/^item total\s*:\s*$/i.test(line))    { section = 'itemtotal';   continue; }
    if (/^serving\s*:\s*$/i.test(line))       { section = 'serving';     continue; }
    if (/^notes?\s*:\s*$/i.test(line))        { section = 'note';        continue; }

    // ── Bullet line inside a section ──────────────────────────────────────
    if (section && /^\s*[-•*]\s/.test(rawLine)) {
      const content = line.replace(/^\s*[-•*]\s*/, '').trim();
      if (section === 'ingredients') {
        // Reuse diet item parser — handles Arabic names + em-dash separators + كالوري
        const it = _dietItem(line);
        if (it) items.push(it);
      } else if (section === 'itemtotal') {
        // "calories: 706 kcal" / "carbs: 42.3g" / "protein: 67.5g" / "fat: 30.4g"
        const calM = content.match(/^calories?\s*:\s*(\d+(?:\.\d+)?)/i)
                  || content.match(/(\d+(?:\.\d+)?)\s*kcal\b/i);
        if (calM && cal === '') cal = +(calM[1] || calM[2]);
        const cM = content.match(/^carbs?\s*:\s*(\d+(?:\.\d+)?)/i);
        const pM = content.match(/^protein\s*:\s*(\d+(?:\.\d+)?)/i);
        const fM = content.match(/^fat\s*:\s*(\d+(?:\.\d+)?)/i);
        if (cM && carbs_g   === '') carbs_g   = +cM[1];
        if (pM && protein_g === '') protein_g = +pM[1];
        if (fM && fat_g     === '') fat_g     = +fM[1];
      } else if (section === 'serving') {
        // "Full Pizza = 706 kcal" / "Half Pizza = 353 kcal"
        const calM = content.match(/(\d+(?:\.\d+)?)\s*kcal\b/i)
                  || content.match(/=\s*(\d+(?:\.\d+)?)/);
        const calories = calM ? +(calM[1] || calM[2]) : 0;
        const label = content
          .replace(/\s*=\s*\d+(?:\.\d+)?\s*kcal\b/i, '')
          .replace(/\s*=\s*\d+(?:\.\d+)?$/, '')
          .trim();
        servingOptions.push({ label: label || content, calories });
      } else if (section === 'note') {
        noteLines.push(content);
      }
      continue;
    }

    // ── Bullet line outside any section → treat as ingredient ─────────────
    if (/^\s*[-•*]\s/.test(rawLine)) {
      const it = _dietItem(line);
      if (it) items.push(it);
      continue;
    }

    // ── Named field lines (resets section) ────────────────────────────────
    section = null;
    let m;
    if ((m = line.match(/^(?:recipe|menu item)\s*:\s*(.+)/i))) { name = m[1].trim(); continue; }
    if ((m = line.match(/^category\s*:\s*(.+)/i)))              { category = m[1].trim(); continue; }
    if ((m = line.match(/^serving\s*:\s*(.+)/i))) {
      // Inline: "Serving: 1 bowl (380g)"
      const numM = m[1].trim().match(/(\d+(?:\.\d+)?)\s*([a-zA-Z]+)/);
      if (numM) { serving = +numM[1]; unit = numM[2]; }
      continue;
    }
    if ((m = line.match(/^serving options?\s*:\s*(.+)/i))) {
      // Inline: "Serving options: half bowl 225 kcal | double 900 kcal"
      servingOptions = m[1].split('|').map(seg => {
        const calM = seg.match(/(\d+(?:\.\d+)?)\s*kcal/i);
        const lbl  = seg.replace(/\d+\s*kcal/i, '').trim();
        return { label: lbl || seg.trim(), calories: calM ? +calM[1] : 0 };
      });
      continue;
    }
    if ((m = line.match(/^notes?\s*:\s*(.+)/i))) { noteLines = [m[1].trim()]; continue; }
    // Inline total macros: "Calories: 450 | Carbs: 68g | Protein: 18g | Fat: 10g"
    if (/calories?|kcal/i.test(line) || /protein|carbs?|fat/i.test(line)) {
      const calM = line.match(/calories?\s*:\s*(\d+(?:\.\d+)?)/i)
                || line.match(/(\d+(?:\.\d+)?)\s*kcal\b/i);
      if (calM && cal === '') cal = +(calM[1] || calM[2]);
      const pM = line.match(/protein\s*:\s*(\d+(?:\.\d+)?)/i);
      const cM = line.match(/carbs?\s*:\s*(\d+(?:\.\d+)?)/i);
      const fM = line.match(/fat\s*:\s*(\d+(?:\.\d+)?)/i);
      if (pM && protein_g === '') protein_g = +pM[1];
      if (cM && carbs_g   === '') carbs_g   = +cM[1];
      if (fM && fat_g     === '') fat_g     = +fM[1];
    }
  }

  if (!name)      warnings.push('Recipe name is missing.');
  if (cal === '') warnings.push('Total calories are missing.');

  const note = noteLines.filter(Boolean).join(' ');

  return {
    id: _id(), name, category, serving: +serving || 1, unit: unit || 'serving',
    cal: +cal || 0,
    protein_g: +protein_g || 0, carbs_g: +carbs_g || 0, fat_g: +fat_g || 0,
    p: +protein_g || 0, c: +carbs_g || 0, f: +fat_g || 0,
    qty: `${+serving || 1} ${unit || 'serving'}`,
    items, servingOptions, note,
    tags: [], emoji: '🍳',
    source: 'paste', type: 'recipe',
    createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
    _warnings: warnings,
  };
}

function parseFor(type, raw, forcedSubType) {
  if (!raw || !raw.trim()) return { rows: [], error: 'Nothing pasted.' };
  if (type === 'diet') {
    const subType = forcedSubType || detectInputType(raw);
    if (subType === 'local-product') {
      const data = parseLocalProduct(raw);
      return { type: 'local-product', rows: data.name ? [data] : [], data, warnings: data._warnings };
    }
    if (subType === 'recipe') {
      const data = parseRecipe(raw);
      return { type: 'recipe', rows: data.name ? [data] : [], data, warnings: data._warnings };
    }
    const r = parseDiet(raw);
    return { ...r, type: 'diet' };
  }
  if (type === 'finance') return parseFinance(raw, forcedSubType);
  if (type === 'diary')   return parseDiary(raw);
  if (type === 'goal')    return parseGoals(raw);
  return { rows: [], error: 'Unsupported project type.' };
}

// ── Diet parser helpers (module-level so Preview can also use them) ──

// Extract calorie value — handles BOTH "420 kcal" (value-first) AND "calories: 420"
// Supported keywords: kcal, cal, calories, calorie, كالوري
function _dietCal(text) {
  if (!text) return '';
  // "NUMBER kcal / calories / calorie / cal / كالوري"  (value BEFORE keyword)
  // Order: longest alternatives first so "kcal" wins over "cal" substring
  // NOTE: use (?!\w) instead of \b — Arabic chars are not \w so \b breaks كالوري matching
  let m = text.match(/(\d+(?:\.\d+)?)\s*(?:kcal|calories?|cal|كالوري)(?!\w)/i);
  if (m) return +m[1];
  // "calories: NUMBER" / "kcal NUMBER"  (keyword BEFORE value)
  m = text.match(/(?:kcal|calories?|cal|كالوري)\s*[:=]?\s*(\d+(?:\.\d+)?)/i);
  if (m) return +m[1];
  return '';
}

// Macro keyword patterns — full names + single-letter abbreviations (C/P/F)
const _KW_CARBS   = 'carb(?:ohydrate)?s?|C';
const _KW_PROTEIN = 'protein|prot|P';
const _KW_FAT     = 'fats?|F';

// Extract a macro — "keyword [: ] NUMBER [g]"
// kwPat is matched at a word boundary; supports full names and single-letter C/P/F
function _dietMacro(text, kwPat) {
  if (!text) return '';
  const re = new RegExp('(?:^|[|,\\s])(?:' + kwPat + ')\\s*[:=]?\\s*(\\d+(?:\\.\\d+)?)\\s*g?', 'i');
  const m = text.match(re);
  return m ? +m[1] : '';
}

// Parse "May 26, 2026" or "2026-05-26" → "YYYY-MM-DD"
const _DIET_MO = {january:1,february:2,march:3,april:4,may:5,june:6,july:7,august:8,
  september:9,october:10,november:11,december:12,
  jan:1,feb:2,mar:3,apr:4,jun:6,jul:7,aug:8,sep:9,sept:9,oct:10,nov:11,dec:12};
function _dietDate(line) {
  let m = line.match(/(\d{4})-(\d{2})-(\d{2})/);
  if (m) return m[1] + '-' + m[2] + '-' + m[3];
  m = line.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})(?!\d)(?:[\s,]+(\d{4}))?/i);
  if (m) {
    const mo = _DIET_MO[m[1].toLowerCase()];
    const d = +m[2], y = m[3] ? +m[3] : new Date().getFullYear();
    return y + '-' + String(mo).padStart(2,'0') + '-' + String(d).padStart(2,'0');
  }
  return '';
}

// Parse one item line: "- name — amount — 150 cal | carbs 18g | protein 6g | fat 6g"
// Also handles Arabic names and amounts with mixed separators.
function _dietItem(trimmed) {
  // Remove leading bullet/dash (with any leading whitespace)
  const stripped = trimmed.replace(/^\s*[-•*]\s*/, '');
  // Split on em-dash, en-dash (— –), or space-hyphen-space ( - )
  const parts = stripped.split(/\s*[—–]\s*|\s+-\s+/);
  let name = '', amount = '', calText = '';
  if (parts.length >= 3) {
    name    = parts[0].trim();
    amount  = parts[1].trim();
    calText = parts.slice(2).join(' ');
  } else if (parts.length === 2) {
    name    = parts[0].trim();
    calText = parts[1];
  } else {
    // No dash separator — fall back: everything is calText, name from before first ":"
    const ci = stripped.indexOf(':');
    if (ci > 0) { name = stripped.slice(0, ci).trim(); calText = stripped.slice(ci + 1); }
    else { calText = stripped; }
  }
  const cal = _dietCal(calText);
  if (cal === '') return null; // must have a calorie figure to be a valid item
  return {
    name, amount, calories: cal,
    carbs_g:   _dietMacro(calText, _KW_CARBS),
    protein_g: _dietMacro(calText, _KW_PROTEIN),
    fat_g:     _dietMacro(calText, _KW_FAT),
  };
}

// ── Diet parser (streaming, handles Arabic/English mixed input) ──────
function parseDiet(raw) {
  const lines = raw.split(/\r?\n/);
  const rows  = [];
  let curDate = '';
  let noteLines = null; // null = not yet collecting; [] = collecting
  let dailyTotal = null;
  const _id = () => 'pp_' + Math.random().toString(36).slice(2, 9);

  // Meal name patterns — English + Arabic
  const MPAT = 'breakfast|brunch|lunch|dinner|supper|snacks?|فطار|غداء|عشاء|سناك';
  // Meal header: "Lunch: 811 kcal | ..." or "Lunch:" or "Lunch" (bare)
  const MEAL_HDR   = new RegExp('^\\s*(' + MPAT + ')\\s*[:\\-—–]?\\s*(.*)$', 'i');
  // Meal total line: "Lunch Total: ..."
  const MEAL_TOT   = new RegExp('^\\s*(' + MPAT + ')\\s+(?:total|إجمالي)\\s*[:：]\\s*(.+)$', 'i');
  // Daily total: "Total: 1230 kcal | ..."
  const DAILY_TOT  = /^\s*(?:total|الإجمالي|المجموع|إجمالي)\s*[:：]\s*(.+)/i;
  // Item line: starts with - • * (possibly indented)
  const ITEM_LN    = /^\s*[-•*]\s/;

  // Normalize meal key to canonical English
  const MEAL_NORM = { فطار:'breakfast', غداء:'lunch', عشاء:'dinner', سناك:'snack',
    snacks:'snack', supper:'dinner', brunch:'breakfast' };
  const MEAL_EMOJI = { breakfast:'🌅', lunch:'🍽️', dinner:'🌙', snack:'🍎' };
  function normMeal(n) {
    const l = (n || '').trim().toLowerCase();
    return MEAL_NORM[l] || MEAL_NORM[n.trim()] || l;
  }

  let curMeal = null;

  for (const line of lines) {
    const tr = line.trim();

    // ── Inside note section — collect everything verbatim ──
    if (noteLines !== null) { noteLines.push(tr); continue; }

    if (!tr) continue;

    // ── Note header: "Note" / "Note:" / "Note: inline text" ──
    const noteInline = tr.match(/^\s*(?:notes?|ملاحظات?|ملاحظة)\s*[:：]\s*(.+)/i);
    const noteOnly   = !noteInline && /^\s*(?:notes?|ملاحظات?|ملاحظة)\s*:?\s*$/i.test(tr);
    if (noteInline) { noteLines = [noteInline[1].trim()]; continue; }
    if (noteOnly)   { noteLines = []; continue; }

    // ── Daily total line ──
    const dtM = tr.match(DAILY_TOT);
    if (dtM) {
      dailyTotal = {
        calories:  _dietCal(dtM[1]),
        carbs_g:   _dietMacro(dtM[1], _KW_CARBS),
        protein_g: _dietMacro(dtM[1], _KW_PROTEIN),
        fat_g:     _dietMacro(dtM[1], _KW_FAT),
      };
      continue;
    }

    // ── Date line (skip if it's also a meal header) ──
    const dt = _dietDate(tr);
    if (dt && !MEAL_HDR.test(tr)) { curDate = dt; continue; }

    // ── Meal total line: "Lunch Total: 811 kcal ..." ──
    const mtM = tr.match(MEAL_TOT);
    if (mtM && curMeal) {
      const c = _dietCal(mtM[2]);           if (c !== '') curMeal.calories  = c;
      const cb = _dietMacro(mtM[2], _KW_CARBS); if (cb !== '') curMeal.carbs_g   = cb;
      const pr = _dietMacro(mtM[2], _KW_PROTEIN);               if (pr !== '') curMeal.protein_g = pr;
      const ft = _dietMacro(mtM[2], _KW_FAT);                    if (ft !== '') curMeal.fat_g     = ft;
      continue;
    }

    // ── Meal header ──
    const mH = tr.match(MEAL_HDR);
    if (mH) {
      if (!curDate) curDate = todayISO();
      const mealKey = normMeal(mH[1]);
      const rest    = (mH[2] || '').trim();
      const row = {
        id: _id(), date: curDate, meal: mealKey,
        name:      mealKey.charAt(0).toUpperCase() + mealKey.slice(1),
        emoji:     MEAL_EMOJI[mealKey] || '🍽️', time: '',
        calories:  _dietCal(rest) || 0,
        carbs_g:   _dietMacro(rest, _KW_CARBS),
        protein_g: _dietMacro(rest, _KW_PROTEIN),
        fat_g:     _dietMacro(rest, _KW_FAT),
        items: [], source: 'paste',
      };
      rows.push(row);
      curMeal = row;
      continue;
    }

    // ── Item line ──
    if (ITEM_LN.test(line) && curMeal) {
      const item = _dietItem(tr);
      if (item) curMeal.items.push(item);
      continue;
    }
    // Unknown line (metadata, blank labels, extra fields) — skip safely
  }

  // Build note from collected lines
  let noteOut = null;
  if (noteLines && noteLines.length > 0) {
    const text = noteLines.filter(Boolean).join('\n').trim();
    if (text) noteOut = { date: curDate || todayISO(), text };
  }

  return { rows, note: noteOut, dailyTotal,
    error: rows.length ? null : 'No meal lines detected.' };
}

// ── Finance structured entries (Income Entry / Expense Entry) ─────────
function _finToday() {
  const d = new Date();
  return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
}
function _finNum(v) {
  if (v == null || v === '') return null;
  const m = String(v).replace(/,/g, '').match(/-?\d+(?:\.\d+)?/);
  return m ? parseFloat(m[0]) : null;
}
function _finCur(v) {
  if (!v) return '';
  const m = String(v).match(/\b(EGP|USD|EUR|GBP|SAR|AED)\b/i);
  return m ? m[1].toUpperCase() : '';
}
function _finAmount(v) {
  if (!v) return { amount: 0, currency: '' };
  const s = String(v).replace(/,/g, '');
  const numM = s.match(/-?\d+(?:\.\d+)?/);
  return { amount: numM ? Math.abs(parseFloat(numM[0])) : 0, currency: _finCur(s) };
}
// Parse "Key: value" lines into a normalized {key -> value} dict. Heading lines
// (no colon) are ignored, and unknown keys are simply preserved without breaking.
function _finFields(raw) {
  const out = {};
  (raw || '').split(/\r?\n/).forEach(line => {
    const m = line.match(/^\s*([A-Za-z][A-Za-z0-9 _\/-]*?)\s*:\s*(.*)$/);
    if (!m) return;
    const key = m[1].trim().toLowerCase().replace(/[_\/-]+/g, ' ').replace(/\s+/g, ' ');
    const val = m[2].trim();
    if (!(key in out)) out[key] = val; // first occurrence wins
  });
  return out;
}
function detectFinanceEntryType(raw) {
  if (/^\s*income\s+entry\b/im.test(raw))  return 'income-entry';
  if (/^\s*expense\s+entry\b/im.test(raw)) return 'expense-entry';
  return null;
}
function parseFinanceIncomeEntry(raw) {
  const f = _finFields(raw);
  const date       = _dietDate(f['date'] || '') || _finToday();
  const source     = (f['source'] || '').trim();
  const incomeType = (f['income type'] || f['type'] || '').trim();
  let currency = _finCur(f['currency'] || '');
  const amt = _finAmount(f['amount']);
  if (!currency) currency = amt.currency || 'EGP';
  const exchangeRate  = _finNum(f['exchange rate'] || f['rate']);
  const egpEquivalent = _finNum(f['egp equivalent'] || f['egp equiv'] || f['egp']);
  const account = (f['receiving account'] || f['account'] || '').trim();
  const notes   = (f['notes'] || f['note'] || '').trim();
  const warnings = [];
  if (!amt.amount) warnings.push('Amount is missing or zero.');
  return {
    kind: 'income', date,
    title: source || incomeType || 'Income',
    sourceName: incomeType || source,   // → income source (subcategory of Income)
    incomeType,
    amount: amt.amount, currency,
    exchangeRate, egpEquivalent,
    accountName: account,
    notes, _warnings: warnings,
  };
}
function parseFinanceExpenseEntry(raw) {
  const f = _finFields(raw);
  const date        = _dietDate(f['date'] || '') || _finToday();
  const item        = (f['item'] || f['title'] || '').trim();
  const category    = (f['category'] || '').trim();
  const subcategory = (f['subcategory'] || f['sub category'] || '').trim();
  const typeRaw     = (f['type'] || f['spending type'] || '').trim().toLowerCase();
  let currency = _finCur(f['currency'] || '');
  const amt = _finAmount(f['amount']);
  if (!currency) currency = amt.currency || 'EGP';
  const exchangeRate  = _finNum(f['exchange rate'] || f['rate']);
  const egpEquivalent = _finNum(f['egp equivalent'] || f['egp equiv'] || f['egp']);
  const account = (f['account'] || '').trim();
  const paymentMethod = (f['payment method'] || f['method'] || '').trim();
  const notes   = (f['notes'] || f['note'] || '').trim();
  const warnings = [];
  if (!amt.amount) warnings.push('Amount is missing or zero.');
  return {
    kind: 'expense', date,
    title: item || category || 'Expense',
    categoryName: category,
    subcategoryName: subcategory,
    spendingType: (typeRaw === 'need' || typeRaw === 'want') ? typeRaw : '',
    amount: amt.amount, currency,
    exchangeRate, egpEquivalent,
    accountName: account, paymentMethod,
    notes, _warnings: warnings,
  };
}

function parseFinance(raw, forcedSubType) {
  const sub = forcedSubType || detectFinanceEntryType(raw);
  if (sub === 'income-entry') {
    const entry = parseFinanceIncomeEntry(raw);
    return { type: 'income-entry', entry, rows: [entry], warnings: entry._warnings };
  }
  if (sub === 'expense-entry') {
    const entry = parseFinanceExpenseEntry(raw);
    return { type: 'expense-entry', entry, rows: [entry], warnings: entry._warnings };
  }
  // ── Legacy single-line fallback: "YYYY-MM-DD title amount category" ──
  const lines = raw.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
  const rows = [];
  const _id = () => 'pp_' + Math.random().toString(36).slice(2, 9);
  for (const line of lines) {
    const m = line.match(/^(\d{4}-\d{2}-\d{2})\s+(.+?)\s+([+-]?\d+(?:\.\d+)?)\s+(\w+)(?:\s+(income|expense))?$/i);
    if (!m) continue;
    const [, date, title, rawAmt, cat, explicitKind] = m;
    const amt = parseFloat(rawAmt);
    const kind = explicitKind ? explicitKind.toLowerCase()
                : (rawAmt.startsWith('+') || /income|salary|revenue/i.test(cat)) ? 'income' : 'expense';
    rows.push({ id: _id(), date, kind, amount: Math.abs(amt), category: cat, title: title.trim(), notes: '' });
  }
  return { type: 'finance-lines', rows,
    error: rows.length ? null : 'Paste an "Income Entry" or "Expense Entry" using a template below.' };
}

function parseDiary(raw) {
  const lines = raw.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
  const rows = [];
  const _id = () => 'pp_' + Math.random().toString(36).slice(2, 9);
  for (const line of lines) {
    // "2026-05-22 — text" / "2026-05-22 : text" / "2026-05-22 text"
    const m = line.match(/^(\d{4})-(\d{2})-(\d{2})\s*[—–\-:]?\s*(.+)$/);
    if (!m) continue;
    const [, y, mo, d, text] = m;
    rows.push({
      id: _id(),
      day: +d, month: +mo, year: +y,
      dateKey: `${y}-${mo}-${d}`,
      title: '',
      eventText: text.trim(),
      source: 'paste',
      tags: [],
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    });
  }
  return { rows, error: rows.length ? null : 'No valid diary rows. Expected: "YYYY-MM-DD text".' };
}

function parseGoals(raw) {
  const lines = raw.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
  const rows = [];
  const _id = () => 'pp_' + Math.random().toString(36).slice(2, 9);
  for (const line of lines) {
    // "Goal: Name — 1x/day — done DATE — miss DATE — done DATE"
    const m = line.match(/^Goal:\s*(.+?)\s*[—–\-]\s*(\d+)\s*x\/day(.*)$/i);
    if (!m) continue;
    const [, name, tpd, rest] = m;
    const log = {};
    const checks = rest.match(/(done|miss|skip)\s+(\d{4}-\d{2}-\d{2})/gi) || [];
    for (const c of checks) {
      const cm = c.match(/(done|miss|skip)\s+(\d{4}-\d{2}-\d{2})/i);
      if (cm) {
        const slot = cm[1].toLowerCase() === 'done' ? 'done' : 'miss';
        log[cm[2]] = [slot];
      }
    }
    rows.push({
      id: _id(), name: name.trim(),
      timesPerDay: +tpd || 1,
      weekdays: [0,1,2,3,4,5,6],
      startDate: Object.keys(log).sort()[0] || todayISO(),
      durationDays: 365,
      color: '#1F3D2E',
      log,
    });
  }
  return { rows, error: rows.length ? null : 'No valid goals. Expected: "Goal: Name — Nx/day — done DATE".' };
}

window.PasteParseScreen = PasteParseScreen;
