// ─────────────────────────────────────────────────────────────
// LifeWheel Coach — Live Session view
// Two-pane workspace during a coaching call.
// LEFT  = shared canvas (what the client also sees)
// RIGHT = private observations (only the coach)
// ─────────────────────────────────────────────────────────────

// Top-level: route between demo (LWDATA fixture) and real (RTDB-backed) modes
// based on URL params.
//   ?id=<fixtureId>           → demo mode (legacy, design-partner walkthroughs)
//   ?clientId=<userUid>       → real mode against /sessions/{sessionId}
//   ?sessionId=<id>           → resume an existing real session
//   (no params)               → demo mode against next upcoming LWDATA fixture
function LWSession({ routeParams }) {
  // Brutalism theme override on the page-entry, before sub-components mount
  const [brutMode, setBrutMode] = window.LWBrutal.useBrutMode();
  const cBase = (window.LWC && window.LWC.t('dark')) || {};
  const c = window.LWBrutal.makeC(cBase, brutMode);
  const fonts = { ...((window.LWC && window.LWC.fonts) || {}), ...window.LWBrutal.fonts };
  window.__brutMode = brutMode; window.__brutSetMode = setBrutMode;
  window.__brutC = c; window.__brutFonts = fonts;
  // Prefer SPA route params, fall back to legacy ?clientId=&sessionId= for direct loads.
  const rp = routeParams || (window.LWRouter ? (window.LWRouter.parseRoute().params || {}) : {});
  const search = new URLSearchParams(window.location.search);
  const fixtureId = rp.id || search.get('id');
  const clientId  = rp.clientId || search.get('clientId');
  const sessionIdParam = rp.sessionId || search.get('sessionId');

  if (clientId) return <RealSession clientId={clientId} sessionIdParam={sessionIdParam} />;
  return <DemoSession fixtureId={fixtureId} />;
}

// ── Demo (legacy) — localStorage persistence ──────────────────
function DemoSession({ fixtureId }) {
  const session = fixtureId
    ? LWDATA.SESSIONS.find(s => s.id === fixtureId)
    : LWDATA.SESSIONS.find(s => s.status === 'upcoming');
  const client = session ? LWDATA.byId(session.clientId) : LWDATA.CLIENTS[0];

  const lsKey = `lw_session_v2_${session ? session.id : 'demo'}`;
  const persisted = readDemoPersisted(lsKey);
  const initial = {
    elapsedSec: persisted?.elapsedSec ?? 18 * 60 + 12,
    scores:      persisted?.scores      ?? (client.scores ? [...client.scores] : Array(8).fill(5)),
    entryScores: persisted?.entryScores ?? (client.scores ? [...client.scores] : Array(8).fill(5)),
    agenda:      persisted?.agenda      ?? DEFAULT_AGENDA,
    commitments: persisted?.commitments ?? DEFAULT_COMMITMENTS,
    todos:       persisted?.todos       ?? DEFAULT_TODOS,
    moments:     persisted?.moments     ?? DEFAULT_MOMENTS,
    privateNotes: persisted?.privateNotes ?? DEFAULT_PRIVATE_NOTES,
    themes:      persisted?.themes      ?? DEFAULT_THEMES,
    followups:   persisted?.followups   ?? DEFAULT_FOLLOWUPS,
  };
  return (
    <SessionInnerUI
      sessionMeta={{ minutes: session?.minutes || 50, sessionNumber: 14, mode: 'demo' }}
      client={client}
      initial={initial}
      saveAll={(state) => { try { localStorage.setItem(lsKey, JSON.stringify(state)); } catch {} }}
      saveShared={null}
      savePrivate={null}
      onEnd={(scoresAtEnd) => {
        try { localStorage.removeItem(lsKey); } catch {}
        (window.LWRouter ? window.LWRouter.go(`Client.html?id=${client.id}`) : (window.location.href = `Client.html?id=${client.id}`));
      }}
    />
  );
}

function readDemoPersisted(lsKey) {
  try { return JSON.parse(localStorage.getItem(lsKey) || 'null'); } catch { return null; }
}

// ── Real mode — RTDB-backed ────────────────────────────────────
function RealSession({ clientId, sessionIdParam }) {
  const data = useRealSessionData(clientId, sessionIdParam);
  if (data.status === 'loading') return <SessionLoadingShell />;
  if (data.status === 'unauthorized') return <SessionUnauthorizedShell />;
  // status === 'ready' — mount inner UI with hydrated initial state.
  return (
    <SessionInnerUI
      sessionMeta={data.sessionMeta}
      client={data.client}
      initial={data.initial}
      saveAll={null}
      saveShared={data.saveShared}
      savePrivate={data.savePrivate}
      onEnd={data.onEnd}
      onSendAll={data.flushOutbox}
    />
  );
}

// Async hydration + debounced persistence for a real session.
//   Returns one of:
//     { status: 'loading' }
//     { status: 'unauthorized' }
//     { status: 'ready', client, sessionMeta, initial, saveShared, savePrivate, onEnd }
function useRealSessionData(clientId, sessionIdParam) {
  const [state, setState] = useState({ status: 'loading' });
  useEffect(() => {
    if (!window.LWAuth || !window.LWFB) return;
    let cancelled = false;
    let coachUid = null;
    let resolvedSessionId = null;

    const unsubAuth = window.LWAuth.onAuthChanged(async (u) => {
      if (!u) return; // requireAuth on the page handles redirect
      coachUid = u.uid;
      try {
        // 1. Roster + link gate
        const [rosterSnap, linkSnap] = await Promise.all([
          window.LWFB.db.ref(`/coach_clients/${coachUid}/${clientId}`).get(),
          window.LWFB.db.ref(`/users/${clientId}/coach_link`).get(),
        ]);
        if (cancelled) return;
        const link = linkSnap.exists() ? linkSnap.val() : null;
        if (!link || link.coachId !== coachUid || link.status !== 'active') {
          setState({ status: 'unauthorized' });
          return;
        }
        const roster = rosterSnap.exists() ? rosterSnap.val() : {};

        // 2. Decide session id (resume vs new)
        const startedAt = Date.now();
        const isResume = !!sessionIdParam;
        resolvedSessionId = sessionIdParam || `${coachUid}_${clientId}_${startedAt}`;

        // 3. Hydrate from existing session if present, else seed from spheres.
        // Reading /sessions/{id} that doesn't exist returns PERMISSION_DENIED (rule
        // requires data.exists()), so only attempt the read when resuming.
        // /users/{uid}/spheres + /areas can also be empty/denied — tolerate via .catch.
        const safeGet = (ref) => ref.get().catch(() => ({ exists: () => false, val: () => null }));
        const [sessionSnap, privateSnap, spheresSnap, areasSnap] = await Promise.all([
          isResume ? safeGet(window.LWFB.db.ref(`/sessions/${resolvedSessionId}`))                 : Promise.resolve({ exists: () => false, val: () => null }),
          isResume ? safeGet(window.LWFB.db.ref(`/coach_private/${coachUid}/${resolvedSessionId}`)) : Promise.resolve({ exists: () => false, val: () => null }),
          safeGet(window.LWFB.db.ref(`/users/${clientId}/spheres`)),
          safeGet(window.LWFB.db.ref(`/users/${clientId}/areas`)),
        ]);
        if (cancelled) return;

        const seedScores = sphereScoresFromAnyForSession(
          spheresSnap.exists() ? spheresSnap.val() : null,
          areasSnap.exists() ? areasSnap.val() : null,
        ) || Array(8).fill(5);

        const existing  = sessionSnap.exists() ? sessionSnap.val() : null;
        const existingP = privateSnap.exists() ? privateSnap.val() : null;

        // Update URL to include sessionId for refresh-resume.
        try {
          const url = new URL(window.location.href);
          url.searchParams.set('clientId', clientId);
          url.searchParams.set('sessionId', resolvedSessionId);
          url.searchParams.delete('id');
          window.history.replaceState({}, '', url.toString());
        } catch {}

        const initial = {
          elapsedSec: existing && existing.startedAt ? Math.max(0, Math.floor((Date.now() - Number(existing.startedAt)) / 1000)) : 0,
          scores:       (existing && existing.scoresWorking) || (existing && existing.scoresAtStart) || [...seedScores],
          entryScores:  (existing && existing.scoresAtStart) || [...seedScores],
          agenda:       (existing && existing.agenda)      || [],
          commitments:  (existing && existing.commitments) || [],
          todos:        (existing && existing.todos)       || [],
          moments:      (existing && existing.moments)     || [],
          privateNotes: (existingP && existingP.privateNotes) || '',
          themes:       (existingP && existingP.themes)       || [],
          followups:    (existingP && existingP.followups)    || [],
        };

        // Build the client object the UI expects.
        const fallbackName = roster.displayName || link.coachName /* fallback only */ || 'Client';
        const initials = roster.initials || (window.LWAuth ? window.LWAuth.initialsOf(fallbackName) : '·');
        const client = {
          id: clientId,
          userUid: clientId,
          name: fallbackName,
          initials,
          avatarHue: roster.avatarHue ?? 140,
          commPref: { channel: roster.channel || 'direct', handle: roster.channelHandle || '' },
          scores: initial.entryScores,
        };

        // Lazy-create session doc on first save (idempotent update).
        const sessionRef = window.LWFB.db.ref(`/sessions/${resolvedSessionId}`);
        const privateRef = window.LWFB.db.ref(`/coach_private/${coachUid}/${resolvedSessionId}`);

        let sessionDocCreated = !!existing;
        async function ensureSessionDoc() {
          if (sessionDocCreated) return;
          await sessionRef.update({
            coachId: coachUid,
            userUid: clientId,
            startedAt,
            scheduledMinutes: 50,
            scoresAtStart: [...initial.entryScores],
          });
          sessionDocCreated = true;
        }

        const saveShared = debounceCb(async (shared) => {
          await ensureSessionDoc();
          const payload = {
            scoresWorking: shared.scores,
            agenda: shared.agenda || [],
            commitments: shared.commitments || [],
            todos: shared.todos || [],
            moments: shared.moments || [],
          };
          try { await sessionRef.update(payload); }
          catch (err) { console.warn('[session] saveShared failed:', err.message); }
        }, 600);

        const savePrivate = debounceCb(async (priv) => {
          // No need to create the public session doc just to save private state,
          // but we still want a consistent (coachId, userUid, sessionId) tuple.
          try {
            await privateRef.update({
              coachId: coachUid,
              userUid: clientId,
              sessionId: resolvedSessionId,
              privateNotes: priv.privateNotes ?? '',
              themes: priv.themes ?? [],
              followups: priv.followups ?? [],
            });
          } catch (err) { console.warn('[session] savePrivate failed:', err.message); }
        }, 600);

        // Materialize current shared state to /coach_outbox + write a push signal.
        // Item ids are deterministic (use the moment/commitment local id) so repeat
        // calls upsert instead of duplicating. Items already accepted/skipped by
        // the client are preserved as-is.
        async function flushOutbox(state, signalKind) {
          const ts = Date.now();
          const outboxRef = window.LWFB.db.ref(`/coach_outbox/${clientId}`);
          const coachName = (window.LWAuth.currentCoach && window.LWAuth.currentCoach()?.profile?.displayName) || coachUid;
          const ops = [];
          const writeItem = (id, payload) => {
            if (!id) return;
            ops.push((async () => {
              const childRef = outboxRef.child(id);
              const existingSnap = await childRef.get().catch(() => null);
              const existing = existingSnap && existingSnap.exists() ? existingSnap.val() : null;
              if (existing && (existing.acceptedAt || existing.skippedAt)) return; // client already resolved — leave alone
              const merged = {
                ...payload,
                fromSessionId: resolvedSessionId,
                fromCoachId: coachUid,
                fromCoachName: coachName,
                createdAt: existing?.createdAt || ts,
              };
              // Reset pushedAt to null on first write only — re-pushes for the
              // same item id are gated by CF (skips items where pushedAt is set).
              if (!existing) merged.pushedAt = null;
              await childRef.update(merged);
            })());
          };
          (state && state.moments || []).forEach((m) => {
            if (!m || !m.text || !m.id) return;
            writeItem(m.id, { kind: 'journal', text: m.text });
          });
          (state && state.commitments || []).forEach((co) => {
            if (!co || !co.id) return;
            const title = co.text || co.name || co.title;
            if (!title) return;
            writeItem(co.id, {
              kind: co.kind === 'habit' ? 'habit' : 'goal',
              title,
              sphere: co.sphere || null,
              due: co.due || null,
              days: co.days || null,
              time: co.time || null,
              remind: !!co.remind,
              subtasks: Array.isArray(co.subtasks) ? co.subtasks.filter(s => s && s.text) : [],
            });
          });
          (state && state.todos || []).forEach((td) => {
            if (!td || !td.id) return;
            const title = td.text || td.title;
            if (!title) return;
            writeItem(td.id, { kind: 'todo', title });
          });
          if (ops.length) {
            try { await Promise.all(ops); }
            catch (err) { console.warn('[session] outbox upsert failed:', err.message); throw err; }
          }
          // Trigger CF — fires OneSignal push to client.
          try {
            await outboxRef.child('_meta/lastPushSignal').set({
              at: ts,
              fromCoachId: coachUid,
              fromSessionId: resolvedSessionId,
              kind: signalKind,
            });
          } catch (err) {
            console.warn('[session] push-signal write failed:', err.message);
            throw err;
          }
        }

        async function onEnd(scoresAtEnd, finalState) {
          await ensureSessionDoc();
          const endedAt = Date.now();
          try {
            await sessionRef.update({ endedAt, scoresAtEnd });
          } catch (err) { console.warn('[session] end failed:', err.message); }
          try {
            await flushOutbox(finalState, 'session_end');
          } catch {
            // Non-fatal — session is closed even if outbox write fails.
          }
          (window.LWRouter ? window.LWRouter.go(`Client.html?id=${clientId}`) : (window.location.href = `Client.html?id=${clientId}`));
        }

        setState({
          status: 'ready',
          client,
          sessionMeta: { minutes: 50, sessionNumber: null, mode: 'real', sessionId: resolvedSessionId },
          initial,
          saveShared,
          savePrivate,
          onEnd,
          flushOutbox: (state) => flushOutbox(state, 'manual'),
        });
      } catch (err) {
        console.error('[session] hydration failed:', err);
        if (!cancelled) setState({ status: 'unauthorized' });
      }
    });

    return () => { cancelled = true; if (unsubAuth) unsubAuth(); };
  }, [clientId, sessionIdParam]);

  return state;
}

