/* Nutri — Weight log + InBody tab (inside Calories).
   Parser + entry list + paste&parse (preview→confirm, dup protection) + comparison +
   analysis + charts. InBody entries live in settings.inBodyEntries; weight in settings.weights.
   Reuses global helpers (useStore, nutriStyles, EmptyState, inputStyle, NUTRI_TODAY, isoOf). */

/* ── date + parser helpers ────────────────────────────────────────────── */
function _ibDate(s) {
  const t = (s || '').trim();
  let m = t.match(/^(\d{4})-(\d{2})-(\d{2})/); if (m) return m[1] + '-' + m[2] + '-' + m[3];
  const d = new Date(t); if (!isNaN(d.getTime())) return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
  return '';
}
function _ibEsc(s) { return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
const _IB_HEADERS = ['Basic Data', 'Body Composition Analysis', 'Muscle-Fat Analysis', 'Obesity Analysis', 'Research Parameters', 'Weight Control', 'Segmental Lean Analysis', 'Segmental Fat Analysis', 'Body Composition History', 'Goal', 'Daily Nutrition Evaluation Targets', 'Note'];
function _ibSection(text, header) {
  const lines = text.split('\n');
  const isHeader = (line, h) => new RegExp('^\\s*' + _ibEsc(h) + '\\s*:?\\s*$', 'i').test(line.trim());
  const start = lines.findIndex(l => isHeader(l, header));
  if (start < 0) return '';
  let end = lines.length;
  for (let i = start + 1; i < lines.length; i++) {
    if (_IB_HEADERS.some(h => h !== header && isHeader(lines[i], h))) { end = i; break; }
  }
  return lines.slice(start + 1, end).map(l => l.replace(/^\s*[-•]\s*/, '').trim()).filter(Boolean).join('\n');
}
function parseInBody(text) {
  const t = (text || '').replace(/\r/g, '');
  const out = {};
  const num = (label) => { const m = t.match(new RegExp('(?:^|\\n)\\s*[-•]?\\s*' + _ibEsc(label) + '\\s*:?\\s*(-?\\d+(?:\\.\\d+)?)', 'i')); return m ? +m[1] : undefined; };
  const str = (label) => { const m = t.match(new RegExp('(?:^|\\n)\\s*[-•]?\\s*' + _ibEsc(label) + '\\s*:?\\s*([^\\n]+)', 'i')); return m ? m[1].trim() : undefined; };
  const range = (label) => { const m = t.match(new RegExp(_ibEsc(label) + '\\s*:?\\s*(-?\\d+(?:\\.\\d+)?)\\s*(?:[–—\\-]|to)+\\s*(-?\\d+(?:\\.\\d+)?)', 'i')); return m ? [+m[1], +m[2]] : []; };

  out.date = _ibDate(str('Date') || '');
  // basic
  out.age = num('Age');
  const g = str('Gender'); out.gender = g ? g.replace(/[^A-Za-z].*$/, '').trim() : undefined;
  out.height = num('Height');
  out.weight = num('Weight');
  out.inBodyScore = num('InBody Score');
  // composition / research
  out.totalBodyWater = num('Total Body Water');
  out.protein = num('Protein');
  out.minerals = num('Minerals');
  out.bodyFatMass = num('Body Fat Mass');
  out.skeletalMuscleMass = num('Skeletal Muscle Mass');
  out.fatFreeMass = num('Fat Free Mass');
  out.basalMetabolicRate = num('Basal Metabolic Rate');
  out.bmi = num('BMI');
  out.pbf = num('PBF');
  out.waistHipRatio = num('Waist-Hip Ratio');
  out.visceralFatLevel = num('Visceral Fat Level');
  out.obesityDegree = num('Obesity Degree');
  out.boneMineralContent = num('Bone Mineral Content');
  // weight control
  out.targetWeight = num('Target Weight');
  out.weightControl = num('Weight Control');
  out.fatControl = num('Fat Control');
  out.muscleControl = num('Muscle Control');
  // segmental
  const leanSec = _ibSection(t, 'Segmental Lean Analysis');
  const fatSec = _ibSection(t, 'Segmental Fat Analysis');
  const seg = (sec, part) => { if (!sec) return {}; const m = sec.match(new RegExp(_ibEsc(part) + '\\s*:?\\s*(-?\\d+(?:\\.\\d+)?)\\s*kg\\s*\\|\\s*(-?\\d+(?:\\.\\d+)?)\\s*%\\s*\\|\\s*(\\w+)', 'i')); return m ? { kg: +m[1], pct: +m[2], status: m[3] } : {}; };
  [['leftArm', 'Left Arm'], ['rightArm', 'Right Arm'], ['trunk', 'Trunk'], ['leftLeg', 'Left Leg'], ['rightLeg', 'Right Leg']].forEach(([k, part]) => {
    const l = seg(leanSec, part); if (l.kg != null) { out[k + 'LeanKg'] = l.kg; out[k + 'LeanPercent'] = l.pct; out[k + 'LeanStatus'] = l.status; }
    const f = seg(fatSec, part); if (f.kg != null) { out[k + 'FatKg'] = f.kg; out[k + 'FatPercent'] = f.pct; out[k + 'FatStatus'] = f.status; }
  });
  // history
  out.previousWeight = num('Previous Weight'); out.currentWeight = num('Current Weight'); out.weightChange = num('Weight Change');
  out.previousSkeletalMuscleMass = num('Previous Skeletal Muscle Mass'); out.currentSkeletalMuscleMass = num('Current Skeletal Muscle Mass'); out.skeletalMuscleMassChange = num('Skeletal Muscle Mass Change');
  out.previousPBF = num('Previous PBF'); out.currentPBF = num('Current PBF'); out.pbfChange = num('PBF Change');
  out.previousBodyFatMass = num('Previous Body Fat Mass'); out.currentBodyFatMass = num('Current Body Fat Mass'); out.bodyFatMassChange = num('Body Fat Mass Change');
  out.previousVisceralFatLevel = num('Previous Visceral Fat Level'); out.currentVisceralFatLevel = num('Current Visceral Fat Level');
  // targets
  const pt = range('Protein Target'); out.proteinTargetMin = pt[0]; out.proteinTargetMax = pt[1];
  const ct = range('Calories Target'); out.caloriesTargetMin = ct[0]; out.caloriesTargetMax = ct[1];
  const lr = range('Ideal Weight Loss Rate'); out.idealWeightLossRateMin = lr[0]; out.idealWeightLossRateMax = lr[1];
  // text
  out.goalText = _ibSection(t, 'Goal');
  out.priorityText = str('Priority') || '';
  out.notes = _ibSection(t, 'Note');

  Object.keys(out).forEach(k => { if (out[k] === undefined) delete out[k]; });
  return out;
}

/* ── comparison + analysis ────────────────────────────────────────────── */
// metric defs: lower-is-better (lb) or higher-is-better (hb) or neutral
const _IB_METRICS = [
  { key: 'weight', label: 'Weight', unit: 'kg', dir: 'lb' },
  { key: 'skeletalMuscleMass', label: 'Skeletal muscle', unit: 'kg', dir: 'hb' },
  { key: 'bodyFatMass', label: 'Body fat mass', unit: 'kg', dir: 'lb' },
  { key: 'pbf', label: 'Body fat %', unit: '%', dir: 'lb' },
  { key: 'visceralFatLevel', label: 'Visceral fat', unit: '', dir: 'lb' },
  { key: 'basalMetabolicRate', label: 'BMR', unit: 'kcal', dir: 'hb' },
  { key: 'waistHipRatio', label: 'Waist-hip ratio', unit: '', dir: 'lb' },
  { key: 'inBodyScore', label: 'InBody score', unit: '', dir: 'hb' },
];
function inBodyCompare(prev, cur) {
  return _IB_METRICS.map(m => {
    const a = prev ? prev[m.key] : null, b = cur ? cur[m.key] : null;
    if (a == null || b == null) return { ...m, prev: a, cur: b, change: null, direction: 'neutral' };
    const change = +(b - a).toFixed(2);
    let direction = 'neutral';
    if (change !== 0 && m.dir !== 'neutral') direction = ((change < 0 && m.dir === 'lb') || (change > 0 && m.dir === 'hb')) ? 'improved' : 'worse';
    return { ...m, prev: a, cur: b, change, direction };
  }).filter(r => r.prev != null || r.cur != null);
}

/* ── self-contained trend chart (global chart rules: ticks, grid, viewBox) ── */
function InBodyTrendChart({ points, color, unit }) {
  const pts = (points || []).filter(p => p.y != null && !isNaN(+p.y)).map(p => ({ x: p.x, y: +p.y }));
  if (pts.length < 1) return <div style={{ textAlign: 'center', color: 'var(--muted)', fontSize: 12.5, padding: '14px 0' }}>No data yet.</div>;
  const W = 320, H = 140, PADL = 42, PADR = 10, PADT = 14, PADB = 26;
  const ys = pts.map(p => p.y);
  let min = Math.min(...ys), max = Math.max(...ys);
  if (min === max) { min -= 1; max += 1; }
  const padY = (max - min) * 0.12 || 1;
  const y0 = min - padY, y1 = max + padY;
  const TICKS = 4;
  const xAt = i => PADL + (pts.length === 1 ? 0.5 : i / (pts.length - 1)) * (W - PADL - PADR);
  const yAt = v => PADT + (1 - (v - y0) / (y1 - y0)) * (H - PADT - PADB);
  const lineD = pts.map((p, i) => `${i ? 'L' : 'M'}${xAt(i).toFixed(1)},${yAt(p.y).toFixed(1)}`).join(' ');
  const fmt = v => (Math.abs(v) >= 100 ? Math.round(v) : (+v).toFixed(1));
  const c = color || 'var(--accent)';
  const labelEvery = Math.ceil(pts.length / 5);
  return (
    <svg width="100%" viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', overflow: 'visible' }}>
      {Array.from({ length: TICKS + 1 }).map((_, i) => {
        const v = y0 + (i / TICKS) * (y1 - y0); const y = yAt(v);
        return (
          <g key={i}>
            <line x1={PADL} y1={y} x2={W - PADR} y2={y} stroke="var(--border)" strokeWidth="1"/>
            <text x={PADL - 5} y={y + 3} textAnchor="end" fontSize="9" fill="var(--muted)">{fmt(v)}</text>
          </g>
        );
      })}
      {pts.length > 1 && <path d={lineD} fill="none" stroke={c} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round"/>}
      {pts.map((p, i) => (
        <g key={i}>
          <circle cx={xAt(i)} cy={yAt(p.y)} r="2.6" fill={c}/>
          {(i % labelEvery === 0 || i === pts.length - 1) && <text x={xAt(i)} y={H - PADB + 13} textAnchor="middle" fontSize="8.5" fill="var(--muted)">{p.x}</text>}
        </g>
      ))}
    </svg>
  );
}

/* ── small UI atoms ───────────────────────────────────────────────────── */
const _ibPill = (active) => ({ padding: '6px 12px', borderRadius: 999, whiteSpace: 'nowrap', flexShrink: 0, background: active ? 'var(--accent)' : 'var(--surface)', color: active ? 'var(--on-accent)' : 'var(--text)', border: '1px solid ' + (active ? 'var(--accent)' : 'var(--border-2)'), fontSize: 12, fontWeight: 600, cursor: 'pointer', font: 'inherit' });
const _ibBtn = { padding: '8px 14px', borderRadius: 10, background: 'var(--accent)', color: 'var(--on-accent)', border: '1px solid var(--accent)', fontSize: 13, fontWeight: 600, cursor: 'pointer', font: 'inherit' };
const _ibGhost = { padding: '7px 12px', borderRadius: 10, background: 'var(--surface-2)', color: 'var(--text)', border: '1px solid var(--border-2)', fontSize: 12, fontWeight: 600, cursor: 'pointer', font: 'inherit' };
const _ibChip = { padding: '5px 10px', borderRadius: 999, background: 'var(--surface-2)', color: 'var(--text)', border: '1px solid var(--border-2)', fontSize: 11, fontWeight: 600, cursor: 'pointer', font: 'inherit' };
const _ibLabel = { fontSize: 11, color: 'var(--muted)', fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase', marginBottom: 6 };
function _ibFmt(v, unit) { if (v == null || v === '') return '—'; return (typeof v === 'number' ? v : v) + (unit ? ' ' + unit : ''); }
function _ibDateLabel(iso) { if (!iso) return ''; try { return new Date(iso + 'T00:00:00').toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); } catch (_) { return iso; } }

/* ════════════════════════════════════════════════════════════════════════
   MAIN — InBody tab
   ════════════════════════════════════════════════════════════════════════ */
function InBodyScreen({ flashToast }) {
  const [sub, setSub] = React.useState('weight');
  const SUBS = [['weight', '⚖️ Weight'], ['entries', '📋 InBody'], ['add', '＋ Add'], ['compare', '⇄ Compare'], ['analysis', '🧠 Analysis'], ['charts', '📈 Charts']];
  return (
    <div style={{ padding: 14 }}>
      <div style={{ display: 'flex', gap: 6, marginBottom: 12, overflowX: 'auto' }} className="nutri-scroll-x">
        {SUBS.map(([id, label]) => <button key={id} onClick={() => setSub(id)} style={_ibPill(sub === id)}>{label}</button>)}
      </div>
      {sub === 'weight' && <WeightLogSection flashToast={flashToast}/>}
      {sub === 'entries' && <InBodyEntriesList flashToast={flashToast} onAdd={() => setSub('add')}/>}
      {sub === 'add' && <InBodyAddPaste flashToast={flashToast} onDone={() => setSub('entries')}/>}
      {sub === 'compare' && <InBodyCompareView/>}
      {sub === 'analysis' && <InBodyAnalysisView/>}
      {sub === 'charts' && <InBodyChartsView/>}
    </div>
  );
}

/* ── Weight log ───────────────────────────────────────────────────────── */
function WeightLogSection({ flashToast }) {
  const store = useStore();
  const weights = store.settings && (store.settings().weights || {}) || {};
  const wnotes = store.weightNotes ? store.weightNotes() : {};
  const list = Object.entries(weights).filter(([d, v]) => v != null).sort((a, b) => b[0].localeCompare(a[0]));
  const [date, setDate] = React.useState(NUTRI_TODAY);
  const [kg, setKg] = React.useState('');
  const [note, setNote] = React.useState('');
  const [editing, setEditing] = React.useState(null);

  function save() {
    const v = parseFloat(kg);
    if (!v || isNaN(v)) { flashToast && flashToast('Enter a weight', 'err'); return; }
    store.setWeight(date, v);
    if (store.setWeightNote) store.setWeightNote(date, note);
    flashToast && flashToast(editing ? 'Weight updated' : 'Weight saved');
    setKg(''); setNote(''); setEditing(null);
  }
  function startEdit(d) { setEditing(d); setDate(d); setKg(String(weights[d])); setNote(wnotes[d] || ''); }
  function del(d) { if (!confirm('Delete weight for ' + _ibDateLabel(d) + '?')) return; store.deleteWeight ? store.deleteWeight(d) : store.setWeight(d, ''); flashToast && flashToast('Deleted'); if (editing === d) { setEditing(null); setKg(''); setNote(''); } }

  const chartPts = Object.entries(weights).filter(([d, v]) => v != null).sort((a, b) => a[0].localeCompare(b[0])).map(([d, v]) => ({ x: (d || '').slice(5), y: +v }));

  return (
    <div>
      {/* add / edit */}
      <div style={{ ...nutriStyles.card, padding: 12, marginBottom: 12 }}>
        <div style={{ fontWeight: 700, fontSize: 14, marginBottom: 8 }}>{editing ? 'Edit weight' : 'Log weight'}</div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
          <label style={{ flex: 1 }}><div style={_ibLabel}>Date</div><input type="date" value={date} onChange={e => setDate(e.target.value)} style={inputStyle}/></label>
          <label style={{ width: 110 }}><div style={_ibLabel}>Weight (kg)</div><input type="number" inputMode="decimal" value={kg} onChange={e => setKg(e.target.value)} placeholder="0.0" style={inputStyle}/></label>
        </div>
        <label style={{ display: 'block', marginBottom: 8 }}><div style={_ibLabel}>Note (optional)</div><input value={note} onChange={e => setNote(e.target.value)} placeholder="e.g. morning, post-workout" style={inputStyle}/></label>
        <div style={{ display: 'flex', gap: 8 }}>
          <button onClick={save} style={_ibBtn}>{editing ? 'Update' : 'Save weight'}</button>
          {editing && <button onClick={() => { setEditing(null); setKg(''); setNote(''); setDate(NUTRI_TODAY); }} style={_ibGhost}>Cancel</button>}
        </div>
      </div>

      {/* trend */}
      {chartPts.length > 0 && (
        <div style={{ ...nutriStyles.card, padding: 12, marginBottom: 12 }}>
          <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 6 }}>Weight trend</div>
          <InBodyTrendChart points={chartPts} unit="kg" color="var(--c-fat)"/>
        </div>
      )}

      {/* list */}
      {list.length === 0
        ? <EmptyState icon="⚖️" title="No weight logged yet" subtitle="Add your weight above — it powers the trend and InBody comparison."/>
        : list.map(([d, v]) => (
          <div key={d} style={{ ...nutriStyles.card, padding: 10, marginBottom: 8, display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontWeight: 700, fontSize: 14 }}>{(+v).toFixed(1)} kg <span style={{ color: 'var(--muted)', fontWeight: 400, fontSize: 12 }}>· {_ibDateLabel(d)}</span></div>
              {wnotes[d] && <div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 2 }}>{wnotes[d]}</div>}
            </div>
            <button onClick={() => startEdit(d)} style={_ibChip}>Edit</button>
            <button onClick={() => del(d)} style={{ ..._ibChip, color: '#C2554E', borderColor: 'rgba(194,85,78,0.35)' }}>Delete</button>
          </div>
        ))}
    </div>
  );
}

