/* global React, GlassCard, SectionLabel, ScreenHeader, Pill, Btn, PulseNum */
const { useState, useMemo, useEffect } = React;

// ============= WAM DATA + ENGINE =============
//
// All weights are percentage of subject (sum to 100 per subject).
// Marks are percentage scores. null = not yet sat.
// Set wam_target on the user level. Subject targets default to wam_target.
//
const DEFAULT_WAM_TARGET = 80;
const LS_OVERRIDES = 'optimiser_marks_v1';
const LS_TARGETS = 'optimiser_targets_v1';

// Year 1 (2025) — completed, locked in. Used as cumulative WAM contribution.
const completed = [
  { code: 'BUSS1000', name: 'Future of Business',                year: '2025 S1', mark: 77 },
  { code: 'BUSS1030', name: 'Accounting for Decision Making',    year: '2025 S1', mark: 84 },
  { code: 'BUSS1040', name: 'Economics for Business Decisions',  year: '2025 S1', mark: 65 },
  { code: 'ECON1002', name: 'Introductory Macroeconomics',       year: '2025 S1', mark: 75 },
  { code: 'ACCT1006', name: 'Accounting and Financial Mgmt',     year: '2025 S2', mark: 75 },
  { code: 'BUSS1020', name: 'Quantitative Business Analysis',    year: '2025 S2', mark: 83 },
  { code: 'CLAW1001', name: 'Foundations of Business Law',       year: '2025 S2', mark: 81 },
  { code: 'FASS2300', name: 'Asian Economic Community',          year: '2025 S2', mark: 78 },
];

// Current S1 2026 — in progress. Each has assessments with weight + mark (or null if pending).
const current = [
  {
    code: 'FINC2011',
    name: 'Corporate Finance I',
    target: DEFAULT_WAM_TARGET,
    assessments: [
      { name: 'Early Feedback Quiz',   weight: 5,  mark: 100,  due: '2026-03-14',  done: true },
      { name: 'Mid-sem Exam',          weight: 20, mark: 85,   due: '2026-04-26',  done: true },
      { name: 'Group Assignment',      weight: 25, mark: null, due: '2026-05-23',  done: false },
      { name: 'Final Exam',            weight: 50, mark: null, due: '2026-06-10',  done: false },
    ],
  },
  {
    code: 'CLAW1003',
    name: 'Company Law',
    target: DEFAULT_WAM_TARGET,
    assessments: [
      { name: 'In-class Quiz',         weight: 10, mark: 70,   due: '2026-03-20',  done: true },
      { name: 'In-sem Test',           weight: 30, mark: 83.3, due: '2026-04-10',  done: true },
      { name: 'Final Exam',            weight: 60, mark: null, due: '2026-06-17',  done: false },
    ],
  },
  {
    code: 'BUSS2000',
    name: 'Leading & Influencing in Business',
    target: DEFAULT_WAM_TARGET,
    assessments: [
      { name: 'Early Feedback Task',   weight: 5,  mark: 100,    due: '2026-03-21', done: true },
      { name: 'DTFY Assignment',       weight: 30, mark: 83.67,  due: '2026-04-18', done: true },
      { name: 'Participation',         weight: 10, mark: null,   due: '2026-06-03', done: false },
      { name: 'Group Presentation',    weight: 20, mark: null,   due: '2026-05-29', done: false },
      { name: 'Final Exam',            weight: 35, mark: null,   due: '2026-06-11', done: false },
    ],
  },
  {
    code: 'SCDL2991',
    name: 'Leadership in STEMM',
    target: DEFAULT_WAM_TARGET,
    assessments: [
      { name: 'Online Discussions',    weight: 5,  mark: null, due: '2026-06-03', done: false },
      { name: 'Critical Reflection Journal', weight: 5, mark: null, due: '2026-06-03', done: false },
      { name: 'Leadership Manual',     weight: 10, mark: 80,   due: '2026-04-04', done: true },
      { name: 'Leadership Practice',   weight: 20, mark: null, due: '2026-05-15', done: false },
      { name: 'Case Study',            weight: 30, mark: null, due: '2026-05-08', done: false, submitted: true },
      { name: 'Critical Reflection Summary', weight: 30, mark: null, due: '2026-05-30', done: false },
    ],
  },
];

// ---- Subject math ----

function subjectRunning(subject) {
  const done = subject.assessments.filter(a => a.done && a.mark != null);
  if (done.length === 0) return null;
  const num = done.reduce((s, a) => s + a.mark * a.weight, 0);
  const denom = done.reduce((s, a) => s + a.weight, 0);
  return num / denom;
}

function subjectAssessed(subject) {
  return subject.assessments
    .filter(a => a.done && a.mark != null)
    .reduce((s, a) => s + a.weight, 0);
}

function subjectProjected(subject, baseline = 77) {
  const assessed = subjectAssessed(subject);
  const running = subjectRunning(subject);
  if (assessed < 15 || running == null) return baseline;
  return running;
}

// Required mark on a SPECIFIC pending assessment to hit the subject target,
// assuming all OTHER pending assessments land at the subject target.
// "Awaiting result" assessments (done=true, mark=null) are treated as pending.
function requiredOnAssessment(subject, asmt) {
  const target = subject.target;
  const realDone = subject.assessments.filter(a => a.done && a.mark != null);
  const otherPending = subject.assessments.filter(a => a !== asmt && !(a.done && a.mark != null));

  const lockedContribution = realDone.reduce((s, a) => s + a.mark * a.weight, 0);
  const otherPendingContribution = otherPending.reduce((s, a) => s + target * a.weight, 0);
  const need = target * 100 - lockedContribution - otherPendingContribution;
  const required = need / asmt.weight;
  return required;
}

function cumulativeWAM(currentSubjects) {
  const completedMarks = completed.map(s => s.mark);
  const currentMarks = currentSubjects.map(s => subjectProjected(s));
  const all = [...completedMarks, ...currentMarks];
  return all.reduce((s, m) => s + m, 0) / all.length;
}

function lockedWAM() {
  const arr = completed.map(s => s.mark);
  return arr.reduce((s, m) => s + m, 0) / arr.length;
}

function requiredCurrentSemAverage(currentSubjects, target = DEFAULT_WAM_TARGET) {
  const completedSum = completed.reduce((s, m) => s + m.mark, 0);
  const totalUnits = completed.length + currentSubjects.length;
  const sumNeeded = target * totalUnits - completedSum;
  return sumNeeded / currentSubjects.length;
}

// ---- worker connection ----
const LS_WORKER_URL = 'optimiser_worker_url';
const LS_AUTH_TOKEN = 'optimiser_auth_token';
function getWorkerUrl() { return localStorage.getItem(LS_WORKER_URL) || ''; }
function getAuthToken() { return localStorage.getItem(LS_AUTH_TOKEN) || ''; }
function setWorkerUrl(u) { localStorage.setItem(LS_WORKER_URL, (u || '').replace(/\/$/, '')); }
function setAuthToken(t) { localStorage.setItem(LS_AUTH_TOKEN, (t || '').trim()); }
function isWorkerConfigured() { return !!(getWorkerUrl() && getAuthToken()); }