// Same logic as sphereScoresFromAny in client.jsx — kept duplicated for
// page isolation. See comment there for the legacy-shape rationale.
// Round a sphere score for display: integers stay integers, fractions show 1 decimal.
function fmtScore(v) {
  if (v == null) return '—';
  const n = Number(v);
  if (!isFinite(n)) return '—';
  return Number.isInteger(n) ? String(n) : n.toFixed(1);
}

function sphereScoresFromAnyForSession(spheres, areas) {
  const order = LWDATA.SPHERE_KEYS;
  if (spheres && typeof spheres === 'object') {
    if (Array.isArray(spheres) && spheres.length === 8) return spheres.map(n => Number(n) || 0);
    return order.map(k => spheres[k] != null ? Number(spheres[k]) : null);
  }
  if (areas) {
    const list = Array.isArray(areas) ? areas : Object.values(areas);
    const byKey = {};
    list.forEach(a => {
      if (!a) return;
      const key = (a.initialCategory || a.sphere || a.key || a.id || '').toString().toLowerCase();
      const v = a.value != null ? a.value : (a.score != null ? a.score : null);
      if (key && v != null && order.indexOf(key) >= 0) byKey[key] = parseFloat(v);
    });
    if (Object.keys(byKey).length >= 4) {
      return order.map(k => byKey[k] != null ? byKey[k] : null);
    }
    const sorted = list
      .filter(a => a && a.value != null && a.sortOrder != null)
      .map(a => ({ pos: parseInt(a.sortOrder, 10) - 1, value: parseFloat(a.value) }))
      .filter(x => x.pos >= 0 && x.pos < 8 && !isNaN(x.value));
    if (sorted.length === 0) return null;
    const out = Array(8).fill(null);
    sorted.forEach(({ pos, value }) => { out[pos] = Math.max(0, Math.min(10, value)); });
    return out;
  }
  return null;
}

// Debounce helper — calls the latest invocation `delay` ms after the last call.
function debounceCb(fn, delay) {
  let t = null, latest = null;
  return function (...args) {
    latest = args;
    if (t) clearTimeout(t);
    t = setTimeout(() => { t = null; const a = latest; latest = null; fn.apply(null, a); }, delay);
  };
}

// ── Inner UI — owns state, persists via supplied callbacks ─────
const DEFAULT_AGENDA = [
  { id: 1, text: "How the boundary conversation with mom went", done: true },
  { id: 2, text: "Whether to take the team-lead role", done: false },
  { id: 3, text: "Sleep & energy — the running cost of carrying both", done: false },
];
const DEFAULT_COMMITMENTS = [
  { id: 1, kind: 'habit', text: "Real lunch break (off-laptop)", sphere: 'health', days: [1,2,3,4,5], time: '12:30', remind: true },
  { id: 2, kind: 'goal',  text: "Decide on the team-lead role", sphere: 'career',  due: '2026-05-20', remind: true,
    subtasks: [
      { id: 21, text: "Draft acceptance letter (don't send)", done: false },
      { id: 22, text: "List 3 boundaries the role would need", done: false },
      { id: 23, text: "Talk to Priya about her experience", done: true },
    ],
  },
];
const DEFAULT_TODOS = [
  { id: 1, text: "Send the journaling prompt I mentioned", done: false },
  { id: 2, text: "Re-read Mar 25 session notes before next time", done: false },
];
const DEFAULT_MOMENTS = [
  { id: 1, text: "I keep saying yes to be safe — but the safety isn't real anymore." },
];
const DEFAULT_PRIVATE_NOTES = "Brings career stuff first but lights up when we talk about her mom. The lead role is a proxy question. She wants permission to say no without feeling small.";
const DEFAULT_THEMES = ['permission', 'small-self', 'safety as habit'];
const DEFAULT_FOLLOWUPS = [
  { id: 1, text: "Revisit: where did 'being needed = being safe' get installed?" },
  { id: 2, text: "Try Internal Family Systems framing on the small-self piece" },
];

// Carried over from prior sessions (read-only context). Demo only.
const DEMO_PREVIOUS_COMMITMENTS = [
  { id: 91, kind: 'goal', text: "Set up weekly therapy with Dr. Wen", sphere: 'health', from: 'Session 13 · Apr 8', status: 'done',
    subtasks: [{ id: 911, text: 'Email intake form', done: true }, { id: 912, text: 'Book first slot', done: true }] },
  { id: 92, kind: 'habit', text: "Sunday week-review (15 min)", sphere: 'growth', from: 'Session 12 · Mar 25', status: 'slipping',
    days: [0], time: '19:00' },
  { id: 93, kind: 'goal', text: "Have the boundary conversation with mom", sphere: 'love', from: 'Session 13 · Apr 8', status: 'done', subtasks: [] },
];