/* ── InBody entries list + detail ─────────────────────────────────────── */
function InBodyEntriesList({ flashToast, onAdd }) {
  const store = useStore();
  const entries = store.inBodyEntries().slice().sort((a, b) => (b.date || '').localeCompare(a.date || ''));
  const [openId, setOpenId] = React.useState(null);
  if (openId) {
    const e = entries.find(x => x.id === openId);
    if (!e) { setOpenId(null); return null; }
    return <InBodyDetail entry={e} onBack={() => setOpenId(null)} onDelete={() => { if (confirm('Delete this InBody entry?')) { store.deleteInBodyEntry(e.id); flashToast && flashToast('Deleted'); setOpenId(null); } }}/>;
  }
  if (entries.length === 0) {
    return <div><EmptyState icon="🧬" title="No InBody scans yet" subtitle="Paste your InBody result text to add your first scan."/>
      <button onClick={onAdd} style={{ ..._ibBtn, width: '100%', marginTop: 12 }}>＋ Add InBody (paste &amp; parse)</button></div>;
  }
  return (
    <div>
      <button onClick={onAdd} style={{ ..._ibGhost, marginBottom: 10 }}>＋ Add InBody</button>
      {entries.map(e => (
        <div key={e.id} onClick={() => setOpenId(e.id)} style={{ ...nutriStyles.card, padding: 12, marginBottom: 8, cursor: 'pointer' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
            <div style={{ fontWeight: 700, fontSize: 14 }}>{_ibDateLabel(e.date)}</div>
            {e.inBodyScore != null && <div style={{ fontSize: 12, color: 'var(--accent)', fontWeight: 700 }}>Score {e.inBodyScore}</div>}
          </div>
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginTop: 5, fontSize: 12, color: 'var(--muted)' }}>
            {e.weight != null && <span><b style={{ color: 'var(--text)' }}>{e.weight}</b> kg</span>}
            {e.pbf != null && <span>PBF <b style={{ color: 'var(--text)' }}>{e.pbf}%</b></span>}
            {e.skeletalMuscleMass != null && <span>Muscle <b style={{ color: 'var(--text)' }}>{e.skeletalMuscleMass}</b></span>}
            {e.bodyFatMass != null && <span>Fat <b style={{ color: 'var(--text)' }}>{e.bodyFatMass}</b></span>}
            {e.visceralFatLevel != null && <span>Visc <b style={{ color: 'var(--text)' }}>{e.visceralFatLevel}</b></span>}
          </div>
        </div>
      ))}
    </div>
  );
}