async function workerCall(path, opts = {}) {
  const url = getWorkerUrl();
  const token = getAuthToken();
  if (!url || !token) throw new Error('Worker not configured');
  const headers = {
    'Authorization': 'Bearer ' + token,
    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),
    ...(opts.headers || {}),
  };
  const r = await fetch(url + path, { ...opts, headers });
  if (!r.ok) throw new Error(r.status + ': ' + (await r.text()));
  const ct = r.headers.get('content-type') || '';
  return ct.includes('application/json') ? r.json() : r.text();
}

// Fire and forget — for non-blocking sync. Logs failures but never throws.
function workerSendChat(message) {
  if (!isWorkerConfigured()) return Promise.resolve();
  return workerCall('/chat', { method: 'POST', body: JSON.stringify({ message }) }).catch(e => console.warn('chat send failed', e));
}

// ---- localStorage state mgmt ----
function loadOverrides() {
  try { return JSON.parse(localStorage.getItem(LS_OVERRIDES) || '{}'); }
  catch { return {}; }
}
function saveOverrides(ov) { localStorage.setItem(LS_OVERRIDES, JSON.stringify(ov)); }
function loadTargets() {
  try { return JSON.parse(localStorage.getItem(LS_TARGETS) || '{}'); }
  catch { return {}; }
}
function saveTargets(t) { localStorage.setItem(LS_TARGETS, JSON.stringify(t)); }
function loadUserWamTarget() {
  const raw = localStorage.getItem('optimiser_wam_target_v1');
  return raw ? parseFloat(raw) : DEFAULT_WAM_TARGET;
}
function saveUserWamTarget(t) { localStorage.setItem('optimiser_wam_target_v1', String(t)); }

// Build the live `current` from the static template + saved overrides + saved targets.
function buildCurrentFromOverrides(overrides, targets, defaultTarget) {
  return current.map(s => ({
    ...s,
    target: targets[s.code] != null ? targets[s.code] : defaultTarget,
    assessments: s.assessments.map(a => {
      const ov = overrides && overrides[s.code] && overrides[s.code][a.name];
      if (!ov) return a;
      return { ...a, ...ov };
    }),
  }));
}

// ---- Format helpers ----
const fmt1 = n => (n == null || isNaN(n) ? '—' : n.toFixed(1));
const fmt0 = n => (n == null || isNaN(n) ? '—' : Math.round(n));
const colorFor = required => {
  if (required == null || isNaN(required)) return '#9a8b6e';
  if (required > 100) return '#c4453a';      // impossible
  if (required >= 90) return '#e0a766';      // hard
  if (required >= 75) return '#f4c987';      // stretch
  return '#7da967';                          // safe
};

function daysUntil(dueIso) {
  const due = new Date(dueIso + 'T23:59:59+10:00');
  const now = new Date();
  const ms = due - now;
  return Math.round(ms / 86400000);
}
function shortDate(dueIso) {
  const d = new Date(dueIso + 'T00:00:00');
  return d.toLocaleDateString('en-AU', { day: 'numeric', month: 'short' });
}

// ============= WAM SCREEN =============
window.WamScreen = function WamScreen() {
  const [overrides, setOverrides] = useState(() => loadOverrides());
  const [targets, setTargets] = useState(() => loadTargets());
  const [userWamTarget, setUserWamTarget] = useState(() => loadUserWamTarget());
  const [drilldown, setDrilldown] = useState(null);
  const [editor, setEditor] = useState(null); // {kind, subjectCode, asmtName?, currentValue}

  // Recompute live `current` from overrides + targets whenever they change.
  const liveCurrent = useMemo(
    () => buildCurrentFromOverrides(overrides, targets, userWamTarget),
    [overrides, targets, userWamTarget]
  );

  // Update mark on a specific assessment.
  const updateMark = (subjectCode, asmtName, mark) => {
    setOverrides(prev => {
      const next = { ...prev };
      next[subjectCode] = { ...(next[subjectCode] || {}) };
      if (mark === null || mark === '' || mark === undefined) {
        // Clear this override (revert to template)
        delete next[subjectCode][asmtName];
        if (Object.keys(next[subjectCode]).length === 0) delete next[subjectCode];
      } else {
        const numeric = parseFloat(mark);
        next[subjectCode][asmtName] = { mark: numeric, done: true, awaiting: false };
      }
      saveOverrides(next);
      return next;
    });
  };

  const updateSubjectTarget = (subjectCode, target) => {
    setTargets(prev => {
      const next = { ...prev, [subjectCode]: parseFloat(target) };
      saveTargets(next);
      return next;
    });
  };

  const updateUserWamTarget = (target) => {
    const t = parseFloat(target);
    setUserWamTarget(t);
    saveUserWamTarget(t);
  };

  // When a subject was selected for drilldown, find its updated version after edits.
  const drilldownSubject = drilldown
    ? liveCurrent.find(s => s.code === drilldown)
    : null;

  return (
    <>
      {drilldownSubject ? (
        <SubjectDetail
          subject={drilldownSubject}
          onBack={() => setDrilldown(null)}
          onEditMark={(asmtName, currentValue) => setEditor({ kind: 'mark', subjectCode: drilldownSubject.code, asmtName, currentValue })}
          onEditTarget={() => setEditor({ kind: 'subjectTarget', subjectCode: drilldownSubject.code, currentValue: drilldownSubject.target })}
        />
      ) : (
        <WamOverview
          currentSubjects={liveCurrent}
          userTarget={userWamTarget}
          onPickSubject={s => setDrilldown(s.code)}
          onEditWamTarget={() => setEditor({ kind: 'wamTarget', currentValue: userWamTarget })}
        />
      )}

      {editor && (
        <EditModal
          editor={editor}
          onCancel={() => setEditor(null)}
          onSave={(val) => {
            if (editor.kind === 'mark') {
              updateMark(editor.subjectCode, editor.asmtName, val);
            } else if (editor.kind === 'subjectTarget') {
              updateSubjectTarget(editor.subjectCode, val);
            } else if (editor.kind === 'wamTarget') {
              updateUserWamTarget(val);
            }
            setEditor(null);
          }}
          onClear={editor.kind === 'mark' ? () => { updateMark(editor.subjectCode, editor.asmtName, null); setEditor(null); } : null}
        />
      )}
    </>
  );
};

function EditModal({ editor, onCancel, onSave, onClear }) {
  const [val, setVal] = useState(editor.currentValue == null ? '' : String(editor.currentValue));
  const title = editor.kind === 'mark' ? 'Enter mark'
    : editor.kind === 'subjectTarget' ? 'Subject target'
    : 'WAM target';
  return (
    <div onClick={onCancel} style={{
      position: 'fixed', inset: 0, zIndex: 200,
      background: 'rgba(5, 3, 2, 0.7)',
      backdropFilter: 'blur(12px)', WebkitBackdropFilter: 'blur(12px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 360,
        background: '#181210',
        border: '1px solid rgba(232,217,189,0.12)',
        borderRadius: 20, padding: 22,
      }}>
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e', marginBottom: 6 }}>{title}</div>
        {editor.kind === 'mark' && (
          <div style={{ fontSize: 14, color: '#c8b896', marginBottom: 14 }}>{editor.asmtName}</div>
        )}
        <input
          type="number" inputMode="decimal" min="0" max="100" step="0.1"
          value={val} onChange={e => setVal(e.target.value)} autoFocus
          onKeyDown={e => { if (e.key === 'Enter' && val !== '') onSave(val); if (e.key === 'Escape') onCancel(); }}
          style={{
            width: '100%', background: 'rgba(0,0,0,0.3)',
            border: '1px solid rgba(232,217,189,0.10)', borderRadius: 10,
            color: '#e8d9bd', padding: '14px 16px',
            fontFamily: 'Inter, sans-serif', fontSize: 22, fontWeight: 600,
            outline: 'none', textAlign: 'center', fontVariantNumeric: 'tabular-nums',
          }}
        />
        <div style={{ display: 'flex', gap: 8, marginTop: 14 }}>
          <Btn kind="ghost" onClick={onCancel} style={{ flex: 1 }}>Cancel</Btn>
          {onClear && <Btn kind="danger" onClick={onClear} style={{ flex: 1 }}>Clear</Btn>}
          <Btn kind="primary" onClick={() => val !== '' && onSave(val)} style={{ flex: 1 }} disabled={val === ''}>Save</Btn>
        </div>
      </div>
    </div>
  );
}