function SessionInnerUI({ sessionMeta, client, initial, saveAll, saveShared, savePrivate, onEnd, onSendAll }) {
  const { c, fonts } = useLW();
  const { isMobile } = useViewport();
  const isReal = sessionMeta.mode === 'real';

  const [elapsedSec, setElapsedSec] = useState(initial.elapsedSec);
  useEffect(() => {
    const t = setInterval(() => setElapsedSec(s => s + 1), 1000);
    return () => clearInterval(t);
  }, []);

  const [scores, setScores] = useState(initial.scores);
  const entryScores = initial.entryScores;
  const [agenda, setAgenda] = useState(initial.agenda);
  const [commitments, setCommitments] = useState(initial.commitments);
  const [todos, setTodos] = useState(initial.todos);
  const [moments, setMoments] = useState(initial.moments);
  const [privateNotes, setPrivateNotes] = useState(initial.privateNotes);
  const [themes, setThemes] = useState(initial.themes);
  const [followups, setFollowups] = useState(initial.followups);

  const previousCommitments = isReal ? [] : DEMO_PREVIOUS_COMMITMENTS;

  // Persist on change.
  useEffect(() => {
    if (saveAll) {
      saveAll({ elapsedSec, scores, entryScores, agenda, commitments, moments, privateNotes, themes, followups, todos });
    } else if (saveShared) {
      saveShared({ scores, agenda, commitments, todos, moments });
    }
  }, [elapsedSec, scores, agenda, commitments, moments, todos]); // intentional: don't fire on private state
  useEffect(() => {
    if (savePrivate) savePrivate({ privateNotes, themes, followups });
  }, [privateNotes, themes, followups]);

  const [activePane, setActivePane] = useState('shared');
  const [confirmEnd, setConfirmEnd] = useState(false);
  const [sendState, setSendState] = useState('idle'); // idle | sending | sent | error
  useEffect(() => {
    if (sendState === 'sent' || sendState === 'error') {
      const t = setTimeout(() => setSendState('idle'), 2200);
      return () => clearTimeout(t);
    }
  }, [sendState]);
  const handleSendAll = async () => {
    if (!onSendAll || sendState === 'sending') return;
    setSendState('sending');
    try {
      await onSendAll({ moments, commitments, todos });
      setSendState('sent');
    } catch (e) {
      console.warn('[session] send-all failed:', e?.message);
      setSendState('error');
    }
  };

  const fmtTime = (s) => {
    const h = Math.floor(s / 3600);
    const m = Math.floor((s % 3600) / 60).toString().padStart(2, '0');
    const ss = (s % 60).toString().padStart(2, '0');
    return h ? `${h}:${m}:${ss}` : `${m}:${ss}`;
  };
  const minutes = sessionMeta.minutes || 50;
  const overrunning = elapsedSec > minutes * 60;
  const minutesLeft = minutes - Math.floor(elapsedSec / 60);

  return (
    <div style={{ height: '100vh', background: c.bg, display: 'flex', flexDirection: 'column' }}>
      <SessionHeader
        client={client} session={{ minutes, channel: client.commPref?.channel }}
        elapsedSec={elapsedSec} minutesLeft={minutesLeft} overrunning={overrunning}
        fmtTime={fmtTime}
        onEnd={() => setConfirmEnd(true)}
        onSendAll={onSendAll ? handleSendAll : null}
        sendState={sendState}
      />

      {isMobile && (
        <div style={{
          display: 'flex', padding: '8px 16px', gap: 6,
          borderBottom: `1px solid ${c.borderSubtle}`, background: c.bgSubtle,
        }}>
          {[
            { v: 'shared', label: window.LWLang ? window.LWLang.t('session.shared_canvas') : 'Shared canvas', icon: '◐' },
            { v: 'private', label: window.LWLang ? window.LWLang.t('session.your_notes') : 'Your notes', icon: '🔒' },
          ].map(o => (
            <button key={o.v} onClick={() => setActivePane(o.v)} style={{
              flex: 1, padding: '8px 10px', borderRadius: 8, border: 'none', cursor: 'pointer',
              background: activePane === o.v ? c.elevated : 'transparent',
              color: activePane === o.v ? c.textPrimary : c.textSecondary,
              fontFamily: fonts.body, fontSize: 13, fontWeight: 600,
            }}>
              <span style={{ marginRight: 6 }}>{o.icon}</span>{o.label}
            </button>
          ))}
        </div>
      )}

      <div style={{
        flex: 1, display: 'grid',
        gridTemplateColumns: isMobile ? '1fr' : '1.25fr 1fr',
        gap: 1, background: c.borderSubtle,
        minHeight: 0,
      }}>
        {(!isMobile || activePane === 'shared') && (
          <SharedPane
            client={client}
            scores={scores} entryScores={entryScores} setScores={setScores}
            agenda={agenda} setAgenda={setAgenda}
            commitments={commitments} setCommitments={setCommitments}
            previousCommitments={previousCommitments}
            todos={todos} setTodos={setTodos}
            moments={moments} setMoments={setMoments}
          />
        )}
        {(!isMobile || activePane === 'private') && (
          <PrivatePane
            privateNotes={privateNotes} setPrivateNotes={setPrivateNotes}
            themes={themes} setThemes={setThemes}
            followups={followups} setFollowups={setFollowups}
          />
        )}
      </div>

      {confirmEnd && (
        <EndSessionSheet
          onClose={() => setConfirmEnd(false)}
          client={client}
          commitments={commitments} moments={moments} followups={followups}
          elapsedSec={elapsedSec} fmtTime={fmtTime}
          onConfirm={() => onEnd(scores, { moments, commitments, todos, agenda, themes, followups })}
        />
      )}
    </div>
  );
}

// ── Loading + unauthorized shells ──────────────────────────────
function SessionLoadingShell() {
  const { c: cBase, fonts: fontsBase, t } = useLW();
  const isBrut = !!(window.__brutMode && window.__brutC);
  const c = isBrut ? window.__brutC : cBase;
  const fonts = isBrut ? (window.__brutFonts || fontsBase) : fontsBase;
  return (
    <div style={{
      height: '100vh', background: c.bg,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      flexDirection: 'column', gap: 16, padding: 24,
    }}>
      <div style={{
        fontFamily: isBrut ? fonts.mono : fonts.display,
        fontStyle: isBrut ? 'normal' : 'italic',
        fontSize: isBrut ? 13 : 18,
        color: c.textTertiary,
        letterSpacing: isBrut ? '0.16em' : 'normal',
        textTransform: isBrut ? 'uppercase' : 'none',
      }}>{isBrut ? `—— ${t('session.preparing')} ——` : t('session.preparing')}</div>
    </div>
  );
}

function SessionUnauthorizedShell() {
  const { c: cBase, fonts: fontsBase, t } = useLW();
  const isBrut = !!(window.__brutMode && window.__brutC);
  const c = isBrut ? window.__brutC : cBase;
  const fonts = isBrut ? (window.__brutFonts || fontsBase) : fontsBase;
  return (
    <div style={{
      height: '100vh', background: c.bg,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
    }}>
      <div style={{
        maxWidth: 440, padding: 36,
        borderRadius: isBrut ? 2 : 18,
        background: c.card,
        border: isBrut ? `1.5px solid ${c.ink}` : `1px solid ${c.borderSubtle}`,
        boxShadow: isBrut ? `6px 6px 0 0 ${c.ink}` : '0 12px 36px rgba(0,0,0,0.30)',
      }}>
        <h2 style={{
          fontFamily: isBrut ? fonts.mono : fonts.display,
          fontSize: isBrut ? 20 : 22,
          fontWeight: isBrut ? 700 : 600,
          lineHeight: 1.2, letterSpacing: '-0.01em',
          color: c.textPrimary, margin: 0, marginBottom: 8,
          textTransform: isBrut ? 'uppercase' : 'none',
        }}>{t('session.unauth_title')}</h2>
        <p style={{
          fontFamily: isBrut ? fonts.mono : fonts.display,
          fontStyle: 'italic',
          fontSize: isBrut ? 13 : 15,
          color: c.textSecondary, margin: 0, marginBottom: 22,
          lineHeight: 1.55,
          ...(isBrut ? { borderLeft: `2px solid ${c.ink}`, paddingLeft: 14 } : {}),
        }}>
          {t('session.unauth_body')}
        </p>
        <a href="#/dashboard" className={isBrut ? 'br-cta' : ''} style={isBrut ? {
          display: 'inline-block', padding: '11px 18px', fontSize: 12,
          textDecoration: 'none',
        } : {
          display: 'inline-block', padding: '10px 18px', borderRadius: 10,
          background: c.accent, color: c.textOnAccent, textDecoration: 'none',
          fontFamily: fonts.app, fontSize: 14, fontWeight: 700, letterSpacing: '0.01em',
        }}>
          {isBrut ? `[ ← ${(t('client.back_to_dashboard') || 'Back to dashboard').toUpperCase()} ]` : t('client.back_to_dashboard')}
        </a>
      </div>
    </div>
  );
}