function _ibRow(label, value, unit) { return value == null || value === '' ? null : <div style={{ display: 'flex', justifyContent: 'space-between', padding: '5px 0', borderTop: '1px solid var(--border)', fontSize: 12.5 }}><span style={{ color: 'var(--muted)' }}>{label}</span><span style={{ fontWeight: 600 }}>{value}{unit ? ' ' + unit : ''}</span></div>; }
function InBodyDetail({ entry: e, onBack, onDelete }) {
  const seg = (k) => e[k + 'Kg'] != null ? `${e[k + 'Kg']} kg · ${e[k + 'Percent']}% · ${e[k + 'Status'] || ''}` : null;
  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
        <button onClick={onBack} style={_ibGhost}>‹ Back</button>
        <div style={{ fontWeight: 700 }}>{_ibDateLabel(e.date)}</div>
        <button onClick={onDelete} style={{ ..._ibChip, color: '#C2554E', borderColor: 'rgba(194,85,78,0.35)' }}>Delete</button>
      </div>
      <div style={{ ...nutriStyles.card, padding: 12, marginBottom: 10 }}>
        <div style={_ibLabel}>Key metrics</div>
        {_ibRow('Weight', e.weight, 'kg')}
        {_ibRow('InBody score', e.inBodyScore)}
        {_ibRow('Skeletal muscle mass', e.skeletalMuscleMass, 'kg')}
        {_ibRow('Body fat mass', e.bodyFatMass, 'kg')}
        {_ibRow('Body fat %', e.pbf, '%')}
        {_ibRow('Visceral fat level', e.visceralFatLevel)}
        {_ibRow('BMI', e.bmi)}
        {_ibRow('BMR', e.basalMetabolicRate, 'kcal')}
        {_ibRow('Fat free mass', e.fatFreeMass, 'kg')}
        {_ibRow('Total body water', e.totalBodyWater, 'L')}
        {_ibRow('Waist-hip ratio', e.waistHipRatio)}
        {_ibRow('Target weight', e.targetWeight, 'kg')}
      </div>
      {(seg('leftArm') || seg('trunk')) && (
        <div style={{ ...nutriStyles.card, padding: 12, marginBottom: 10 }}>
          <div style={_ibLabel}>Segmental lean</div>
          {_ibRow('Left arm', seg('leftArmLean'))}{_ibRow('Right arm', seg('rightArmLean'))}{_ibRow('Trunk', seg('trunkLean'))}{_ibRow('Left leg', seg('leftLegLean'))}{_ibRow('Right leg', seg('rightLegLean'))}
          <div style={{ ..._ibLabel, marginTop: 10 }}>Segmental fat</div>
          {_ibRow('Left arm', seg('leftArmFat'))}{_ibRow('Right arm', seg('rightArmFat'))}{_ibRow('Trunk', seg('trunkFat'))}{_ibRow('Left leg', seg('leftLegFat'))}{_ibRow('Right leg', seg('rightLegFat'))}
        </div>
      )}
      {(e.goalText || e.notes) && (
        <div style={{ ...nutriStyles.card, padding: 12 }}>
          {e.goalText && <><div style={_ibLabel}>Goal</div><div style={{ fontSize: 12.5, color: 'var(--text-2)', whiteSpace: 'pre-wrap', marginBottom: 8 }}>{e.goalText}</div></>}
          {e.notes && <><div style={_ibLabel}>Notes</div><div style={{ fontSize: 12.5, color: 'var(--text-2)', whiteSpace: 'pre-wrap' }}>{e.notes}</div></>}
        </div>
      )}
    </div>
  );
}