function WamOverview({ currentSubjects, userTarget, onPickSubject, onEditWamTarget }) {
  const wamProjected = cumulativeWAM(currentSubjects);
  const wamLocked = lockedWAM();
  const reqAvg = requiredCurrentSemAverage(currentSubjects, userTarget);
  const gap = userTarget - wamProjected;

  return (
    <div>
      <ScreenHeader title="WAM" sub="Bachelor of Commerce + BAdvSt" />

      <SectionLabel right={
        <span onClick={onEditWamTarget} style={{ cursor: 'pointer', color: '#e0a766' }}>
          target {userTarget} ✎
        </span>
      }>WAM</SectionLabel>
      <GlassCard style={{ marginBottom: 14 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
          <div>
            <div style={{ fontSize: 11, fontWeight: 500, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e', marginBottom: 6 }}>Projected</div>
            <PulseNum value={fmt1(wamProjected)} size={42} />
          </div>
          <div style={{ textAlign: 'right' }}>
            <div style={{ fontSize: 11, fontWeight: 500, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e', marginBottom: 6 }}>Locked</div>
            <span style={{ fontFamily: 'Inter, sans-serif', fontSize: 22, fontWeight: 600, color: '#c8b896', fontVariantNumeric: 'tabular-nums' }}>{fmt1(wamLocked)}</span>
          </div>
        </div>
        <div style={{ marginTop: 14, paddingTop: 14, borderTop: '1px solid rgba(232,217,189,0.06)', fontSize: 13, color: '#c8b896', lineHeight: 1.5 }}>
          {gap > 0
            ? <>To reach <span style={{ color: '#f4c987', fontWeight: 600 }}>{userTarget}</span> by end of S1: need <span style={{ color: '#f4c987', fontWeight: 600 }}>{fmt1(reqAvg)}</span> average across this sem's four subjects.</>
            : <>On track. Current trajectory is {gap === 0 ? 'exactly' : 'above'} target.</>
          }
        </div>
      </GlassCard>

      <SectionLabel right={currentSubjects.length + ' in progress'}>This semester</SectionLabel>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 18 }}>
        {currentSubjects.map(s => {
          const running = subjectRunning(s);
          const assessed = subjectAssessed(s);
          return (
            <GlassCard key={s.code} style={{ padding: '14px 16px', cursor: 'pointer' }} onClick={() => onPickSubject(s)}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
                <div style={{ minWidth: 0, flex: 1 }}>
                  <div style={{ fontSize: 14, fontWeight: 600, color: '#e8d9bd' }}>{s.code}</div>
                  <div style={{ fontSize: 12, color: '#9a8b6e', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.name}</div>
                  <div style={{ fontSize: 11, color: '#6b5d44', marginTop: 4 }}>{assessed}% assessed</div>
                </div>
                <div style={{ textAlign: 'right', flexShrink: 0 }}>
                  <div style={{ fontFamily: 'Inter, sans-serif', fontWeight: 600, fontSize: 22, color: '#e8d9bd', letterSpacing: -0.5, fontVariantNumeric: 'tabular-nums' }}>
                    {running == null ? '—' : fmt1(running)}
                  </div>
                  <div style={{ fontSize: 10, color: '#9a8b6e', marginTop: 2, letterSpacing: 0.4, textTransform: 'uppercase' }}>running</div>
                </div>
              </div>
            </GlassCard>
          );
        })}
      </div>

      <SectionLabel right={completed.length + ' units'}>Year 1 · locked</SectionLabel>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {completed.map(s => (
          <div key={s.code} style={{ display: 'flex', justifyContent: 'space-between', padding: '10px 14px', borderBottom: '1px solid rgba(232,217,189,0.06)' }}>
            <div style={{ minWidth: 0, flex: 1 }}>
              <span style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 12, color: '#9a8b6e', marginRight: 10 }}>{s.code}</span>
              <span style={{ fontSize: 12, color: '#c8b896' }}>{s.name}</span>
            </div>
            <span style={{ fontFamily: 'Inter, sans-serif', fontWeight: 600, fontSize: 14, color: s.mark >= 80 ? '#7da967' : s.mark >= 70 ? '#e8d9bd' : '#c4453a', fontVariantNumeric: 'tabular-nums' }}>{s.mark}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function SubjectDetail({ subject, onBack, onEditMark, onEditTarget }) {
  const running = subjectRunning(subject);
  const assessed = subjectAssessed(subject);

  return (
    <div>
      <div style={{ padding: '12px 4px 6px' }}>
        <button onClick={onBack} style={{
          background: 'transparent', border: 'none', color: '#e0a766',
          fontSize: 14, fontFamily: 'inherit', padding: '6px 0', cursor: 'pointer',
          display: 'flex', alignItems: 'center', gap: 6,
        }}>
          ← All subjects
        </button>
      </div>

      <ScreenHeader title={subject.code} sub={subject.name} />

      <GlassCard feature style={{ marginBottom: 14 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
          <div>
            <div style={{ fontSize: 11, fontWeight: 500, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e', marginBottom: 6 }}>Running</div>
            <PulseNum value={running == null ? '—' : fmt1(running)} size={42} />
          </div>
          <div onClick={onEditTarget} style={{ textAlign: 'right', cursor: 'pointer' }}>
            <div style={{ fontSize: 11, fontWeight: 500, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e', marginBottom: 6 }}>Target ✎</div>
            <span style={{ fontFamily: 'Inter, sans-serif', fontSize: 22, fontWeight: 600, color: '#f4c987', fontVariantNumeric: 'tabular-nums' }}>{subject.target}</span>
          </div>
        </div>
        <div style={{ marginTop: 14, paddingTop: 14, borderTop: '1px solid rgba(232,217,189,0.06)', fontSize: 13, color: '#c8b896' }}>
          {assessed}% of subject assessed. {assessed < 100 ? (100 - assessed) + '% remaining.' : 'Subject complete.'}
        </div>
      </GlassCard>

      <SectionLabel right="tap to edit">Assessments</SectionLabel>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {subject.assessments.map((a, i) => {
          const required = !a.done ? requiredOnAssessment(subject, a) : null;
          return (
            <GlassCard key={i} style={{ padding: '14px 16px', cursor: 'pointer' }} onClick={() => onEditMark(a.name, a.mark)}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
                <div style={{ minWidth: 0, flex: 1 }}>
                  <div style={{ fontSize: 14, fontWeight: 600, color: '#e8d9bd' }}>{a.name}</div>
                  <div style={{ fontSize: 11, color: '#9a8b6e', marginTop: 4, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                    <span>{a.weight}%</span>
                    <span>·</span>
                    <span>{shortDate(a.due)}</span>
                    {a.awaiting && <><span>·</span><span style={{ color: '#e0a766' }}>awaiting</span></>}
                    {a.submitted && <><span>·</span><span style={{ color: '#e0a766' }}>submitted</span></>}
                  </div>
                </div>
                <div style={{ textAlign: 'right', flexShrink: 0 }}>
                  {a.done && a.mark != null ? (
                    <div style={{ fontFamily: 'Inter, sans-serif', fontWeight: 700, fontSize: 22, color: a.mark >= 80 ? '#7da967' : '#e8d9bd', fontVariantNumeric: 'tabular-nums' }}>{fmt1(a.mark)}</div>
                  ) : (
                    <>
                      <div style={{ fontSize: 10, color: '#9a8b6e', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: 2 }}>Need</div>
                      <div style={{ fontFamily: 'Inter, sans-serif', fontWeight: 700, fontSize: 22, color: colorFor(required), fontVariantNumeric: 'tabular-nums' }}>
                        {required > 100 ? '>100' : required < 0 ? 'any' : fmt0(required)}
                      </div>
                    </>
                  )}
                </div>
              </div>
            </GlassCard>
          );
        })}
      </div>

      <div style={{ marginTop: 18, padding: '14px 4px', fontSize: 12, color: '#6b5d44', lineHeight: 1.55 }}>
        Required marks assume all other pending assessments land at the subject target ({subject.target}). Tap any row to input a real mark. Numbers refresh live.
      </div>
    </div>
  );
}

// ============= GYM SCREEN =============
const LS_GYM_SESSIONS = 'optimiser_gym_sessions_v1';
const LS_GYM_CURRENT = 'optimiser_gym_current_v1';

const SPLITS = {
  'back-bi': {
    label: 'Back + Biceps',
    exercises: [
      { name: 'Weighted Pull-ups', target: '4×5-8' },
      { name: 'Bent Over Row', target: '3×8-10' },
      { name: 'Lat Pulldown', target: '3×8-12' },
      { name: 'Cable Row', target: '3×8-12' },
      { name: 'Incline DB Curl', target: '3×8-10' },
      { name: 'Hammer Curl', target: '3×10-12' },
      { name: 'Rear Delt Fly', target: '2×12-15' },
    ],
  },
  'chest-tri': {
    label: 'Chest + Triceps',
    exercises: [
      { name: 'Incline BB Bench', target: '4×5-8' },
      { name: 'Flat DB Press', target: '3×8-10' },
      { name: 'Weighted Dips', target: '3×6-8' },
      { name: 'Cable Fly', target: '3×10-12' },
      { name: 'Single-Arm Pushdowns', target: '3×10-12' },
      { name: 'Overhead Triceps Ext', target: '3×10-12' },
      { name: 'Lateral Raises', target: '3×12-15' },
    ],
  },
  'legs-shoulder': {
    label: 'Legs + Shoulder Press',
    exercises: [
      { name: 'Quad Extensions', target: '5×8-12' },
      { name: 'Hamstring Curls', target: '3×8-12' },
      { name: 'Hip Thrusts', target: '3×8-12' },
      { name: 'Calf Raises', target: '3×10-15' },
      { name: 'Seated Shoulder Press', target: '3×6-10' },
      { name: 'Lateral Raises', target: '3×12-15' },
    ],
  },
};

function loadSessions() {
  try { return JSON.parse(localStorage.getItem(LS_GYM_SESSIONS) || '[]'); }
  catch { return []; }
}
function saveSessions(s) { localStorage.setItem(LS_GYM_SESSIONS, JSON.stringify(s.slice(0, 100))); }
function loadCurrent() {
  try { return JSON.parse(localStorage.getItem(LS_GYM_CURRENT) || 'null'); }
  catch { return null; }
}
function saveCurrent(s) {
  if (s) localStorage.setItem(LS_GYM_CURRENT, JSON.stringify(s));
  else localStorage.removeItem(LS_GYM_CURRENT);
}

// Format a finished workout + recent history for Claude to critique.
function formatWorkoutForCritique(finished, history) {
  const recent = history.filter(h => h.splitKey === finished.splitKey).slice(0, 3);
  const fmtSession = (s) => {
    const date = new Date(s.ts).toLocaleDateString('en-AU', { day: 'numeric', month: 'short' });
    const lines = [`**${date}** — ${s.splitLabel}`];
    s.exercises.filter(e => e.sets.length).forEach(e => {
      lines.push(`  ${e.name}: ` + e.sets.map(st => `${st.reps}×${st.weight}kg${st.notes ? ' ('+st.notes+')' : ''}`).join(', '));
    });
    return lines.join('\n');
  };
  let msg = `Just finished a workout. Critique me for muscle growth — what to push next time, where to fix form, where to add weight. Reference my training history.\n\n## TODAY\n${fmtSession(finished)}\n`;
  if (recent.length > 0) {
    msg += `\n## RECENT ${finished.splitLabel.toUpperCase()} SESSIONS\n` + recent.map(fmtSession).join('\n\n');
  }
  msg += `\n\nGoal: ectomorph rebuilding to 85kg lean. No squats or RDLs (stress fracture). Side delts + arms priority. Be specific per exercise. No hyphens in your reply.`;
  return msg;
}

window.GymScreen = function GymScreen() {
  const [view, setView] = useState(() => loadCurrent() ? 'active' : 'idle');
  const [current, setCurrent] = useState(() => loadCurrent());
  const [sessions, setSessions] = useState(() => loadSessions());
  const [setEditor, setSetEditor] = useState(null); // { exerciseIdx, setIdx } | null
  const [done, setDone] = useState(null); // last finished session
  const [critique, setCritique] = useState({ status: 'idle', text: '' });

  const startWorkout = (splitKey) => {
    const split = SPLITS[splitKey];
    const w = {
      id: 's_' + Date.now(),
      ts: new Date().toISOString(),
      splitKey,
      splitLabel: split.label,
      exercises: split.exercises.map(e => ({ name: e.name, target: e.target, sets: [] })),
    };
    setCurrent(w);
    saveCurrent(w);
    setView('active');
  };

  const addSet = (exerciseIdx, set) => {
    setCurrent(prev => {
      const next = { ...prev, exercises: prev.exercises.map((e, i) => i === exerciseIdx ? { ...e, sets: [...e.sets, set] } : e) };
      saveCurrent(next);
      return next;
    });
  };

  const removeSet = (exerciseIdx, setIdx) => {
    setCurrent(prev => {
      const next = { ...prev, exercises: prev.exercises.map((e, i) => i === exerciseIdx ? { ...e, sets: e.sets.filter((_, j) => j !== setIdx) } : e) };
      saveCurrent(next);
      return next;
    });
  };

  const finishWorkout = async () => {
    if (!current) return;
    const finished = { ...current, finishedTs: new Date().toISOString() };
    const all = [finished, ...sessions];
    saveSessions(all);
    setSessions(all);
    setCurrent(null);
    saveCurrent(null);
    setDone(finished);
    setView('done');
    // Request AI critique if worker is configured
    if (isWorkerConfigured()) {
      setCritique({ status: 'loading', text: '' });
      const summary = formatWorkoutForCritique(finished, sessions);
      try {
        const r = await workerCall('/chat', { method: 'POST', body: JSON.stringify({ message: summary }) });
        setCritique({ status: 'ok', text: r.reply || '(no response)' });
      } catch (e) {
        setCritique({ status: 'fail', text: 'Critique request failed: ' + e.message });
      }
    } else {
      setCritique({ status: 'unconfigured', text: '' });
    }
  };

  const cancelWorkout = () => {
    if (!confirm('Discard this workout?')) return;
    setCurrent(null);
    saveCurrent(null);
    setView('idle');
  };

  // ---- views ----
  if (view === 'idle') {
    return (
      <div>
        <ScreenHeader title="Gym" sub={sessions.length + ' sessions logged'} />
        <SectionLabel>Start workout</SectionLabel>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 18 }}>
          {Object.entries(SPLITS).map(([k, s]) => (
            <GlassCard key={k} feature style={{ padding: '16px 18px', cursor: 'pointer' }} onClick={() => startWorkout(k)}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <div>
                  <div style={{ fontSize: 16, fontWeight: 600, color: '#e8d9bd' }}>{s.label}</div>
                  <div style={{ fontSize: 11, color: '#9a8b6e', marginTop: 4 }}>{s.exercises.length} exercises</div>
                </div>
                <div style={{ color: '#e0a766', fontSize: 22 }}>→</div>
              </div>
            </GlassCard>
          ))}
        </div>
        {sessions.length > 0 && (
          <>
            <SectionLabel>Recent</SectionLabel>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              {sessions.slice(0, 5).map(s => {
                const totalSets = s.exercises.reduce((sum, e) => sum + e.sets.length, 0);
                const totalVolume = s.exercises.reduce((sum, e) => sum + e.sets.reduce((v, st) => v + (st.weight || 0) * (st.reps || 0), 0), 0);
                return (
                  <GlassCard key={s.id} style={{ padding: '12px 14px' }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                      <div>
                        <div style={{ fontSize: 13, fontWeight: 600, color: '#e8d9bd' }}>{s.splitLabel}</div>
                        <div style={{ fontSize: 11, color: '#9a8b6e', marginTop: 2 }}>{new Date(s.ts).toLocaleDateString('en-AU', { weekday: 'short', day: 'numeric', month: 'short' })}</div>
                      </div>
                      <div style={{ textAlign: 'right' }}>
                        <div style={{ fontSize: 12, color: '#c8b896' }}>{totalSets} sets</div>
                        <div style={{ fontSize: 11, color: '#9a8b6e' }}>{Math.round(totalVolume)} kg·reps</div>
                      </div>
                    </div>
                  </GlassCard>
                );
              })}
            </div>
          </>
        )}
      </div>
    );
  }

  if (view === 'active' && current) {
    return (
      <>
        <div>
          <ScreenHeader title={current.splitLabel} sub="In progress · tap exercise to log set" />
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 14 }}>
            {current.exercises.map((ex, i) => {
              const totalSets = ex.sets.length;
              const lastSet = ex.sets[ex.sets.length - 1];
              return (
                <GlassCard key={i} style={{ padding: '14px 16px' }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, marginBottom: ex.sets.length > 0 ? 10 : 0 }}>
                    <div>
                      <div style={{ fontSize: 14, fontWeight: 600, color: '#e8d9bd' }}>{ex.name}</div>
                      <div style={{ fontSize: 11, color: '#9a8b6e', marginTop: 2 }}>{ex.target} · {totalSets} done</div>
                    </div>
                    <Btn kind="ghost" onClick={() => setSetEditor({ exerciseIdx: i, prefill: lastSet })} style={{ padding: '8px 14px', fontSize: 13 }}>+ set</Btn>
                  </div>
                  {ex.sets.length > 0 && (
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 4, paddingTop: 8, borderTop: '1px solid rgba(232,217,189,0.06)' }}>
                      {ex.sets.map((s, j) => (
                        <div key={j} onClick={() => removeSet(i, j)} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, color: '#c8b896', padding: '4px 0', cursor: 'pointer' }}>
                          <span><span style={{ color: '#9a8b6e' }}>{j + 1}.</span>  {s.reps} × {s.weight} kg</span>
                          {s.notes && <span style={{ color: '#9a8b6e', fontSize: 11, fontStyle: 'italic', maxWidth: '60%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.notes}</span>}
                        </div>
                      ))}
                    </div>
                  )}
                </GlassCard>
              );
            })}
          </div>
          <div style={{ display: 'flex', gap: 8, marginBottom: 14 }}>
            <Btn kind="ghost" onClick={cancelWorkout} style={{ flex: 1 }}>Discard</Btn>
            <Btn onClick={finishWorkout} style={{ flex: 2 }}>Finish workout</Btn>
          </div>
        </div>
        {setEditor && (
          <SetEditModal
            exercise={current.exercises[setEditor.exerciseIdx]}
            prefill={setEditor.prefill}
            onCancel={() => setSetEditor(null)}
            onSave={(set) => { addSet(setEditor.exerciseIdx, set); setSetEditor(null); }}
          />
        )}
      </>
    );
  }

  if (view === 'done' && done) {
    const totalSets = done.exercises.reduce((sum, e) => sum + e.sets.length, 0);
    const totalVolume = done.exercises.reduce((sum, e) => sum + e.sets.reduce((v, st) => v + (st.weight || 0) * (st.reps || 0), 0), 0);
    return (
      <div>
        <ScreenHeader title="Workout complete" sub={done.splitLabel} />
        <GlassCard feature style={{ marginBottom: 14 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
            <div>
              <div style={{ fontSize: 11, fontWeight: 500, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e', marginBottom: 6 }}>Total volume</div>
              <PulseNum value={Math.round(totalVolume)} size={36} unit="kg·reps" />
            </div>
            <div style={{ textAlign: 'right' }}>
              <div style={{ fontSize: 11, fontWeight: 500, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e', marginBottom: 6 }}>Sets</div>
              <span style={{ fontFamily: 'Inter', fontSize: 24, fontWeight: 600, color: '#e8d9bd' }}>{totalSets}</span>
            </div>
          </div>
        </GlassCard>

        <SectionLabel>Per exercise</SectionLabel>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 18 }}>
          {done.exercises.filter(e => e.sets.length > 0).map((e, i) => {
            const top = e.sets.reduce((m, s) => Math.max(m, s.weight || 0), 0);
            const reps = e.sets.reduce((sum, s) => sum + (s.reps || 0), 0);
            return (
              <GlassCard key={i} style={{ padding: '12px 14px' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                  <span style={{ fontSize: 13, color: '#e8d9bd' }}>{e.name}</span>
                  <span style={{ fontSize: 12, color: '#c8b896' }}>{e.sets.length} sets · top {top} kg · {reps} reps</span>
                </div>
              </GlassCard>
            );
          })}
        </div>

        <SectionLabel>Critique</SectionLabel>
        <GlassCard style={{ marginBottom: 14 }}>
          {critique.status === 'loading' && (
            <div style={{ fontSize: 13, color: '#c8b896' }}>Claude reviewing against your history…</div>
          )}
          {critique.status === 'ok' && (
            <div style={{ fontSize: 13, color: '#e8d9bd', lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>{critique.text}</div>
          )}
          {critique.status === 'fail' && (
            <div style={{ fontSize: 13, color: '#c4453a' }}>{critique.text}</div>
          )}
          {critique.status === 'unconfigured' && (
            <div style={{ fontSize: 13, color: '#c8b896', lineHeight: 1.55 }}>
              Configure the worker (tap the WO logo top right → Settings) to enable AI critique against your training history.
            </div>
          )}
        </GlassCard>

        <Btn full kind="ghost" onClick={() => setView('idle')}>Back to gym</Btn>
      </div>
    );
  }

  return null;
};

const DB_WEIGHTS = [10, 12.5, 15, 17.5, 20, 22.5, 27.5, 30, 32.5, 35, 37.5, 40, 42.5, 45, 47.5, 50];

function SetEditModal({ exercise, prefill, onCancel, onSave }) {
  const [weight, setWeight] = useState(prefill ? String(prefill.weight) : '');
  const [reps, setReps] = useState(prefill ? String(prefill.reps) : '');
  const [notes, setNotes] = useState('');

  const valid = weight !== '' && reps !== '';
  const save = () => valid && onSave({ weight: parseFloat(weight), reps: parseInt(reps), notes: notes.trim() });

  return (
    <div onClick={onCancel} style={{
      position: 'fixed', inset: 0, zIndex: 200,
      background: 'rgba(5, 3, 2, 0.7)',
      backdropFilter: 'blur(12px)', WebkitBackdropFilter: 'blur(12px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 380,
        background: '#181210',
        border: '1px solid rgba(232,217,189,0.12)',
        borderRadius: 20, padding: 22,
      }}>
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e', marginBottom: 4 }}>Add set</div>
        <div style={{ fontSize: 14, color: '#e8d9bd', marginBottom: 16 }}>{exercise.name}</div>

        <div style={{ display: 'flex', gap: 10, marginBottom: 12 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 11, color: '#9a8b6e', marginBottom: 6 }}>Weight (kg)</div>
            <input type="number" inputMode="decimal" step="0.5" value={weight} onChange={e => setWeight(e.target.value)} autoFocus
              style={{
                width: '100%', background: 'rgba(0,0,0,0.3)',
                border: '1px solid rgba(232,217,189,0.10)', borderRadius: 10,
                color: '#e8d9bd', padding: '12px 14px', fontFamily: 'Inter', fontSize: 20, fontWeight: 600,
                outline: 'none', textAlign: 'center', fontVariantNumeric: 'tabular-nums', boxSizing: 'border-box',
              }}
            />
          </div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 11, color: '#9a8b6e', marginBottom: 6 }}>Reps</div>
            <input type="number" inputMode="numeric" step="1" value={reps} onChange={e => setReps(e.target.value)}
              style={{
                width: '100%', background: 'rgba(0,0,0,0.3)',
                border: '1px solid rgba(232,217,189,0.10)', borderRadius: 10,
                color: '#e8d9bd', padding: '12px 14px', fontFamily: 'Inter', fontSize: 20, fontWeight: 600,
                outline: 'none', textAlign: 'center', fontVariantNumeric: 'tabular-nums', boxSizing: 'border-box',
              }}
            />
          </div>
        </div>

        <div style={{ marginBottom: 14 }}>
          <div style={{ fontSize: 11, color: '#9a8b6e', marginBottom: 6 }}>Quick DB weights</div>
          <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
            {DB_WEIGHTS.map(w => (
              <span key={w} onClick={() => setWeight(String(w))} style={{
                padding: '5px 10px', borderRadius: 6, fontSize: 11, fontWeight: 500, cursor: 'pointer',
                background: 'rgba(232,217,189,0.05)', border: '1px solid rgba(232,217,189,0.10)',
                color: '#c8b896', userSelect: 'none',
              }}>{w}</span>
            ))}
          </div>
        </div>

        <div style={{ marginBottom: 14 }}>
          <div style={{ fontSize: 11, color: '#9a8b6e', marginBottom: 6 }}>Notes (form, tempo, RPE)</div>
          <textarea value={notes} onChange={e => setNotes(e.target.value)} placeholder="e.g. paused 2s at bottom, slow eccentric"
            style={{
              width: '100%', minHeight: 60, background: 'rgba(0,0,0,0.3)',
              border: '1px solid rgba(232,217,189,0.10)', borderRadius: 10,
              color: '#e8d9bd', padding: '10px 14px', fontFamily: 'inherit', fontSize: 13,
              resize: 'vertical', boxSizing: 'border-box', outline: 'none',
            }}
          />
        </div>

        <div style={{ display: 'flex', gap: 8 }}>
          <Btn kind="ghost" onClick={onCancel} style={{ flex: 1 }}>Cancel</Btn>
          <Btn onClick={save} disabled={!valid} style={{ flex: 2 }}>Save set</Btn>
        </div>
      </div>
    </div>
  );
}

// ============= TASKS SCREEN =============
window.TasksScreen = function TasksScreen() {
  const [overrides, setOverrides] = useState(() => loadOverrides());
  const [targets] = useState(() => loadTargets());
  const [userTarget] = useState(() => loadUserWamTarget());
  const [editor, setEditor] = useState(null);

  const liveCurrent = useMemo(
    () => buildCurrentFromOverrides(overrides, targets, userTarget),
    [overrides, targets, userTarget]
  );

  const updateMark = (subjectCode, asmtName, mark) => {
    setOverrides(prev => {
      const next = { ...prev };
      next[subjectCode] = { ...(next[subjectCode] || {}) };
      if (mark === null || mark === '' || mark === undefined) {
        delete next[subjectCode][asmtName];
        if (Object.keys(next[subjectCode]).length === 0) delete next[subjectCode];
      } else {
        next[subjectCode][asmtName] = { mark: parseFloat(mark), done: true, awaiting: false };
      }
      saveOverrides(next);
      return next;
    });
  };

  // Build flat task list — every pending assessment.
  const tasks = [];
  for (const subject of liveCurrent) {
    for (const a of subject.assessments) {
      const isPending = !(a.done && a.mark != null);
      if (!isPending) continue;
      const required = requiredOnAssessment(subject, a);
      tasks.push({
        subjectCode: subject.code,
        subjectName: subject.name,
        name: a.name,
        weight: a.weight,
        due: a.due,
        days: daysUntil(a.due),
        awaiting: a.awaiting,
        submitted: a.submitted,
        required,
      });
    }
  }
  tasks.sort((a, b) => new Date(a.due) - new Date(b.due));

  // Group by urgency
  const overdue = tasks.filter(t => t.days <= 0);
  const thisWeek = tasks.filter(t => t.days > 0 && t.days <= 7);
  const next3Weeks = tasks.filter(t => t.days > 7 && t.days <= 21);
  const later = tasks.filter(t => t.days > 21);

  const Section = ({ label, items, colorDot }) => items.length === 0 ? null : (
    <>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '20px 4px 10px' }}>
        <span style={{ width: 6, height: 6, borderRadius: '50%', background: colorDot }} />
        <span style={{ fontSize: 11, fontWeight: 600, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e' }}>{label}</span>
        <span style={{ fontSize: 11, color: '#6b5d44', marginLeft: 'auto' }}>{items.length}</span>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {items.map((t, i) => (
          <GlassCard key={i} style={{ padding: '14px 16px', cursor: 'pointer' }} onClick={() => setEditor({ kind: 'mark', subjectCode: t.subjectCode, asmtName: t.name, currentValue: null })}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
              <div style={{ minWidth: 0, flex: 1 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
                  <span style={{ fontFamily: 'JetBrains Mono, SF Mono, monospace', fontSize: 11, color: '#9a8b6e', letterSpacing: 0.4 }}>{t.subjectCode}</span>
                  <span style={{ fontSize: 11, color: '#6b5d44' }}>· {t.weight}%</span>
                  {t.awaiting && <span style={{ fontSize: 11, color: '#e0a766' }}>· awaiting</span>}
                  {t.submitted && <span style={{ fontSize: 11, color: '#e0a766' }}>· submitted</span>}
                </div>
                <div style={{ fontSize: 14, fontWeight: 600, color: '#e8d9bd', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.name}</div>
                <div style={{ fontSize: 11, color: '#9a8b6e', marginTop: 2 }}>
                  {shortDate(t.due)} · {t.days <= 0 ? 'overdue' : t.days === 1 ? 'tomorrow' : t.days + ' days'}
                </div>
              </div>
              <div style={{ textAlign: 'right', flexShrink: 0 }}>
                <div style={{ fontSize: 10, color: '#9a8b6e', marginBottom: 2, letterSpacing: 0.4, textTransform: 'uppercase' }}>Need</div>
                <div style={{ fontFamily: 'Inter, sans-serif', fontWeight: 700, fontSize: 22, letterSpacing: -0.5, color: colorFor(t.required), fontVariantNumeric: 'tabular-nums' }}>
                  {t.required > 100 ? '>100' : t.required < 0 ? 'any' : fmt0(t.required)}
                </div>
              </div>
            </div>
          </GlassCard>
        ))}
      </div>
    </>
  );

  if (tasks.length === 0) {
    return (
      <div>
        <ScreenHeader title="Tasks" sub="No pending assessments" />
        <GlassCard feature><div style={{ fontSize: 14, color: '#c8b896' }}>Nothing pending. Have a beer.</div></GlassCard>
      </div>
    );
  }

  return (
    <>
      <div>
        <ScreenHeader title="Tasks" sub={tasks.length + ' pending · tap to enter mark'} />
        <Section label="Overdue" items={overdue} colorDot="#c4453a" />
        <Section label="This week" items={thisWeek} colorDot="#c4453a" />
        <Section label="Next 3 weeks" items={next3Weeks} colorDot="#e0a766" />
        <Section label="Later" items={later} colorDot="#7da967" />
      </div>
      {editor && (
        <EditModal
          editor={editor}
          onCancel={() => setEditor(null)}
          onSave={(val) => { updateMark(editor.subjectCode, editor.asmtName, val); setEditor(null); }}
          onClear={() => { updateMark(editor.subjectCode, editor.asmtName, null); setEditor(null); }}
        />
      )}
    </>
  );
};

// ============= REFLECT SCREEN =============
const LS_REFLECTIONS = 'optimiser_reflections_v1';
function loadReflections() {
  try { return JSON.parse(localStorage.getItem(LS_REFLECTIONS) || '[]'); }
  catch { return []; }
}
function saveReflection(r) {
  const all = loadReflections();
  all.unshift(r);
  localStorage.setItem(LS_REFLECTIONS, JSON.stringify(all.slice(0, 50)));
}

window.ReflectScreen = function ReflectScreen() {
  const [energy, setEnergy] = useState(7);
  const [mood, setMood] = useState('focused');
  const [thoughts, setThoughts] = useState('');
  const [view, setView] = useState('form'); // 'form' | 'submitted' | 'history'
  const [history, setHistory] = useState(() => loadReflections());
  const moods = ['focused', 'flat', 'buzzing', 'foggy', 'anxious', 'content', 'tired'];

  const submit = () => {
    const entry = { ts: new Date().toISOString(), energy, mood, thoughts };
    saveReflection(entry);
    setHistory(loadReflections());
    setView('submitted');
    // Fire to worker chat — Claude picks it up and updates state, can include in next brief
    const message = `Reflection check in.\nEnergy: ${energy}/10\nMood: ${mood}\nThoughts: ${thoughts || '(none)'}`;
    workerSendChat(message);
  };

  if (view === 'history') {
    return (
      <div>
        <div style={{ padding: '12px 4px 6px' }}>
          <button onClick={() => setView('form')} style={{ background: 'transparent', border: 'none', color: '#e0a766', fontSize: 14, fontFamily: 'inherit', padding: '6px 0', cursor: 'pointer' }}>← New entry</button>
        </div>
        <ScreenHeader title="Reflect · history" sub={history.length + ' entries'} />
        {history.length === 0 ? (
          <GlassCard feature><div style={{ fontSize: 14, color: '#c8b896' }}>No entries yet.</div></GlassCard>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {history.map((h, i) => (
              <GlassCard key={i} style={{ padding: '14px 16px' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
                  <span style={{ fontSize: 12, color: '#9a8b6e' }}>{new Date(h.ts).toLocaleString('en-AU', { timeZone: 'Australia/Sydney', weekday: 'short', day: 'numeric', month: 'short', hour: 'numeric', minute: '2-digit' })}</span>
                  <span style={{ fontSize: 12, fontWeight: 600, color: h.energy >= 7 ? '#7da967' : h.energy >= 4 ? '#e0a766' : '#c4453a' }}>{h.energy}/10 · {h.mood}</span>
                </div>
                {h.thoughts && <div style={{ fontSize: 13, color: '#c8b896', lineHeight: 1.5, whiteSpace: 'pre-wrap' }}>{h.thoughts}</div>}
              </GlassCard>
            ))}
          </div>
        )}
      </div>
    );
  }

  if (view === 'submitted') {
    return (
      <div>
        <ScreenHeader title="Reflect" sub="Today · submitted" />
        <GlassCard feature style={{ marginBottom: 14 }}>
          <div style={{ fontSize: 11, fontWeight: 500, letterSpacing: 0.6, textTransform: 'uppercase', color: '#9a8b6e', marginBottom: 12 }}>Saved</div>
          <div style={{ fontSize: 14, color: '#e8d9bd', lineHeight: 1.55 }}>
            Energy {energy}/10. Mood: {mood}.
          </div>
        </GlassCard>
        <div style={{ display: 'flex', gap: 8 }}>
          <Btn kind="ghost" onClick={() => { setView('form'); setThoughts(''); }} style={{ flex: 1 }}>New entry</Btn>
          <Btn kind="ghost" onClick={() => setView('history')} style={{ flex: 1 }}>History · {history.length}</Btn>
        </div>
      </div>
    );
  }

  return (
    <div>
      <ScreenHeader title="Reflect" sub="End of day check in" />

      <GlassCard style={{ marginBottom: 14 }}>
        <div style={{ fontSize: 13, color: '#9a8b6e', marginBottom: 10 }}>Energy · {energy} of 10</div>
        <input type="range" min="1" max="10" value={energy} onChange={e => setEnergy(parseInt(e.target.value))} style={{ width: '100%' }} />
      </GlassCard>

      <GlassCard style={{ marginBottom: 14 }}>
        <div style={{ fontSize: 13, color: '#9a8b6e', marginBottom: 10 }}>Mood</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          {moods.map(m => <Pill key={m} selected={mood === m} onClick={() => setMood(m)}>{m}</Pill>)}
        </div>
      </GlassCard>

      <GlassCard style={{ marginBottom: 14 }}>
        <div style={{ fontSize: 13, color: '#9a8b6e', marginBottom: 10 }}>Thoughts</div>
        <textarea
          value={thoughts}
          onChange={e => setThoughts(e.target.value)}
          placeholder="What ran the day, what didn't…"
          style={{
            width: '100%', minHeight: 90, background: 'rgba(0,0,0,0.25)',
            border: '1px solid rgba(232,217,189,0.10)', borderRadius: 10,
            color: '#e8d9bd', padding: '12px 14px', fontFamily: 'inherit', fontSize: 14,
            resize: 'vertical', boxSizing: 'border-box', outline: 'none',
          }}
        />
      </GlassCard>

      <div style={{ display: 'flex', gap: 8 }}>
        <Btn onClick={submit} style={{ flex: 1 }}>Save entry</Btn>
        <Btn kind="ghost" onClick={() => setView('history')} style={{ flex: 1 }}>History · {history.length}</Btn>
      </div>
    </div>
  );
};

// ============= SETTINGS OVERLAY =============
window.SettingsOverlay = function SettingsOverlay({ onClose }) {
  const [url, setUrl] = useState(getWorkerUrl());
  const [token, setToken] = useState(getAuthToken());
  const [status, setStatus] = useState({ kind: 'idle', text: '' });
  const [pushPerm, setPushPerm] = useState(typeof Notification !== 'undefined' ? Notification.permission : 'default');

  const save = async () => {
    setWorkerUrl(url);
    setAuthToken(token);
    setStatus({ kind: 'testing', text: 'Testing connection...' });
    try {
      await workerCall('/state');
      setStatus({ kind: 'ok', text: 'Connected. Token valid.' });
    } catch (e) {
      setStatus({ kind: 'fail', text: e.message });
    }
  };

  const enablePush = async () => {
    try {
      if (Notification.permission !== 'granted') {
        const p = await Notification.requestPermission();
        setPushPerm(p);
        if (p !== 'granted') { setStatus({ kind: 'fail', text: 'Push permission denied' }); return; }
      }
      const reg = await navigator.serviceWorker.ready;
      const vapid = await workerCall('/push/vapid-public-key');
      const key = urlBase64ToUint8Array(vapid.trim());
      const existing = await reg.pushManager.getSubscription();
      if (existing) await existing.unsubscribe();
      const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });
      const json = sub.toJSON();
      await workerCall('/push/subscribe', { method: 'POST', body: JSON.stringify({ endpoint: json.endpoint, keys: json.keys, device_label: navigator.userAgent.slice(0, 80) }) });
      setStatus({ kind: 'ok', text: 'Push enabled.' });
    } catch (e) {
      setStatus({ kind: 'fail', text: e.message });
    }
  };

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 200,
      background: 'rgba(5, 3, 2, 0.7)',
      backdropFilter: 'blur(12px)', WebkitBackdropFilter: 'blur(12px)',
      display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
      padding: 20, paddingTop: 'calc(env(safe-area-inset-top) + 20px)',
      overflowY: 'auto',
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 460,
        background: '#181210',
        border: '1px solid rgba(232,217,189,0.12)',
        borderRadius: 20, padding: 22, marginBottom: 40,
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
          <div style={{ fontSize: 18, fontWeight: 600, color: '#e8d9bd' }}>Settings</div>
          <button onClick={onClose} style={{ background: 'transparent', border: 'none', color: '#9a8b6e', fontSize: 22, cursor: 'pointer' }}>×</button>
        </div>

        <div style={{ marginBottom: 14 }}>
          <div style={{ fontSize: 11, color: '#9a8b6e', marginBottom: 6, letterSpacing: 0.4, textTransform: 'uppercase' }}>Worker URL</div>
          <input type="url" value={url} onChange={e => setUrl(e.target.value)} placeholder="https://optimiser-v2.willormond.workers.dev"
            autoCapitalize="off" autoCorrect="off" spellCheck="false"
            style={{ width: '100%', background: 'rgba(0,0,0,0.3)', border: '1px solid rgba(232,217,189,0.10)', borderRadius: 10, color: '#e8d9bd', padding: '12px 14px', fontFamily: 'JetBrains Mono, monospace', fontSize: 12, outline: 'none', boxSizing: 'border-box' }} />
        </div>

        <div style={{ marginBottom: 14 }}>
          <div style={{ fontSize: 11, color: '#9a8b6e', marginBottom: 6, letterSpacing: 0.4, textTransform: 'uppercase' }}>Auth token</div>
          <input type="text" value={token} onChange={e => setToken(e.target.value)} placeholder="64-char shared secret"
            autoCapitalize="off" autoCorrect="off" spellCheck="false"
            style={{ width: '100%', background: 'rgba(0,0,0,0.3)', border: '1px solid rgba(232,217,189,0.10)', borderRadius: 10, color: '#e8d9bd', padding: '12px 14px', fontFamily: 'JetBrains Mono, monospace', fontSize: 12, outline: 'none', boxSizing: 'border-box' }} />
          <div style={{ fontSize: 11, color: '#6b5d44', marginTop: 4 }}>Read from `~/.config/optimiser/auth_token` on the laptop.</div>
        </div>

        <Btn full onClick={save}>Save + test</Btn>

        {status.kind !== 'idle' && (
          <div style={{ marginTop: 12, padding: '10px 14px', borderRadius: 10, fontSize: 13,
            background: status.kind === 'ok' ? 'rgba(125,169,103,0.10)' : status.kind === 'fail' ? 'rgba(196,69,58,0.10)' : 'rgba(232,217,189,0.05)',
            color: status.kind === 'ok' ? '#7da967' : status.kind === 'fail' ? '#c4453a' : '#c8b896',
            border: '1px solid ' + (status.kind === 'ok' ? 'rgba(125,169,103,0.30)' : status.kind === 'fail' ? 'rgba(196,69,58,0.30)' : 'rgba(232,217,189,0.10)'),
          }}>{status.text}</div>
        )}

        <div style={{ height: 1, background: 'rgba(232,217,189,0.08)', margin: '20px 0' }} />

        <div style={{ marginBottom: 12 }}>
          <div style={{ fontSize: 13, fontWeight: 600, color: '#e8d9bd', marginBottom: 6 }}>Push notifications</div>
          <div style={{ fontSize: 12, color: '#9a8b6e' }}>
            Status: {pushPerm === 'granted' ? 'enabled' : pushPerm === 'denied' ? 'denied (enable in iOS settings)' : 'not enabled'}
          </div>
        </div>
        <Btn full kind="ghost" onClick={enablePush} disabled={!isWorkerConfigured() || pushPerm === 'denied'}>
          {pushPerm === 'granted' ? 'Re-subscribe' : 'Enable push'}
        </Btn>

        <div style={{ height: 1, background: 'rgba(232,217,189,0.08)', margin: '20px 0' }} />

        <div style={{ fontSize: 11, color: '#6b5d44', lineHeight: 1.55 }}>
          Optimiser v3 · Dune palette · WAM math + Tasks + Gym + Reflect.<br/>
          Local first; cloud sync via the worker when configured.
        </div>
      </div>
    </div>
  );
};

function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - base64String.length % 4) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const raw = atob(base64);
  const out = new Uint8Array(raw.length);
  for (let i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
  return out;
}

Object.assign(window, {
  WamScreen: window.WamScreen, GymScreen: window.GymScreen,
  TasksScreen: window.TasksScreen, ReflectScreen: window.ReflectScreen,
  SettingsOverlay: window.SettingsOverlay,
});