// ─── Header ────────────────────────────────────────────────────
function SessionHeader({ client, session, elapsedSec, minutesLeft, overrunning, fmtTime, onEnd, onSendAll, sendState }) {
  const { c, fonts, t } = useLW();
  const { isMobile } = useViewport();
  const sessionNumber = 14; // mock — would come from data
  const cp = client.commPref || { channel: 'video' };
  const channelLabel = { telegram: 'Telegram', whatsapp: 'WhatsApp', zoom: 'Zoom', meet: 'Google Meet', phone: 'Phone', sms: 'SMS' }[cp.channel] || 'Video';

  const isBrut = !!(window.__brutMode && window.__brutC);
  return (
    <header style={{
      padding: isMobile ? '12px 16px' : '14px 24px',
      borderBottom: isBrut ? `1.5px solid ${c.ink || c.borderSubtle}` : `1px solid ${c.borderSubtle}`,
      background: isBrut ? c.bg : hexToRgba(c.bg, 0.92),
      backdropFilter: isBrut ? 'none' : 'blur(10px)',
      display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
    }}>
      <a href="Dashboard.html" style={{
        fontFamily: isBrut ? '"IBM Plex Mono", monospace' : fonts.body,
        fontSize: 13,
        fontWeight: isBrut ? 700 : 400,
        letterSpacing: isBrut ? '0.06em' : 'normal',
        textTransform: isBrut ? 'uppercase' : 'none',
        color: c.textTertiary, textDecoration: 'none',
        display: 'flex', alignItems: 'center', gap: 6,
      }}>
        ← <span>{window.LWLang ? window.LWLang.t('nav.today') : 'Dashboard'}</span>
      </a>

      <div style={{ display: 'flex', alignItems: 'center', gap: 12, flex: 1, minWidth: 200 }}>
        <Avatar name={client.name} size={isMobile ? 36 : 42} hue={client.avatarHue} status="online" />
        <div style={{ minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 1 }}>
            <span style={{ fontFamily: fonts.display, fontSize: isMobile ? 16 : 19, fontWeight: 600, color: c.textPrimary, letterSpacing: '-0.005em' }}>
              {client.name}
            </span>
            <span style={{ fontFamily: fonts.body, fontSize: 11, fontWeight: 700, color: c.accent, letterSpacing: '0.10em', textTransform: 'uppercase' }}>
              <span style={{
                display: 'inline-block', width: 7, height: 7, borderRadius: '50%',
                background: c.accent, marginRight: 5, animation: 'lw-pulse 2s ease-in-out infinite',
              }} />
              live
            </span>
          </div>
          <div style={{ fontFamily: fonts.body, fontSize: 12, color: c.textSecondary, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            <span>Session 14</span><span style={{ opacity: 0.4 }}>·</span>
            <span>{sessionMinutes(session)} min · {channelLabel}</span>
            {!isMobile && (<><span style={{ opacity: 0.4 }}>·</span><span>biweekly</span></>)}
          </div>
        </div>
      </div>

      {/* Timer */}
      <div style={{
        display: 'flex', alignItems: 'baseline', gap: 8,
        padding: '8px 14px', borderRadius: 10,
        background: overrunning ? hexToRgba(c.flame, 0.10) : c.bgSubtle,
        border: `1px solid ${overrunning ? hexToRgba(c.flame, 0.3) : c.borderSubtle}`,
      }}>
        <span style={{
          fontFamily: fonts.app, fontSize: isMobile ? 18 : 22, fontWeight: 700,
          color: overrunning ? c.flame : c.textPrimary, letterSpacing: '-0.01em',
          fontVariantNumeric: 'tabular-nums',
        }}>{fmtTime(elapsedSec)}</span>
        <span style={{ fontFamily: fonts.body, fontSize: 11, fontWeight: 600, color: overrunning ? c.flame : c.textTertiary, letterSpacing: '0.05em' }}>
          {overrunning ? `+${Math.abs(minutesLeft)}m over` : `${minutesLeft}m left`}
        </span>
      </div>

      {onSendAll && (
        <Button
          variant={sendState === 'error' ? 'ghost' : 'primary'}
          size="sm"
          onClick={onSendAll}
          disabled={sendState === 'sending'}
        >
          {sendState === 'sending' ? t('session.sending')
           : sendState === 'sent' ? t('session.sent')
           : sendState === 'error' ? t('session.send_err')
           : t('session.send_all')}
        </Button>
      )}
      <Button variant="ghost" size="sm" onClick={onEnd}>{t('session.end_session')}</Button>

      <style>{`
        @keyframes lw-pulse {
          0%, 100% { opacity: 1; transform: scale(1); }
          50% { opacity: 0.5; transform: scale(0.85); }
        }
      `}</style>
    </header>
  );
}

function sessionMinutes(s) { return s?.minutes || 50; }

// ─── SHARED PANE ───────────────────────────────────────────────
function SharedPane({ client, scores, entryScores, setScores, agenda, setAgenda, commitments, setCommitments, previousCommitments, todos, setTodos, moments, setMoments }) {
  const { c, fonts, t } = useLW();
  const firstName = client.name.split(' ')[0];
  return (
    <div style={{ background: c.bg, padding: 24, overflowY: 'auto', minHeight: 0 }}>
      <PaneHeader
        eyebrow={<><span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: c.accent, marginRight: 8, verticalAlign: 'middle' }} />{t('session.shared_eyebrow', { name: firstName })}</>}
        title={t('session.shared_title')}
        subtitle={t('session.shared_subtitle', { name: firstName })}
      />

      <SessionWheel scores={scores} entryScores={entryScores} setScores={setScores} client={client} />

      <SectionHeading title={t('session.todays_agenda')} subtitle={t('session.agenda_count', { done: agenda.filter(a => a.done).length, total: agenda.length })} />
      <CheckList
        items={agenda} setItems={setAgenda}
        placeholder={t('session.add_agenda')}
        toggleable
      />

      <SectionHeading title={t('session.goals_habits')} subtitle={t('session.goals_subtitle')} />
      <CommitmentList items={commitments} setItems={setCommitments} previousItems={previousCommitments} />

      <SectionHeading title={t('session.quick_todos')} subtitle={t('session.quick_subtitle')} />
      <CheckList items={todos} setItems={setTodos} placeholder={t('session.add_todo')} toggleable />

      <SectionHeading title={t('session.moments')} subtitle={t('session.moments_subtitle')} />
      <MomentList items={moments} setItems={setMoments} />
    </div>
  );
}

function PaneHeader({ eyebrow, title, subtitle, tone = 'normal' }) {
  const { c, fonts } = useLW();
  return (
    <div style={{ marginBottom: 24 }}>
      <div style={{
        fontFamily: fonts.body, fontSize: 11, fontWeight: 700, letterSpacing: '0.14em',
        textTransform: 'uppercase', color: tone === 'private' ? c.flame : c.accent, marginBottom: 8,
      }}>
        {eyebrow}
      </div>
      <h2 style={{
        fontFamily: fonts.display, fontSize: 24, fontWeight: 600,
        margin: 0, marginBottom: 4, color: c.textPrimary, letterSpacing: '-0.01em',
      }}>{title}</h2>
      {subtitle && (
        <p style={{ fontFamily: fonts.display, fontStyle: 'italic', fontSize: 14, color: c.textSecondary, margin: 0 }}>
          {subtitle}
        </p>
      )}
    </div>
  );
}

function SectionHeading({ title, subtitle }) {
  const { c, fonts } = useLW();
  return (
    <div style={{ marginTop: 28, marginBottom: 12, display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 8 }}>
      <h3 style={{ fontFamily: fonts.display, fontSize: 17, fontWeight: 600, color: c.textPrimary, margin: 0, letterSpacing: '-0.005em' }}>
        {title}
      </h3>
      {subtitle && (
        <span style={{ fontFamily: fonts.body, fontSize: 12, color: c.textTertiary, fontStyle: 'italic' }}>
          {subtitle}
        </span>
      )}
    </div>
  );
}