/* ── Add via paste & parse (preview → confirm, dup protection) ─────────── */
const _IB_EDIT_FIELDS = [['date', 'Date', 'date'], ['weight', 'Weight (kg)', 'num'], ['skeletalMuscleMass', 'Skeletal muscle (kg)', 'num'], ['bodyFatMass', 'Body fat mass (kg)', 'num'], ['pbf', 'Body fat %', 'num'], ['visceralFatLevel', 'Visceral fat', 'num'], ['bmi', 'BMI', 'num'], ['basalMetabolicRate', 'BMR (kcal)', 'num'], ['inBodyScore', 'InBody score', 'num']];
function InBodyAddPaste({ flashToast, onDone }) {
  const store = useStore();
  const [text, setText] = React.useState('');
  const [draft, setDraft] = React.useState(null);
  const [dup, setDup] = React.useState(false);
  function doParse() {
    const parsed = parseInBody(text);
    if (!parsed.date && Object.keys(parsed).length < 3) { flashToast && flashToast('Could not parse — check the format', 'err'); return; }
    if (!parsed.date) parsed.date = NUTRI_TODAY;
    setDraft(parsed);
  }
  function edit(key, val, type) { setDraft(d => ({ ...d, [key]: type === 'num' ? (val === '' ? undefined : +val) : val })); }
  function trySave() { if (store.findInBodyByDate(draft.date)) setDup(true); else commit('new'); }
  function commit(mode) {
    if (mode === 'new') store.addInBodyEntry(draft);
    else store.saveInBodyForDate(draft.date, draft, mode);
    setDup(false); setDraft(null); setText('');
    flashToast && flashToast('InBody saved ✓');
    onDone && onDone();
  }

  if (draft) {
    return (
      <div>
        <div style={{ ...nutriStyles.card, padding: 12, marginBottom: 10 }}>
          <div style={{ fontWeight: 700, fontSize: 14, marginBottom: 4 }}>Review &amp; confirm</div>
          <div style={{ fontSize: 12, color: 'var(--muted)', marginBottom: 10 }}>Edit any value, then save. Full details (segmental, history, targets, notes) are kept from the paste.</div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
            {_IB_EDIT_FIELDS.map(([key, label, type]) => (
              <label key={key} style={{ display: 'block' }}>
                <div style={_ibLabel}>{label}</div>
                <input type={type === 'date' ? 'date' : 'number'} inputMode={type === 'num' ? 'decimal' : undefined}
                  value={draft[key] == null ? '' : draft[key]} onChange={e => edit(key, e.target.value, type)} style={inputStyle}/>
              </label>
            ))}
          </div>
          <div style={{ fontSize: 11, color: 'var(--muted-2)', marginTop: 8 }}>
            Parsed {Object.keys(draft).filter(k => draft[k] != null && draft[k] !== '').length} fields{draft.weight != null ? ' · weight will also update your weight log' : ''}.
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button onClick={trySave} style={{ ..._ibBtn, flex: 1 }}>Save InBody</button>
          <button onClick={() => setDraft(null)} style={_ibGhost}>Back to paste</button>
        </div>

        {dup && (
          <div style={{ position: 'fixed', inset: 0, zIndex: 80, background: 'rgba(0,0,0,0.45)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }} onClick={() => setDup(false)}>
            <div onClick={e => e.stopPropagation()} style={{ ...nutriStyles.card, padding: 18, maxWidth: 320, width: '100%' }}>
              <div style={{ fontWeight: 700, fontSize: 15, marginBottom: 6 }}>Entry exists for {_ibDateLabel(draft.date)}</div>
              <div style={{ fontSize: 12.5, color: 'var(--muted)', marginBottom: 14 }}>An InBody scan is already saved for this date.</div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                <button onClick={() => commit('replace')} style={_ibBtn}>Replace existing</button>
                <button onClick={() => commit('merge')} style={_ibGhost}>Merge missing fields</button>
                <button onClick={() => setDup(false)} style={{ ..._ibGhost, color: 'var(--muted)' }}>Cancel</button>
              </div>
            </div>
          </div>
        )}
      </div>
    );
  }

  return (
    <div>
      <div style={{ fontSize: 12.5, color: 'var(--muted)', marginBottom: 8, lineHeight: 1.5 }}>Paste your full InBody result text below, then Parse. You’ll review and confirm before it’s saved.</div>
      <textarea value={text} onChange={e => setText(e.target.value)} rows={12} placeholder={'Date: 26 May 2026\n\nBasic Data:\n- Weight: 71.5 kg\n- InBody Score: 68 / 100\n...'}
        style={{ ...inputStyle, resize: 'vertical', minHeight: 220, fontFamily: 'monospace', fontSize: 12 }}/>
      <button onClick={doParse} disabled={!text.trim()} style={{ ..._ibBtn, width: '100%', marginTop: 10, opacity: text.trim() ? 1 : 0.5 }}>Parse</button>
    </div>
  );
}

/* ── Compare two entries ──────────────────────────────────────────────── */
function InBodyCompareView() {
  const store = useStore();
  const entries = store.inBodyEntries().slice().sort((a, b) => (a.date || '').localeCompare(b.date || ''));
  const [aId, setAId] = React.useState(null);
  const [bId, setBId] = React.useState(null);
  if (entries.length < 2) return <EmptyState icon="⇄" title="Need two InBody scans" subtitle="Add at least two InBody entries to compare your progress."/>;
  const prev = entries.find(e => e.id === (aId || entries[entries.length - 2].id));
  const cur = entries.find(e => e.id === (bId || entries[entries.length - 1].id));
  const rows = inBodyCompare(prev, cur);
  const dirColor = d => d === 'improved' ? '#4FA862' : d === 'worse' ? '#C2554E' : 'var(--muted)';
  const dirIcon = d => d === 'improved' ? '↑ improved' : d === 'worse' ? '↓ worse' : '–';
  const opt = e => <option key={e.id} value={e.id}>{_ibDateLabel(e.date)}</option>;
  return (
    <div>
      <div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
        <label style={{ flex: 1 }}><div style={_ibLabel}>From</div><select value={prev.id} onChange={e => setAId(e.target.value)} style={inputStyle}>{entries.map(opt)}</select></label>
        <label style={{ flex: 1 }}><div style={_ibLabel}>To</div><select value={cur.id} onChange={e => setBId(e.target.value)} style={inputStyle}>{entries.map(opt)}</select></label>
      </div>
      {rows.map(r => (
        <div key={r.key} style={{ ...nutriStyles.card, padding: 10, marginBottom: 8 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
            <span style={{ fontWeight: 600, fontSize: 13 }}>{r.label}</span>
            <span style={{ fontSize: 12, fontWeight: 700, color: dirColor(r.direction) }}>{r.change == null ? '—' : (r.change > 0 ? '+' : '') + r.change + (r.unit ? ' ' + r.unit : '')} · {dirIcon(r.direction)}</span>
          </div>
          <div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 3 }}>{_ibFmt(r.prev, r.unit)} → <b style={{ color: 'var(--text)' }}>{_ibFmt(r.cur, r.unit)}</b></div>
        </div>
      ))}
    </div>
  );
}

/* ── Analysis ─────────────────────────────────────────────────────────── */
function inBodyAnalysisText(entry, recent) {
  if (!entry) return [];
  const out = [];
  const fatChange = entry.bodyFatMassChange != null ? entry.bodyFatMassChange : (entry.previousBodyFatMass != null && entry.currentBodyFatMass != null ? +(entry.currentBodyFatMass - entry.previousBodyFatMass).toFixed(1) : null);
  const muscleChange = entry.skeletalMuscleMassChange != null ? entry.skeletalMuscleMassChange : (entry.previousSkeletalMuscleMass != null && entry.currentSkeletalMuscleMass != null ? +(entry.currentSkeletalMuscleMass - entry.previousSkeletalMuscleMass).toFixed(1) : null);
  if (fatChange != null) out.push({ t: fatChange < 0 ? 'Fat loss 🔥' : 'Fat trend', good: fatChange <= 0, body: fatChange < 0 ? `Body fat down ${Math.abs(fatChange)} kg — strong fat loss.` : `Body fat up ${fatChange} kg — tighten the deficit.` });
  if (muscleChange != null) out.push({ t: 'Muscle', good: muscleChange >= -0.5, body: muscleChange < -0.5 ? `Muscle dropped ${Math.abs(muscleChange)} kg — prioritise protein + resistance training to rebuild it.` : (muscleChange >= 0 ? `Muscle preserved/gained (${muscleChange >= 0 ? '+' : ''}${muscleChange} kg). Nice.` : `Slight muscle change (${muscleChange} kg) — keep protein high.`) });
  if (entry.previousVisceralFatLevel != null && entry.currentVisceralFatLevel != null) {
    const d = entry.currentVisceralFatLevel - entry.previousVisceralFatLevel;
    out.push({ t: 'Visceral fat', good: d <= 0, body: d < 0 ? `Improved ${entry.previousVisceralFatLevel} → ${entry.currentVisceralFatLevel} — a real health win.` : `${entry.previousVisceralFatLevel} → ${entry.currentVisceralFatLevel}.` });
  }
  // diet support
  const pMin = entry.proteinTargetMin, pMax = entry.proteinTargetMax, cMin = entry.caloriesTargetMin, cMax = entry.caloriesTargetMax;
  if (recent && (pMin || cMin)) {
    const bits = [];
    if (pMin) bits.push(recent.protein != null ? (recent.protein < pMin ? `Protein ${Math.round(recent.protein)}g is below your ${pMin}–${pMax}g target — add a protein source tomorrow to protect muscle.` : `Protein ${Math.round(recent.protein)}g is on target (${pMin}–${pMax}g). 💪`) : `Aim for ${pMin}–${pMax}g protein/day.`);
    if (cMin) bits.push(recent.calories != null ? (recent.calories < cMin ? `Calories ${Math.round(recent.calories)} are low vs ${cMin}–${cMax} — too aggressive a deficit can cost muscle.` : recent.calories > cMax ? `Calories ${Math.round(recent.calories)} are above ${cMin}–${cMax} — tighten slightly for fat loss.` : `Calories ${Math.round(recent.calories)} sit in your ${cMin}–${cMax} range.`) : `Target ${cMin}–${cMax} kcal/day.`);
    out.push({ t: 'Diet today', good: recent.protein != null && pMin && recent.protein >= pMin, body: bits.join(' ') });
  } else if (pMin) {
    out.push({ t: 'Targets', good: true, body: `Protein ${pMin}–${pMax}g/day · ${cMin || ''}${cMax ? '–' + cMax : ''} kcal/day · lose ${entry.idealWeightLossRateMin || 0.4}–${entry.idealWeightLossRateMax || 0.7} kg/week.` });
  }
  out.push({ t: 'Next focus', good: true, body: (muscleChange != null && muscleChange < -0.5) ? 'Preserve/rebuild muscle: hit protein, resistance train 3–4×/week, keep the deficit moderate.' : 'Keep a moderate deficit, high protein, and resistance training to lose fat while protecting muscle.' });
  return out;
}
function InBodyAnalysisView() {
  const store = useStore();
  const entries = store.inBodyEntries().slice().sort((a, b) => (b.date || '').localeCompare(a.date || ''));
  if (!entries.length) return <EmptyState icon="🧠" title="No InBody data" subtitle="Add an InBody scan to see analysis."/>;
  const latest = entries[0];
  // recent nutrition for the InBody date (or today)
  const meals = store.meals ? store.meals() : [];
  const dayMeals = meals.filter(m => m.date === latest.date);
  const base = dayMeals.length ? dayMeals : meals.filter(m => m.date === todayISO());
  const recent = base.length ? base.reduce((s, m) => ({ calories: s.calories + (+m.calories || 0), protein: s.protein + (+m.protein_g || 0) }), { calories: 0, protein: 0 }) : null;
  const items = inBodyAnalysisText(latest, recent);
  return (
    <div>
      <div style={{ fontSize: 12, color: 'var(--muted)', marginBottom: 10 }}>Based on your latest scan ({_ibDateLabel(latest.date)})</div>
      {items.map((it, i) => (
        <div key={i} style={{ ...nutriStyles.card, padding: 12, marginBottom: 8, borderLeft: '3px solid ' + (it.good ? '#4FA862' : '#E89B3C') }}>
          <div style={{ fontWeight: 700, fontSize: 13, marginBottom: 3 }}>{it.t}</div>
          <div style={{ fontSize: 12.5, color: 'var(--text-2)', lineHeight: 1.5 }}>{it.body}</div>
        </div>
      ))}
    </div>
  );
}

/* ── Charts ───────────────────────────────────────────────────────────── */
function InBodyChartsView() {
  const store = useStore();
  const entries = store.inBodyEntries().slice().sort((a, b) => (a.date || '').localeCompare(b.date || ''));
  if (entries.length < 1) return <EmptyState icon="📈" title="No InBody data" subtitle="Add InBody scans to see trends over time."/>;
  const charts = [
    { key: 'weight', label: 'Weight', unit: 'kg', color: 'var(--c-fat)' },
    { key: 'bodyFatMass', label: 'Body fat mass', unit: 'kg', color: '#C2554E' },
    { key: 'skeletalMuscleMass', label: 'Skeletal muscle', unit: 'kg', color: '#4FA862' },
    { key: 'pbf', label: 'Body fat %', unit: '%', color: '#E89B3C' },
    { key: 'visceralFatLevel', label: 'Visceral fat level', unit: '', color: '#A06CC0' },
    { key: 'inBodyScore', label: 'InBody score', unit: '', color: 'var(--accent)' },
  ];
  return (
    <div>
      {charts.map(c => {
        const pts = entries.filter(e => e[c.key] != null).map(e => ({ x: (e.date || '').slice(5), y: +e[c.key] }));
        if (!pts.length) return null;
        return (
          <div key={c.key} style={{ ...nutriStyles.card, padding: 12, marginBottom: 12 }}>
            <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 6 }}>{c.label}</div>
            <InBodyTrendChart points={pts} unit={c.unit} color={c.color}/>
          </div>
        );
      })}
    </div>
  );
}

Object.assign(window, {
  parseInBody, inBodyCompare, inBodyAnalysisText,
  InBodyScreen, WeightLogSection, InBodyEntriesList, InBodyAddPaste, InBodyCompareView, InBodyAnalysisView, InBodyChartsView, InBodyTrendChart,
});