// ─── Live wheel — both can update sphere scores ────────────────
function SessionWheel({ scores, entryScores, setScores, client }) {
  const { c, fonts } = useLW();
  const { isMobile } = useViewport();
  const size = isMobile ? 240 : 280;
  const cx = size / 2, cy = size / 2, rMax = size * 0.42;
  const [hovered, setHovered] = useState(null);

  const updateScore = (i, delta) => {
    setScores(prev => prev.map((v, j) => j === i ? Math.max(0, Math.min(10, v + delta)) : v));
  };

  return (
    <div style={{
      padding: 18, borderRadius: 14,
      background: c.card, border: `1px solid ${c.borderSubtle}`,
      display: 'grid', gridTemplateColumns: isMobile ? '1fr' : `${size + 28}px 1fr`, gap: 24, alignItems: 'center',
    }}>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
          {LWDATA.SPHERE_KEYS.map((sk, i) => {
            const a0 = (i / 8) * Math.PI * 2 - Math.PI / 2;
            const a1 = ((i + 1) / 8) * Math.PI * 2 - Math.PI / 2;
            const score = scores[i];
            const entry = entryScores[i];
            return (
              <g key={i}
                onMouseEnter={() => setHovered(i)}
                onMouseLeave={() => setHovered(null)}
                style={{ cursor: 'pointer' }}>
                {Array.from({ length: 10 }).map((_, lvl) => {
                  const filled = lvl < score;
                  const r0 = (lvl / 10) * rMax;
                  const r1 = ((lvl + 1) / 10) * rMax;
                  const p0 = `${cx + Math.cos(a0) * r0} ${cy + Math.sin(a0) * r0}`;
                  const p1 = `${cx + Math.cos(a0) * r1} ${cy + Math.sin(a0) * r1}`;
                  const p2 = `${cx + Math.cos(a1) * r1} ${cy + Math.sin(a1) * r1}`;
                  const p3 = `${cx + Math.cos(a1) * r0} ${cy + Math.sin(a1) * r0}`;
                  return (
                    <path key={lvl}
                      d={`M${p0} L${p1} A${r1} ${r1} 0 0 1 ${p2} L${p3} A${r0} ${r0} 0 0 0 ${p0} Z`}
                      fill={c.spheres[sk]}
                      opacity={filled ? 0.55 + (lvl / 10) * 0.35 : 0.05}
                      stroke={hovered === i ? c.accent : 'none'}
                      strokeWidth={hovered === i ? 1 : 0}
                    />
                  );
                })}
                {/* entry-score dashed ring */}
                {entry !== score && (
                  <circle cx={cx} cy={cy} r={(entry / 10) * rMax}
                    fill="none" stroke={c.textTertiary} strokeOpacity={0.4}
                    strokeWidth={1} strokeDasharray="2 3"
                    style={{ pointerEvents: 'none' }}
                  />
                )}
              </g>
            );
          })}
          <circle cx={cx} cy={cy} r={rMax * 0.18} fill={c.bg} stroke={c.borderSubtle} />
          <text x={cx} y={cy + 5} textAnchor="middle" fontFamily={fonts.display} fontSize={20} fontWeight={600} fill={c.textPrimary} fontStyle="italic">
            {(scores.reduce((s, v) => s + v, 0) / 8).toFixed(1)}
          </text>
        </svg>
      </div>

      <div>
        <div style={{ fontFamily: fonts.body, fontSize: 11, fontWeight: 700, letterSpacing: '0.10em', textTransform: 'uppercase', color: c.textTertiary, marginBottom: 10 }}>
          {window.LWLang ? window.LWLang.t('session.live_sphere') : 'Live sphere scores'}
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 4 }}>
          {LWDATA.SPHERE_KEYS.map((sk, i) => {
            const delta = scores[i] - entryScores[i];
            return (
              <div key={sk} style={{
                display: 'flex', alignItems: 'center', gap: 8,
                padding: '5px 8px', borderRadius: 7,
                background: hovered === i ? c.bgSubtle : 'transparent',
                transition: 'background 100ms',
              }}
                onMouseEnter={() => setHovered(i)}
                onMouseLeave={() => setHovered(null)}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: c.spheres[sk], flexShrink: 0 }} />
                <span style={{ fontFamily: fonts.body, fontSize: 12, color: c.textPrimary, flex: 1, fontWeight: 500 }}>
                  {(window.LWLang && window.LWLang.sphereNames ? window.LWLang.sphereNames() : LWDATA.SPHERE_NAMES)[i]}
                </span>
                <button onClick={() => updateScore(i, -1)} style={stepBtnStyle(c, fonts)}>−</button>
                <span style={{ fontFamily: fonts.app, fontSize: 13, fontWeight: 700, color: c.textPrimary, minWidth: 20, textAlign: 'center', fontVariantNumeric: 'tabular-nums' }}>
                  {fmtScore(scores[i])}
                </span>
                <button onClick={() => updateScore(i, +1)} style={stepBtnStyle(c, fonts)}>+</button>
                {delta !== 0 ? (
                  <span style={{
                    fontFamily: fonts.app, fontSize: 11, fontWeight: 700,
                    color: delta > 0 ? c.accent : c.flame, minWidth: 22, textAlign: 'right',
                  }}>
                    {delta > 0 ? '+' : ''}{delta}
                  </span>
                ) : (
                  <span style={{ minWidth: 22 }} aria-hidden />
                )}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

function stepBtnStyle(c, fonts) {
  return {
    width: 22, height: 22, borderRadius: 5,
    border: `1px solid ${c.borderSubtle}`, background: 'transparent', cursor: 'pointer',
    color: c.textSecondary, fontFamily: fonts.app, fontSize: 14, fontWeight: 700,
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: 0,
  };
}

// ─── CheckList (agenda) ────────────────────────────────────────
function CheckList({ items, setItems, placeholder, toggleable }) {
  const { c, fonts } = useLW();
  const [draft, setDraft] = useState('');
  const toggle = (id) => setItems(items.map(i => i.id === id ? { ...i, done: !i.done } : i));
  const remove = (id) => setItems(items.filter(i => i.id !== id));
  const add = () => {
    if (!draft.trim()) return;
    setItems([...items, { id: Date.now(), text: draft.trim(), done: false }]);
    setDraft('');
  };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
      {items.map(it => (
        <div key={it.id} style={{
          display: 'flex', alignItems: 'center', gap: 10,
          padding: '8px 10px', borderRadius: 8,
          background: it.done ? 'transparent' : c.card, border: `1px solid ${c.borderSubtle}`,
        }}>
          {toggleable ? (
            <button onClick={() => toggle(it.id)} style={{
              width: 18, height: 18, borderRadius: 5,
              border: `1.5px solid ${it.done ? c.accent : c.borderDefault}`,
              background: it.done ? c.accent : 'transparent',
              cursor: 'pointer', flexShrink: 0, padding: 0,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>
              {it.done && (
                <svg width="11" height="11" viewBox="0 0 12 12" fill="none">
                  <path d="M2 6.5 L5 9 L10 3.5" stroke={c.textOnAccent} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
              )}
            </button>
          ) : <span style={{ width: 18 }} />}
          <span style={{
            flex: 1, fontFamily: fonts.body, fontSize: 14,
            color: it.done ? c.textTertiary : c.textPrimary,
            textDecoration: it.done ? 'line-through' : 'none', lineHeight: 1.45,
          }}>
            {it.text}
          </span>
          <button onClick={() => remove(it.id)} style={{
            background: 'none', border: 'none', cursor: 'pointer', color: c.textTertiary,
            fontSize: 16, padding: '0 4px', opacity: 0.6,
          }}>×</button>
        </div>
      ))}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8,
        padding: '8px 10px', borderRadius: 8,
        border: `1px dashed ${c.borderSubtle}`,
      }}>
        <span style={{ width: 18, height: 18, borderRadius: 5, border: `1.5px dashed ${c.borderSubtle}`, flexShrink: 0 }} />
        <input
          type="text" value={draft} onChange={e => setDraft(e.target.value)}
          onKeyDown={e => e.key === 'Enter' && add()}
          placeholder={placeholder}
          style={{
            flex: 1, background: 'transparent', border: 'none', outline: 'none',
            fontFamily: fonts.body, fontSize: 14, color: c.textPrimary,
          }}
        />
      </div>
    </div>
  );
}

// ─── Goals & habits list (rich: deadlines, subtasks, schedule, reminders) ───
function CommitmentList({ items, setItems, previousItems = [] }) {
  const { c, fonts } = useLW();
  const [adding, setAdding] = useState(null); // null | 'goal' | 'habit'
  const [editing, setEditing] = useState(null); // id of expanded card
  const [editingSphere, setEditingSphere] = useState(null);
  const [showHistory, setShowHistory] = useState(false);

  const remove = (id) => setItems(items.filter(i => i.id !== id));
  const update = (id, patch) => setItems(items.map(i => i.id === id ? { ...i, ...patch } : i));
  const setSphere = (id, sphere) => { update(id, { sphere }); setEditingSphere(null); };
  const addItem = (item) => { setItems([...items, { id: Date.now(), ...item }]); setAdding(null); };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {items.map(it => (
        <ItemCard key={it.id} it={it}
          isEditing={editing === it.id}
          isEditingSphere={editingSphere === it.id}
          onToggleEdit={() => setEditing(editing === it.id ? null : it.id)}
          onToggleSphere={() => setEditingSphere(editingSphere === it.id ? null : it.id)}
          onSetSphere={(s) => setSphere(it.id, s)}
          onUpdate={(patch) => update(it.id, patch)}
          onRemove={() => remove(it.id)}
        />
      ))}

      {adding === null ? (
        <div style={{ display: 'flex', gap: 8, marginTop: 2 }}>
          <button onClick={() => setAdding('goal')} style={addBtnStyle(c, fonts)}>
            <span style={{ fontSize: 14 }}>◎</span> {window.LWLang ? window.LWLang.t('session.add_goal') : 'Add a goal'}
          </button>
          <button onClick={() => setAdding('habit')} style={addBtnStyle(c, fonts)}>
            <span style={{ fontSize: 14 }}>↻</span> {window.LWLang ? window.LWLang.t('session.add_habit') : 'Add a habit'}
          </button>
        </div>
      ) : (
        <NewItemForm kind={adding} onCancel={() => setAdding(null)} onSave={addItem} />
      )}

      {previousItems.length > 0 && (
        <div style={{ marginTop: 8 }}>
          <button onClick={() => setShowHistory(!showHistory)} style={{
            background: 'none', border: 'none', cursor: 'pointer',
            color: c.textTertiary, fontFamily: fonts.body, fontSize: 12, fontWeight: 600,
            padding: '6px 0', display: 'inline-flex', alignItems: 'center', gap: 6,
          }}>
            <span style={{ fontSize: 9, transition: 'transform 150ms', display: 'inline-block', transform: showHistory ? 'rotate(90deg)' : 'rotate(0deg)' }}>▸</span>
            From earlier sessions ({previousItems.length})
          </button>
          {showHistory && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 6, opacity: 0.85 }}>
              {previousItems.map(p => {
                const sIdx = LWDATA.SPHERE_KEYS.indexOf(p.sphere);
                const sName = sIdx >= 0 ? (window.LWLang && window.LWLang.sphereNames ? window.LWLang.sphereNames() : LWDATA.SPHERE_NAMES)[sIdx] : null;
                const sColor = c.spheres[p.sphere] || c.accent;
                const statusColor = p.status === 'done' ? c.accent : p.status === 'slipping' ? c.flame : c.textTertiary;
                const statusLabel = p.status === 'done' ? '✓ done' : p.status === 'slipping' ? '⚠ slipping' : 'in progress';
                return (
                  <div key={p.id} style={{
                    padding: '8px 12px', borderRadius: 9,
                    background: 'transparent', border: `1px dashed ${c.borderSubtle}`,
                    borderLeft: `2px solid ${sColor}`,
                    display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap',
                  }}>
                    <span style={{
                      padding: '1px 6px', borderRadius: 4,
                      background: hexToRgba(sColor, 0.12), color: sColor,
                      fontFamily: fonts.body, fontSize: 9, fontWeight: 800, letterSpacing: '0.10em', textTransform: 'uppercase',
                    }}>{p.kind === 'habit' ? '↻' : '◎'} {sName}</span>
                    <span style={{ flex: 1, minWidth: 160, fontFamily: fonts.body, fontSize: 13, color: c.textSecondary }}>
                      {p.text}
                    </span>
                    <span style={{ fontFamily: fonts.body, fontSize: 10, color: statusColor, fontWeight: 700 }}>{statusLabel}</span>
                    <span style={{ fontFamily: fonts.body, fontSize: 10, color: c.textTertiary, fontStyle: 'italic' }}>{p.from}</span>
                    <button onClick={() => setItems([...items, { ...p, id: Date.now(), from: undefined, status: undefined }])} style={{
                      padding: '3px 8px', borderRadius: 6, border: `1px solid ${c.borderSubtle}`,
                      background: 'transparent', cursor: 'pointer', color: c.textSecondary,
                      fontFamily: fonts.app, fontSize: 10, fontWeight: 700,
                    }}>Carry over</button>
                  </div>
                );
              })}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function addBtnStyle(c, fonts) {
  return {
    flex: 1, padding: '10px 14px', borderRadius: 10,
    border: `1px dashed ${c.borderSubtle}`, background: 'transparent', cursor: 'pointer',
    color: c.textSecondary, fontFamily: fonts.body, fontSize: 13, fontWeight: 600,
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
  };
}

function ItemCard({ it, isEditing, isEditingSphere, onToggleEdit, onToggleSphere, onSetSphere, onUpdate, onRemove }) {
  const { c, fonts } = useLW();
  const sphereIdx = LWDATA.SPHERE_KEYS.indexOf(it.sphere);
  const sphereName = sphereIdx >= 0 ? (window.LWLang && window.LWLang.sphereNames ? window.LWLang.sphereNames() : LWDATA.SPHERE_NAMES)[sphereIdx] : null;
  const sColor = c.spheres[it.sphere] || c.accent;
  const isHabit = it.kind === 'habit';

  return (
    <div style={{
      borderRadius: 12, background: c.card,
      border: `1px solid ${c.borderSubtle}`,
      borderLeft: `3px solid ${sColor}`,
      overflow: 'hidden',
    }}>
      {/* Header */}
      <div style={{ padding: '11px 14px', display: 'flex', alignItems: 'flex-start', gap: 10, flexWrap: 'wrap' }}>
        <span style={{
          padding: '2px 7px', borderRadius: 5,
          background: hexToRgba(sColor, 0.14), color: sColor,
          fontFamily: fonts.body, fontSize: 10, fontWeight: 800, letterSpacing: '0.10em', textTransform: 'uppercase',
          flexShrink: 0,
        }}>
          {isHabit ? '↻ habit' : '◎ goal'}
        </span>
        <div style={{ flex: 1, minWidth: 180, fontFamily: fonts.body, fontSize: 14, fontWeight: 600, color: c.textPrimary, lineHeight: 1.4 }}>
          {it.text}
          <ItemMeta it={it} c={c} fonts={fonts} sColor={sColor} />
        </div>
        <button onClick={onToggleSphere} style={{
          padding: '3px 8px 3px 6px', borderRadius: 999,
          background: hexToRgba(sColor, 0.14), border: `1px solid ${hexToRgba(sColor, 0.3)}`,
          color: sColor, fontFamily: fonts.body, fontSize: 11, fontWeight: 700, cursor: 'pointer',
          display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 0,
        }}>
          <span style={{ width: 8, height: 8, borderRadius: '50%', background: sColor }} />
          {sphereName} <span style={{ fontSize: 9, opacity: 0.7 }}>▾</span>
        </button>
        <button onClick={onToggleEdit} style={iconBtn(c)} title="Edit">✎</button>
        <button onClick={onRemove} style={iconBtn(c)} title="Remove">×</button>
      </div>

      {/* Sphere picker */}
      {isEditingSphere && (
        <div style={{ padding: '0 14px 12px', display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          {LWDATA.SPHERE_KEYS.map((sk, i) => (
            <button key={sk} onClick={() => onSetSphere(sk)} style={{
              padding: '4px 10px', borderRadius: 999,
              border: `1px solid ${it.sphere === sk ? c.spheres[sk] : c.borderSubtle}`,
              background: it.sphere === sk ? hexToRgba(c.spheres[sk], 0.15) : 'transparent',
              color: it.sphere === sk ? c.spheres[sk] : c.textSecondary,
              fontFamily: fonts.body, fontSize: 11, fontWeight: 600, cursor: 'pointer',
            }}>{(window.LWLang && window.LWLang.sphereNames ? window.LWLang.sphereNames() : LWDATA.SPHERE_NAMES)[i]}</button>
          ))}
        </div>
      )}

      {/* Subtasks (goals) — always visible if any exist */}
      {!isHabit && it.subtasks && it.subtasks.length > 0 && (
        <div style={{ padding: '0 14px 8px', display: 'flex', flexDirection: 'column', gap: 4 }}>
          {it.subtasks.map(st => (
            <div key={st.id} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 0' }}>
              <button onClick={() => onUpdate({
                subtasks: it.subtasks.map(s => s.id === st.id ? { ...s, done: !s.done } : s)
              })} style={{
                width: 16, height: 16, borderRadius: 4, padding: 0,
                border: `1.5px solid ${st.done ? sColor : c.borderDefault}`,
                background: st.done ? sColor : 'transparent', cursor: 'pointer', flexShrink: 0,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
              }}>
                {st.done && <svg width="9" height="9" viewBox="0 0 12 12"><path d="M2 6.5 L5 9 L10 3.5" stroke="white" strokeWidth="2" fill="none" strokeLinecap="round"/></svg>}
              </button>
              <span style={{
                flex: 1, fontFamily: fonts.body, fontSize: 13,
                color: st.done ? c.textTertiary : c.textSecondary,
                textDecoration: st.done ? 'line-through' : 'none',
              }}>{st.text}</span>
              <button onClick={() => onUpdate({ subtasks: it.subtasks.filter(s => s.id !== st.id) })} style={{
                background: 'none', border: 'none', cursor: 'pointer', color: c.textTertiary, fontSize: 13, opacity: 0.5,
              }}>×</button>
            </div>
          ))}
        </div>
      )}

      {/* Editor */}
      {isEditing && (
        <ItemEditor it={it} c={c} fonts={fonts} sColor={sColor} onUpdate={onUpdate} onClose={onToggleEdit} />
      )}
    </div>
  );
}

function ItemMeta({ it, c, fonts, sColor }) {
  const isHabit = it.kind === 'habit';
  const dayLetters = ['S','M','T','W','T','F','S'];
  return (
    <div style={{ marginTop: 5, display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center', fontFamily: fonts.body, fontSize: 11, color: c.textTertiary, fontWeight: 500 }}>
      {isHabit ? (
        <>
          <span style={{ display: 'inline-flex', gap: 2 }}>
            {dayLetters.map((d, i) => {
              const on = (it.days || []).includes(i);
              return <span key={i} style={{
                width: 16, height: 16, borderRadius: '50%',
                fontSize: 9, fontWeight: 700, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                background: on ? sColor : 'transparent',
                color: on ? c.textOnAccent : c.textTertiary,
                border: `1px solid ${on ? sColor : c.borderSubtle}`,
              }}>{d}</span>;
            })}
          </span>
          {it.time && <span>· {it.time}</span>}
          {it.remind && <span>· 🔔 reminder</span>}
        </>
      ) : (
        <>
          {it.due && <span>📅 due {fmtDue(it.due)}</span>}
          {it.subtasks && it.subtasks.length > 0 && (
            <span>· {it.subtasks.filter(s => s.done).length}/{it.subtasks.length} done</span>
          )}
          {it.remind && <span>· 🔔 reminder</span>}
        </>
      )}
    </div>
  );
}

function fmtDue(d) {
  const date = new Date(d);
  if (isNaN(date)) return d;
  const now = new Date();
  const days = Math.round((date - now) / 86400000);
  const m = date.toLocaleString('en', { month: 'short', day: 'numeric' });
  if (days < 0) return `${m} (overdue)`;
  if (days === 0) return 'today';
  if (days === 1) return 'tomorrow';
  if (days < 14) return `${m} (in ${days}d)`;
  return m;
}

function iconBtn(c) {
  return {
    background: 'none', border: 'none', cursor: 'pointer',
    color: c.textTertiary, fontSize: 14, padding: '2px 6px', flexShrink: 0,
  };
}

function ItemEditor({ it, c, fonts, sColor, onUpdate, onClose }) {
  const isHabit = it.kind === 'habit';
  const [subtaskDraft, setSubtaskDraft] = useState('');
  const dayLetters = ['S','M','T','W','T','F','S'];
  const toggleDay = (i) => {
    const days = it.days || [];
    onUpdate({ days: days.includes(i) ? days.filter(d => d !== i) : [...days, i].sort() });
  };
  const addSubtask = () => {
    if (!subtaskDraft.trim()) return;
    onUpdate({ subtasks: [...(it.subtasks || []), { id: Date.now(), text: subtaskDraft.trim(), done: false }] });
    setSubtaskDraft('');
  };
  const labelStyle = { fontFamily: fonts.body, fontSize: 10, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: c.textTertiary, marginBottom: 5 };
  const inputStyle = {
    padding: '7px 10px', borderRadius: 7, background: c.bgSubtle,
    border: `1px solid ${c.borderSubtle}`, outline: 'none',
    fontFamily: fonts.body, fontSize: 13, color: c.textPrimary,
  };

  return (
    <div style={{ padding: '12px 14px 14px', borderTop: `1px solid ${c.borderSubtle}`, background: c.bgSubtle, display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div>
        <div style={labelStyle}>{isHabit ? 'Habit name' : 'Goal'}</div>
        <input type="text" value={it.text} onChange={e => onUpdate({ text: e.target.value })}
          style={{ ...inputStyle, width: '100%' }} />
      </div>

      {isHabit ? (
        <>
          <div>
            <div style={labelStyle}>Days</div>
            <div style={{ display: 'flex', gap: 6 }}>
              {dayLetters.map((d, i) => {
                const on = (it.days || []).includes(i);
                return <button key={i} onClick={() => toggleDay(i)} style={{
                  width: 32, height: 32, borderRadius: '50%', cursor: 'pointer',
                  background: on ? sColor : 'transparent', color: on ? c.textOnAccent : c.textSecondary,
                  border: `1px solid ${on ? sColor : c.borderDefault}`,
                  fontFamily: fonts.app, fontWeight: 700, fontSize: 12,
                }}>{d}</button>;
              })}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 12 }}>
            <div style={{ flex: 1 }}>
              <div style={labelStyle}>Time</div>
              <input type="time" value={it.time || ''} onChange={e => onUpdate({ time: e.target.value })} style={{ ...inputStyle, width: '100%' }} />
            </div>
            <div style={{ flex: 1, display: 'flex', alignItems: 'flex-end' }}>
              <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontFamily: fonts.body, fontSize: 13, color: c.textPrimary }}>
                <input type="checkbox" checked={!!it.remind} onChange={e => onUpdate({ remind: e.target.checked })} />
                🔔 Send reminder
              </label>
            </div>
          </div>
        </>
      ) : (
        <>
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
            <div style={{ flex: 1, minWidth: 160 }}>
              <div style={labelStyle}>Deadline</div>
              <input type="date" value={it.due || ''} onChange={e => onUpdate({ due: e.target.value })} style={{ ...inputStyle, width: '100%' }} />
            </div>
            <div style={{ flex: 1, minWidth: 160, display: 'flex', alignItems: 'flex-end' }}>
              <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontFamily: fonts.body, fontSize: 13, color: c.textPrimary }}>
                <input type="checkbox" checked={!!it.remind} onChange={e => onUpdate({ remind: e.target.checked })} />
                🔔 Reminder before due
              </label>
            </div>
          </div>
          <div>
            <div style={labelStyle}>Subtasks</div>
            <div style={{ display: 'flex', gap: 8 }}>
              <input type="text" value={subtaskDraft} onChange={e => setSubtaskDraft(e.target.value)}
                onKeyDown={e => e.key === 'Enter' && addSubtask()}
                placeholder="Add a subtask…" style={{ ...inputStyle, flex: 1 }} />
              <button onClick={addSubtask} style={{
                padding: '0 14px', borderRadius: 7, border: 'none', cursor: 'pointer',
                background: c.accent, color: c.textOnAccent, fontFamily: fonts.app, fontSize: 12, fontWeight: 700,
              }}>Add</button>
            </div>
          </div>
        </>
      )}

      <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 4 }}>
        <button onClick={onClose} style={{
          padding: '6px 14px', borderRadius: 7, border: `1px solid ${c.borderDefault}`,
          background: 'transparent', cursor: 'pointer', color: c.textPrimary,
          fontFamily: fonts.app, fontSize: 12, fontWeight: 700,
        }}>Done</button>
      </div>
    </div>
  );
}

function NewItemForm({ kind, onCancel, onSave }) {
  const { c, fonts } = useLW();
  const [text, setText] = useState('');
  const [sphere, setSphere] = useState('career');
  const [due, setDue] = useState('');
  const [days, setDays] = useState([1,2,3,4,5]);
  const [time, setTime] = useState('09:00');
  const [remind, setRemind] = useState(true);
  const [pickSphere, setPickSphere] = useState(false);
  const [subtasks, setSubtasks] = useState([]);
  const [subtaskDraft, setSubtaskDraft] = useState('');
  const isHabit = kind === 'habit';
  const sColor = c.spheres[sphere] || c.accent;
  const dayLetters = ['S','M','T','W','T','F','S'];
  const toggleDay = (i) => setDays(days.includes(i) ? days.filter(d => d !== i) : [...days, i].sort());
  const inputStyle = {
    padding: '8px 12px', borderRadius: 8, background: c.bgSubtle,
    border: `1px solid ${c.borderSubtle}`, outline: 'none',
    fontFamily: fonts.body, fontSize: 13, color: c.textPrimary,
  };
  const labelStyle = { fontFamily: fonts.body, fontSize: 10, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: c.textTertiary, marginBottom: 5 };

  const addSubtask = () => {
    if (!subtaskDraft.trim()) return;
    setSubtasks([...subtasks, { id: Date.now(), text: subtaskDraft.trim(), done: false }]);
    setSubtaskDraft('');
  };
  const save = () => {
    if (!text.trim()) return;
    if (isHabit) onSave({ kind: 'habit', text: text.trim(), sphere, days, time, remind });
    else onSave({ kind: 'goal', text: text.trim(), sphere, due, remind, subtasks });
  };

  return (
    <div style={{
      borderRadius: 12, background: c.card,
      border: `1px solid ${hexToRgba(sColor, 0.3)}`, borderLeft: `3px solid ${sColor}`,
      padding: 14, display: 'flex', flexDirection: 'column', gap: 12,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <span style={{
          padding: '3px 8px', borderRadius: 5, background: hexToRgba(sColor, 0.14), color: sColor,
          fontFamily: fonts.body, fontSize: 10, fontWeight: 800, letterSpacing: '0.10em', textTransform: 'uppercase',
        }}>{isHabit ? '↻ new habit' : '◎ new goal'}</span>
        <button onClick={() => setPickSphere(!pickSphere)} style={{
          padding: '3px 8px 3px 6px', borderRadius: 999,
          background: hexToRgba(sColor, 0.14), border: `1px solid ${hexToRgba(sColor, 0.3)}`,
          color: sColor, fontFamily: fonts.body, fontSize: 11, fontWeight: 700, cursor: 'pointer',
          display: 'inline-flex', alignItems: 'center', gap: 4,
        }}>
          <span style={{ width: 8, height: 8, borderRadius: '50%', background: sColor }} />
          {(window.LWLang && window.LWLang.sphereNames ? window.LWLang.sphereNames() : LWDATA.SPHERE_NAMES)[LWDATA.SPHERE_KEYS.indexOf(sphere)]} <span style={{ fontSize: 9, opacity: 0.7 }}>▾</span>
        </button>
      </div>
      {pickSphere && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          {LWDATA.SPHERE_KEYS.map((sk, i) => (
            <button key={sk} onClick={() => { setSphere(sk); setPickSphere(false); }} style={{
              padding: '4px 10px', borderRadius: 999,
              border: `1px solid ${sphere === sk ? c.spheres[sk] : c.borderSubtle}`,
              background: sphere === sk ? hexToRgba(c.spheres[sk], 0.15) : 'transparent',
              color: sphere === sk ? c.spheres[sk] : c.textSecondary,
              fontFamily: fonts.body, fontSize: 11, fontWeight: 600, cursor: 'pointer',
            }}>{(window.LWLang && window.LWLang.sphereNames ? window.LWLang.sphereNames() : LWDATA.SPHERE_NAMES)[i]}</button>
          ))}
        </div>
      )}
      <div>
        <div style={labelStyle}>{isHabit ? 'What habit?' : 'What goal?'}</div>
        <input autoFocus type="text" value={text} onChange={e => setText(e.target.value)}
          onKeyDown={e => e.key === 'Enter' && save()}
          placeholder={isHabit ? "e.g. 20-min walk after lunch" : "e.g. Decide on the team-lead role"}
          style={{ ...inputStyle, width: '100%', fontSize: 14 }} />
      </div>
      {isHabit ? (
        <>
          <div>
            <div style={labelStyle}>Days</div>
            <div style={{ display: 'flex', gap: 6 }}>
              {dayLetters.map((d, i) => {
                const on = days.includes(i);
                return <button key={i} onClick={() => toggleDay(i)} style={{
                  width: 32, height: 32, borderRadius: '50%', cursor: 'pointer',
                  background: on ? sColor : 'transparent', color: on ? c.textOnAccent : c.textSecondary,
                  border: `1px solid ${on ? sColor : c.borderDefault}`,
                  fontFamily: fonts.app, fontWeight: 700, fontSize: 12,
                }}>{d}</button>;
              })}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
            <div style={{ flex: 1, minWidth: 140 }}>
              <div style={labelStyle}>Time</div>
              <input type="time" value={time} onChange={e => setTime(e.target.value)} style={{ ...inputStyle, width: '100%' }} />
            </div>
            <div style={{ flex: 1, minWidth: 140, display: 'flex', alignItems: 'flex-end' }}>
              <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontFamily: fonts.body, fontSize: 13, color: c.textPrimary }}>
                <input type="checkbox" checked={remind} onChange={e => setRemind(e.target.checked)} />
                🔔 Send reminder
              </label>
            </div>
          </div>
        </>
      ) : (
        <>
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
            <div style={{ flex: 1, minWidth: 160 }}>
              <div style={labelStyle}>Deadline</div>
              <input type="date" value={due} onChange={e => setDue(e.target.value)} style={{ ...inputStyle, width: '100%' }} />
            </div>
            <div style={{ flex: 1, minWidth: 160, display: 'flex', alignItems: 'flex-end' }}>
              <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontFamily: fonts.body, fontSize: 13, color: c.textPrimary }}>
                <input type="checkbox" checked={remind} onChange={e => setRemind(e.target.checked)} />
                🔔 Reminder before due
              </label>
            </div>
          </div>
          <div>
            <div style={labelStyle}>Subtasks (optional)</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 6 }}>
              {subtasks.map(st => (
                <div key={st.id} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0' }}>
                  <span style={{ width: 14, height: 14, borderRadius: 4, border: `1.5px solid ${c.borderDefault}`, flexShrink: 0 }} />
                  <span style={{ flex: 1, fontFamily: fonts.body, fontSize: 13, color: c.textSecondary }}>{st.text}</span>
                  <button onClick={() => setSubtasks(subtasks.filter(s => s.id !== st.id))} style={{
                    background: 'none', border: 'none', cursor: 'pointer', color: c.textTertiary, fontSize: 13, opacity: 0.5,
                  }}>×</button>
                </div>
              ))}
            </div>
            <div style={{ display: 'flex', gap: 8 }}>
              <input type="text" value={subtaskDraft} onChange={e => setSubtaskDraft(e.target.value)}
                onKeyDown={e => e.key === 'Enter' && (e.preventDefault(), addSubtask())}
                placeholder="Add a subtask…" style={{ ...inputStyle, flex: 1 }} />
              <button onClick={addSubtask} style={{
                padding: '0 14px', borderRadius: 7, border: `1px solid ${c.borderDefault}`, background: 'transparent',
                cursor: 'pointer', color: c.textSecondary, fontFamily: fonts.app, fontSize: 12, fontWeight: 700,
              }}>+ Add</button>
            </div>
          </div>
        </>
      )}
      <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
        <button onClick={onCancel} style={{
          padding: '7px 14px', borderRadius: 7, border: `1px solid ${c.borderDefault}`,
          background: 'transparent', cursor: 'pointer', color: c.textSecondary,
          fontFamily: fonts.app, fontSize: 12, fontWeight: 700,
        }}>Cancel</button>
        <button onClick={save} style={{
          padding: '7px 14px', borderRadius: 7, border: 'none',
          background: c.accent, cursor: 'pointer', color: c.textOnAccent,
          fontFamily: fonts.app, fontSize: 12, fontWeight: 700,
        }}>Add {isHabit ? 'habit' : 'goal'}{isHabit ? '. Subtasks added inline.' : ''}</button>
      </div>
    </div>
  );
}

// ─── Moments to remember ───────────────────────────────────────
function MomentList({ items, setItems }) {
  const { c, fonts } = useLW();
  const [draft, setDraft] = useState('');
  const remove = (id) => setItems(items.filter(i => i.id !== id));
  const add = () => {
    if (!draft.trim()) return;
    setItems([...items, { id: Date.now(), text: draft.trim() }]);
    setDraft('');
  };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {items.map(it => (
        <div key={it.id} style={{
          padding: '14px 18px', borderRadius: 12,
          background: hexToRgba(c.accent, 0.06), border: `1px solid ${hexToRgba(c.accent, 0.20)}`,
          borderLeft: `3px solid ${c.accent}`,
          display: 'flex', alignItems: 'flex-start', gap: 12,
        }}>
          <span style={{ fontFamily: fonts.display, fontSize: 28, color: c.accent, lineHeight: 0.7, opacity: 0.7 }}>"</span>
          <span style={{ flex: 1, fontFamily: fonts.display, fontStyle: 'italic', fontSize: 16, lineHeight: 1.45, color: c.textPrimary }}>
            {it.text}
          </span>
          <button onClick={() => remove(it.id)} style={{
            background: 'none', border: 'none', cursor: 'pointer', color: c.textTertiary, fontSize: 16, padding: '0 4px',
          }}>×</button>
        </div>
      ))}
      <textarea
        value={draft} onChange={e => setDraft(e.target.value)}
        onKeyDown={e => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) add(); }}
        placeholder="A line worth saving — paste a quote she said, or a moment of clarity. ⌘+Enter to save."
        rows={2}
        style={{
          padding: '12px 16px', borderRadius: 12, resize: 'vertical',
          background: 'transparent', border: `1px dashed ${c.borderSubtle}`,
          fontFamily: fonts.display, fontStyle: 'italic', fontSize: 15,
          color: c.textPrimary, outline: 'none', lineHeight: 1.5,
        }}
      />
    </div>
  );
}

// ─── PRIVATE PANE ──────────────────────────────────────────────
function PrivatePane({ privateNotes, setPrivateNotes, themes, setThemes, followups, setFollowups }) {
  const { c, fonts, t } = useLW();
  // We don't have client name in this scope; copy uses generic "they"-style.
  const subtitle = t('session.private_subtitle', { name: t('client.breadcrumb_today') === 'Сегодня' ? 'Клиент' : 'Client' });
  return (
    <div style={{ background: c.bgSubtle, padding: 24, overflowY: 'auto', minHeight: 0, position: 'relative' }}>
      <div style={{
        position: 'absolute', top: 0, left: 0, right: 0, height: 3,
        background: `repeating-linear-gradient(45deg, ${hexToRgba(c.flame, 0.5)} 0 8px, transparent 8px 16px)`,
      }} />

      <PaneHeader
        eyebrow={<><span style={{ marginRight: 8, fontSize: 11 }}>🔒</span>{t('session.private_eyebrow')}</>}
        title={t('session.private_title')}
        subtitle={subtitle}
        tone="private"
      />

      <SectionHeading title={t('session.live_observations')} subtitle={t('session.auto_saves')} />
      <textarea
        value={privateNotes} onChange={e => setPrivateNotes(e.target.value)}
        rows={5}
        placeholder={t('session.private_title')}
        style={{
          width: '100%', padding: '14px 16px', borderRadius: 10, resize: 'vertical',
          background: c.bg, border: `1px solid ${c.borderSubtle}`, outline: 'none',
          fontFamily: fonts.body, fontSize: 14, lineHeight: 1.6, color: c.textPrimary,
          fontStyle: 'normal',
        }}
      />

      <SectionHeading title={t('session.themes')} subtitle={t('session.themes_hint')} />
      <ThemeChips themes={themes} setThemes={setThemes} />

      <SectionHeading title={t('session.followups')} subtitle={t('session.followups_hint')} />
      <FollowupList items={followups} setItems={setFollowups} />

      <div style={{
        marginTop: 32, padding: '14px 16px', borderRadius: 10,
        background: hexToRgba(c.flame, 0.06),
        border: `1px solid ${hexToRgba(c.flame, 0.2)}`,
        display: 'flex', alignItems: 'flex-start', gap: 10,
      }}>
        <span style={{ fontSize: 14, color: c.flame, marginTop: 1 }}>🔒</span>
        <div style={{ fontFamily: fonts.body, fontSize: 12, lineHeight: 1.5, color: c.textSecondary }}>
          {t('session.private_lock')}
        </div>
      </div>
    </div>
  );
}

function ThemeChips({ themes, setThemes }) {
  const { c, fonts } = useLW();
  const [draft, setDraft] = useState('');
  const remove = (idx) => setThemes(themes.filter((_, i) => i !== idx));
  const add = () => {
    const t = draft.trim();
    if (!t || themes.includes(t)) return;
    setThemes([...themes, t]); setDraft('');
  };
  return (
    <div style={{
      display: 'flex', flexWrap: 'wrap', gap: 6,
      padding: '10px 12px', borderRadius: 10,
      background: c.bg, border: `1px solid ${c.borderSubtle}`, minHeight: 44, alignItems: 'center',
    }}>
      {themes.map((t, i) => (
        <span key={i} style={{
          display: 'inline-flex', alignItems: 'center', gap: 6,
          padding: '4px 10px 4px 12px', borderRadius: 999,
          background: hexToRgba(c.accent, 0.10), border: `1px solid ${hexToRgba(c.accent, 0.25)}`,
          fontFamily: fonts.body, fontSize: 12, fontWeight: 600, color: c.accent,
        }}>
          {t}
          <button onClick={() => remove(i)} style={{
            background: 'none', border: 'none', cursor: 'pointer',
            color: c.accent, fontSize: 12, padding: 0, opacity: 0.6, lineHeight: 1,
          }}>×</button>
        </span>
      ))}
      <input
        type="text" value={draft} onChange={e => setDraft(e.target.value)}
        onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); add(); } }}
        placeholder={themes.length ? 'add another…' : 'a one- or two-word handle on what you\'re hearing'}
        style={{
          flex: 1, minWidth: 140, background: 'transparent', border: 'none', outline: 'none',
          fontFamily: fonts.body, fontSize: 13, color: c.textPrimary,
        }}
      />
    </div>
  );
}

function FollowupList({ items, setItems }) {
  const { c, fonts } = useLW();
  const [draft, setDraft] = useState('');
  const remove = (id) => setItems(items.filter(i => i.id !== id));
  const add = () => {
    if (!draft.trim()) return;
    setItems([...items, { id: Date.now(), text: draft.trim() }]); setDraft('');
  };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {items.map(it => (
        <div key={it.id} style={{
          padding: '10px 12px', borderRadius: 9,
          background: c.bg, border: `1px solid ${c.borderSubtle}`,
          display: 'flex', alignItems: 'flex-start', gap: 10,
        }}>
          <span style={{ color: c.flame, fontSize: 14, marginTop: 1 }}>↻</span>
          <span style={{ flex: 1, fontFamily: fonts.body, fontSize: 13, color: c.textPrimary, lineHeight: 1.5 }}>
            {it.text}
          </span>
          <button onClick={() => remove(it.id)} style={{
            background: 'none', border: 'none', cursor: 'pointer',
            color: c.textTertiary, fontSize: 14, padding: '0 4px',
          }}>×</button>
        </div>
      ))}
      <input
        type="text" value={draft} onChange={e => setDraft(e.target.value)}
        onKeyDown={e => e.key === 'Enter' && add()}
        placeholder="something to circle back on…"
        style={{
          padding: '10px 12px', borderRadius: 9,
          background: 'transparent', border: `1px dashed ${c.borderSubtle}`, outline: 'none',
          fontFamily: fonts.body, fontSize: 13, color: c.textPrimary,
        }}
      />
    </div>
  );
}

// ─── End-session sheet ─────────────────────────────────────────
function EndSessionSheet({ onClose, client, commitments, moments, followups, elapsedSec, fmtTime, onConfirm }) {
  const { c, fonts } = useLW();
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 100,
      background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(8px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 520, background: c.bg, borderRadius: 16,
        boxShadow: c.shadowModal, border: `1px solid ${c.borderDefault}`,
        padding: 28,
      }}>
        <div style={{ fontFamily: fonts.body, fontSize: 11, fontWeight: 700, letterSpacing: '0.14em', textTransform: 'uppercase', color: c.accent, marginBottom: 8 }}>
          End session · {fmtTime(elapsedSec)}
        </div>
        <h2 style={{ fontFamily: fonts.display, fontSize: 24, fontWeight: 600, color: c.textPrimary, margin: 0, marginBottom: 6, letterSpacing: '-0.01em' }}>
          Closing out with <span style={{ fontStyle: 'italic' }}>{client.name.split(' ')[0]}</span>
        </h2>
        <p style={{ fontFamily: fonts.display, fontStyle: 'italic', fontSize: 14, color: c.textSecondary, margin: 0, marginBottom: 22, lineHeight: 1.5 }}>
          Here's what gets saved where. You can edit any of it after.
        </p>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginBottom: 24 }}>
          <SaveRow icon="◐" label={`${commitments.length} commitments`} dest={`→ ${client.name.split(' ')[0]}'s tasks`} c={c} fonts={fonts} tone="shared" />
          <SaveRow icon='"' label={`${moments.length} moments`} dest={`→ ${client.name.split(' ')[0]}'s journal`} c={c} fonts={fonts} tone="shared" />
          <SaveRow icon="↻" label={`${followups.length} followups`} dest="→ next prep brief" c={c} fonts={fonts} tone="private" />
          <SaveRow icon="🔒" label="Private notes & themes" dest="→ your session log" c={c} fonts={fonts} tone="private" />
        </div>

        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
          <Button variant="ghost" size="md" onClick={onClose}>Keep going</Button>
          <Button variant="primary" size="md" onClick={onConfirm}>Save & close</Button>
        </div>
      </div>
    </div>
  );
}

function SaveRow({ icon, label, dest, c, fonts, tone }) {
  const accent = tone === 'private' ? c.flame : c.accent;
  return (
    <div style={{
      padding: '10px 14px', borderRadius: 10,
      background: hexToRgba(accent, 0.06), border: `1px solid ${hexToRgba(accent, 0.18)}`,
      display: 'flex', alignItems: 'center', gap: 12,
    }}>
      <span style={{
        width: 28, height: 28, borderRadius: 7,
        background: hexToRgba(accent, 0.15), color: accent,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        fontFamily: fonts.app, fontWeight: 700, fontSize: 14,
      }}>{icon}</span>
      <span style={{ flex: 1, fontFamily: fonts.body, fontSize: 14, fontWeight: 600, color: c.textPrimary }}>
        {label}
      </span>
      <span style={{ fontFamily: fonts.body, fontSize: 12, color: c.textTertiary, fontStyle: 'italic' }}>
        {dest}
      </span>
    </div>
  );
}

window.LWSession = LWSession;
