// ─── Inbox · Tasks · Debrief screens ───
// Tasks: flat list across all projects, breadcrumbed Project › Milestone › Task,
// click to open TaskEditor. Inbox: capture with priority dropdown, paste images,
// convert ideas into tasks on existing project milestones. Debrief: layout-fixed
// metrics + done/drift columns + AI pattern card.

const { useState, useEffect, useRef } = React;
const useLFState = window.useLFState;
const MODES = window.MODES;
const modeOf = window.modeOf;
const taskProgress = window.taskProgress;
const milestoneProgress = window.milestoneProgress;
const projectProgress = window.projectProgress;
const updateTaskById = window.updateTaskById;

// ─── INBOX ───────────────────────────────────────────────────────────────────
const TYPE_COLOR = { task: '#3B82F6', idea: '#8B5CF6', reference: '#22C55E', event: '#F59E0B', unassigned: '#6B7280' };
// Auto-resize a textarea to its content
function autoGrow(el) {
  if (!el) return;
  el.style.height = 'auto';
  el.style.height = Math.max(20, el.scrollHeight) + 'px';
}

// ─── CALENDAR HELPERS ────────────────────────────────────────────────────────
// Build a Google Calendar deep-link that opens calendar.google.com pre-filled.
// Format: YYYYMMDDTHHMMSSZ (UTC, drop milliseconds). Google requires UTC.
function _googleDate(iso) {
  return new Date(iso).toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
}
function googleCalendarUrl(ev) {
  const params = new URLSearchParams({
    action: 'TEMPLATE',
    text: ev.text || 'Event',
    details: ev.description || '',
    location: ev.location || '',
  });
  // Only set dates when we have a start; otherwise Google opens with today
  // at the current rounded hour and the user picks the time in Google's UI.
  if (ev.start) {
    const start = _googleDate(ev.start);
    const end = _googleDate(ev.end || new Date(new Date(ev.start).getTime() + 60 * 60 * 1000).toISOString());
    params.set('dates', `${start}/${end}`);
  }
  if (ev.attendees?.length) params.set('add', ev.attendees.filter((a) => a.includes('@')).join(','));
  return `https://calendar.google.com/calendar/render?${params.toString()}`;
}
// Build an .ics blob URL that opens in the OS default calendar app (Outlook
// on Windows when set as default for .ics). Same .ics also imports cleanly
// into Outlook Web by drag-and-drop or File → Open.
function _icsDate(iso) {
  return new Date(iso).toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
}
function _icsEscape(s) {
  return (s || '').replace(/\\/g, '\\\\').replace(/;/g, '\\;').replace(/,/g, '\\,').replace(/\n/g, '\\n');
}
function buildIcs(ev) {
  const uid = `lf-${ev.id || Date.now()}@laminarflow`;
  const now = _icsDate(new Date().toISOString());
  // .ics requires DTSTART. If the capture doesn't have one yet, default to
  // the next full hour today — user adjusts when the file opens in Outlook.
  const fallbackStart = (() => {
    const d = new Date();
    d.setMinutes(0, 0, 0); d.setHours(d.getHours() + 1);
    return d.toISOString();
  })();
  const startIso = ev.start || fallbackStart;
  const endIso = ev.end || new Date(new Date(startIso).getTime() + 60 * 60 * 1000).toISOString();
  const start = _icsDate(startIso);
  const end = _icsDate(endIso);
  const lines = [
    'BEGIN:VCALENDAR',
    'VERSION:2.0',
    'PRODID:-//Laminar Flow//Event//EN',
    'CALSCALE:GREGORIAN',
    'METHOD:PUBLISH',
    'BEGIN:VEVENT',
    `UID:${uid}`,
    `DTSTAMP:${now}`,
    `DTSTART:${start}`,
    `DTEND:${end}`,
    `SUMMARY:${_icsEscape(ev.text || 'Event')}`,
  ];
  if (ev.description) lines.push(`DESCRIPTION:${_icsEscape(ev.description)}`);
  if (ev.location) lines.push(`LOCATION:${_icsEscape(ev.location)}`);
  (ev.attendees || []).filter((a) => a && a.includes('@')).forEach((a) => {
    lines.push(`ATTENDEE;CN=${_icsEscape(a)};RSVP=TRUE:mailto:${a}`);
  });
  lines.push('END:VEVENT', 'END:VCALENDAR');
  return lines.join('\r\n');
}
function downloadIcs(ev) {
  const blob = new Blob([buildIcs(ev)], { type: 'text/calendar;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `${(ev.text || 'event').replace(/[^a-z0-9_-]+/gi, '_').slice(0, 60)}.ics`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  setTimeout(() => URL.revokeObjectURL(url), 5000);
}
// Format an event's date range for display
function formatEventTime(ev) {
  if (!ev.start) return '';
  const s = new Date(ev.start);
  const e = ev.end ? new Date(ev.end) : null;
  const dateOpts = { weekday: 'short', month: 'short', day: 'numeric' };
  const timeOpts = { hour: 'numeric', minute: '2-digit', hour12: true };
  const sDate = s.toLocaleDateString(undefined, dateOpts);
  const sTime = s.toLocaleTimeString(undefined, timeOpts);
  if (!e) return `${sDate} · ${sTime}`;
  const eTime = e.toLocaleTimeString(undefined, timeOpts);
  const sameDay = s.toDateString() === e.toDateString();
  return sameDay ? `${sDate} · ${sTime} – ${eTime}` : `${sDate} ${sTime} → ${e.toLocaleDateString(undefined, dateOpts)} ${eTime}`;
}
const URG_COLOR = { high: '#EF4444', medium: '#F59E0B', low: '#22C55E' };

// Convert a capture into a task OR sub-task, with the ability to create new
// project / milestone / parent-task inline if the right home doesn't exist
// yet. Single Add button handles the whole chain.
const NEW_SENTINEL = '__new__';
function _uid() { return Math.random().toString(36).slice(2, 9); }

function ConvertToTaskModal({ item, onClose, onConverted }) {
  const [projects] = useLFState('projects', []);

  // Strip HTML so the label doesn't end up with <b>...</b> markup when the
  // source capture was edited in the rich-text editor.
  const stripHtml = (s) => {
    if (!s) return '';
    if (!/<[^>]+>/.test(s)) return s;
    const div = document.createElement('div');
    div.innerHTML = s;
    return (div.textContent || div.innerText || '').trim();
  };
  // Top: the editable capture name — becomes the label of whichever level
  // the user decides to land on.
  const [entryName, setEntryName] = useState(stripHtml(item.text || ''));

  // LEVEL = where the capture lands. 'project' | 'milestone' | 'task' | 'subtask'
  // Defaults to 'task' (the common case). Each level's parents are only shown
  // when the level requires them (progressive disclosure).
  const [level, setLevel] = useState('task');

  // Parent selectors. Each can be an existing id or NEW_SENTINEL.
  const firstProjectId = projects[0]?.id || NEW_SENTINEL;
  const [pid, setPid] = useState(firstProjectId);
  const currentProject = projects.find((p) => p.id === pid);
  const milestonesForProject = currentProject?.branches?.filter((b) => !b.archived) || [];
  const [mid, setMid] = useState(milestonesForProject[0]?.id || NEW_SENTINEL);
  const currentMilestone = milestonesForProject.find((b) => b.id === mid);
  const tasksForMilestone = currentMilestone?.leaves || [];
  const [tid, setTid] = useState(tasksForMilestone[0]?.id || NEW_SENTINEL);

  // Names for new entities the user is creating along the way.
  // For levels ABOVE the landing level, these are intermediate parents that
  // need their own names. For the landing level, entryName at the top is used.
  const [newProjectName, setNewProjectName] = useState('');
  const [newMilestoneName, setNewMilestoneName] = useState('');
  const [newTaskLabel, setNewTaskLabel] = useState('');

  // Keep child selections valid as parents change
  useEffect(() => {
    const ms = projects.find((p) => p.id === pid)?.branches?.filter((b) => !b.archived) || [];
    if (pid === NEW_SENTINEL) setMid(NEW_SENTINEL);
    else if (!ms.find((b) => b.id === mid)) setMid(ms[0]?.id || NEW_SENTINEL);
  }, [pid]);
  useEffect(() => {
    if (mid === NEW_SENTINEL) { setTid(NEW_SENTINEL); return; }
    const ts = projects.find((p) => p.id === pid)?.branches?.find((b) => b.id === mid)?.leaves || [];
    if (!ts.find((t) => t.id === tid)) setTid(ts[0]?.id || NEW_SENTINEL);
  }, [mid]);

  // Which parent fields does the chosen level need?
  // - project: none
  // - milestone: project
  // - task: project + milestone
  // - subtask: project + milestone + task
  const needsProjectField = level !== 'project';
  const needsMilestoneField = level === 'task' || level === 'subtask';
  const needsTaskField = level === 'subtask';

  // Per-field "need to type a new name" flags.
  // For PARENT levels (above the landing level), a "+ new" pick requires
  // typing the parent's name. For the landing level itself, the capture's
  // entryName at top is used — no separate name input needed.
  const needsNewProjectName = needsProjectField && pid === NEW_SENTINEL && level !== 'project';
  const needsNewMilestoneName = needsMilestoneField && (pid === NEW_SENTINEL || mid === NEW_SENTINEL) && level !== 'milestone';
  const needsNewTaskName = needsTaskField && (pid === NEW_SENTINEL || mid === NEW_SENTINEL || tid === NEW_SENTINEL) && level !== 'task';

  const canCommit = !!entryName.trim()
    && (!needsNewProjectName || newProjectName.trim())
    && (!needsNewMilestoneName || newMilestoneName.trim())
    && (!needsNewTaskName || newTaskLabel.trim());

  const commit = () => {
    if (!canCommit) return;
    const entry = entryName.trim();
    window.LF.update('projects', (ps = []) => {
      let next = [...ps];
      let workingProjectId = pid;
      let workingMilestoneId = mid;
      let workingTaskId = tid;

      // LEVEL = project — capture becomes a new top-level project
      if (level === 'project') {
        workingProjectId = _uid();
        next = [...next, { id: workingProjectId, name: entry, color: '#3B82F6', status: 'active', createdAt: Date.now(), branches: [] }];
        return next;
      }

      // For all deeper levels we need a project to live under
      if (pid === NEW_SENTINEL) {
        workingProjectId = _uid();
        next = [...next, { id: workingProjectId, name: newProjectName.trim(), color: '#3B82F6', status: 'active', createdAt: Date.now(), branches: [] }];
      }

      // Carry the capture's dueDate into whichever entity it becomes
      const carryDate = item.dueDate || null;

      if (level === 'milestone') {
        // Capture becomes a milestone under workingProjectId
        workingMilestoneId = _uid();
        const newMs = { id: workingMilestoneId, label: entry, color: '#3B82F6', leaves: [] };
        if (carryDate) newMs.dueDate = carryDate;
        next = next.map((p) => p.id === workingProjectId ? ({ ...p, branches: [...(p.branches || []), newMs] }) : p);
        return next;
      }

      // For deeper levels (task, subtask) we need a milestone
      if (mid === NEW_SENTINEL || pid === NEW_SENTINEL) {
        workingMilestoneId = _uid();
        const milestoneLabel = (level === 'task') ? newMilestoneName.trim() : newMilestoneName.trim();
        next = next.map((p) => p.id === workingProjectId ? ({ ...p, branches: [...(p.branches || []), { id: workingMilestoneId, label: milestoneLabel, color: '#3B82F6', leaves: [] }] }) : p);
      }

      if (level === 'task') {
        const newTask = { ...window.defaultTask(entry), images: item.images || [] };
        if (carryDate) newTask.dueDate = carryDate;
        next = next.map((p) => p.id !== workingProjectId ? p : ({
          ...p,
          branches: (p.branches || []).map((b) => b.id !== workingMilestoneId ? b : ({
            ...b,
            leaves: [...(b.leaves || []), newTask],
          })),
        }));
        return next;
      }

      // level === 'subtask' — need a parent task too
      if (tid === NEW_SENTINEL || mid === NEW_SENTINEL || pid === NEW_SENTINEL) {
        workingTaskId = _uid();
        const newParentTask = { ...window.defaultTask(newTaskLabel.trim()), images: [] };
        newParentTask.id = workingTaskId;
        next = next.map((p) => p.id !== workingProjectId ? p : ({
          ...p,
          branches: (p.branches || []).map((b) => b.id !== workingMilestoneId ? b : ({
            ...b,
            leaves: [...(b.leaves || []), newParentTask],
          })),
        }));
      }
      const newSub = window.defaultSubtask(entry);
      if (carryDate) newSub.dueDate = carryDate;
      next = next.map((p) => p.id !== workingProjectId ? p : ({
        ...p,
        branches: (p.branches || []).map((b) => b.id !== workingMilestoneId ? b : ({
          ...b,
          leaves: (b.leaves || []).map((t) => t.id !== workingTaskId ? t : ({
            ...t,
            subtasks: [...(t.subtasks || []), newSub],
          })),
        })),
      }));
      return next;
    });
    onConverted?.();
    onClose();
  };

  const selectStyle = { width: '100%', background: '#050505', border: '1px solid #1c1c1c', borderRadius: 7, padding: '8px 10px', color: '#e8e8e8', fontFamily: 'inherit', fontSize: 12, outline: 'none' };
  const inputStyle = { ...selectStyle, marginTop: 6 };
  const label = (t) => <div style={{ fontSize: 10, fontWeight: 700, color: '#666', letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: 6 }}>{t}</div>;
  const addLabel = level === 'project' ? 'Add as new project' : level === 'milestone' ? 'Add as milestone' : level === 'task' ? 'Add as task' : 'Add as sub-task';

  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 700, background: 'rgba(0,0,0,0.85)', backdropFilter: 'blur(10px)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: 540, maxWidth: '94vw', maxHeight: '90vh', overflowY: 'auto', background: '#0a0a0a', borderRadius: 12, border: '1px solid #1e1e1e', padding: 20 }}>
        <div style={{ fontSize: 11, fontWeight: 700, color: '#888', letterSpacing: '0.14em', textTransform: 'uppercase', marginBottom: 8 }}>Convert capture · edit name then assign</div>

        {/* Editable capture name — becomes the label of whichever level lands */}
        <textarea
          autoFocus
          value={entryName}
          onChange={(e) => { setEntryName(e.target.value); autoGrow(e.target); }}
          ref={(el) => { if (el) autoGrow(el); }}
          rows={2}
          placeholder="Capture text — edit before assigning"
          style={{
            width: '100%', marginBottom: 16,
            padding: '10px 14px',
            background: '#050505',
            border: '1px solid #1c1c1c',
            borderLeft: `3px solid ${TYPE_COLOR[item.type] || '#666'}`,
            borderRadius: 6,
            color: '#e8e8e8',
            fontFamily: 'inherit', fontSize: 14, lineHeight: 1.5,
            outline: 'none', resize: 'none', boxSizing: 'border-box',
            overflow: 'hidden', minHeight: 50,
          }}
        />

        {/* Level — 4-way toggle. Determines what parents need to be specified. */}
        <div style={{ marginBottom: 12 }}>
          {label('Make this a…')}
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {[['project', 'Project'], ['milestone', 'Milestone'], ['task', 'Task'], ['subtask', 'Sub-task']].map(([k, lbl]) => (
              <button key={k} onClick={() => setLevel(k)} style={{
                flex: 1, minWidth: 80, padding: '8px 10px', borderRadius: 7,
                border: `1.5px solid ${level === k ? '#3B82F6' : '#1c1c1c'}`,
                background: level === k ? 'rgba(59,130,246,0.10)' : 'transparent',
                color: level === k ? '#3B82F6' : '#888',
                cursor: 'pointer', fontFamily: 'inherit', fontSize: 12, fontWeight: 700,
              }}>{lbl}</button>
            ))}
          </div>
        </div>

        {/* Project (shown when level is anything other than project) */}
        {needsProjectField && (
          <div style={{ marginBottom: 12 }}>
            {label('Under project')}
            <select value={pid} onChange={(e) => setPid(e.target.value)} style={selectStyle}>
              {projects.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
              <option value={NEW_SENTINEL}>➕ Create new project…</option>
            </select>
            {needsNewProjectName && (
              <input value={newProjectName} onChange={(e) => setNewProjectName(e.target.value)} placeholder="New project name" style={inputStyle} />
            )}
          </div>
        )}

        {/* Milestone (shown when level is task or subtask) */}
        {needsMilestoneField && (
          <div style={{ marginBottom: 12 }}>
            {label('Under milestone')}
            <select value={mid} onChange={(e) => setMid(e.target.value)} style={selectStyle} disabled={pid === NEW_SENTINEL}>
              {milestonesForProject.map((b) => <option key={b.id} value={b.id}>{b.label}</option>)}
              <option value={NEW_SENTINEL}>➕ Create new milestone…</option>
            </select>
            {needsNewMilestoneName && (
              <input value={newMilestoneName} onChange={(e) => setNewMilestoneName(e.target.value)} placeholder="New milestone name" style={inputStyle} />
            )}
          </div>
        )}

        {/* Task (shown only for subtask) */}
        {needsTaskField && (
          <div style={{ marginBottom: 12 }}>
            {label('Under task')}
            <select value={tid} onChange={(e) => setTid(e.target.value)} style={selectStyle} disabled={pid === NEW_SENTINEL || mid === NEW_SENTINEL}>
              {tasksForMilestone.map((t) => <option key={t.id} value={t.id}>{t.label}</option>)}
              <option value={NEW_SENTINEL}>➕ Create new task…</option>
            </select>
            {needsNewTaskName && (
              <input value={newTaskLabel} onChange={(e) => setNewTaskLabel(e.target.value)} placeholder="New parent-task label" style={inputStyle} />
            )}
          </div>
        )}

        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', alignItems: 'center', marginTop: 16 }}>
          <button onClick={onClose} className="btn btn-ghost btn-sm">Cancel</button>
          <button onClick={commit} className="btn btn-primary btn-sm" disabled={!canCommit} title={!canCommit ? 'Fill in all required fields' : ''}>
            {addLabel}
          </button>
        </div>
      </div>
    </div>
  );
}

// Tiny "+ Due" chip → expands to a parse-on-enter input. Keeps the inbox
// metadata row compact when no date is set.
function InlineDueDateSetter({ onSet }) {
  const [open, setOpen] = React.useState(false);
  const [raw, setRaw] = React.useState('');
  const [err, setErr] = React.useState('');
  if (!open) {
    return (
      <button onClick={() => setOpen(true)} title="Set a due date — try 'fri' or 'jun 15'"
        style={{ background: 'transparent', color: '#3a3a3a', border: '1px dashed #1c1c1c', borderRadius: 4, fontSize: 10, fontWeight: 600, padding: '2px 6px', cursor: 'pointer', fontFamily: 'inherit' }}>
        + due
      </button>
    );
  }
  const commit = (v) => {
    const t = (v || raw).trim();
    if (!t) { setOpen(false); setErr(''); return; }
    const iso = window.parseDueDate?.(t);
    if (!iso) { setErr(`?  try fri / tmrw / jun 15`); return; }
    onSet(iso);
    setOpen(false);
    setRaw(''); setErr('');
  };
  return (
    <span style={{ display: 'inline-flex', flexDirection: 'column', gap: 2 }}>
      <input
        autoFocus
        value={raw}
        onChange={(e) => { setRaw(e.target.value); setErr(''); }}
        onBlur={(e) => commit(e.target.value)}
        onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); commit(e.target.value); } if (e.key === 'Escape') { setOpen(false); setRaw(''); setErr(''); } }}
        placeholder="fri / jun 15"
        style={{ background: '#0a0a0a', border: `1px solid ${err ? '#EF4444' : '#1c1c1c'}`, borderRadius: 4, color: '#e8e8e8', fontSize: 10, fontFamily: 'inherit', padding: '2px 6px', width: 90, outline: 'none' }}
      />
      {err && <span style={{ fontSize: 9, color: '#EF4444' }}>{err}</span>}
    </span>
  );
}

window.InlineDueDateSetter = InlineDueDateSetter;

function InboxScreen() {
  const [input, setInput] = useState('');
  const [pendingImages, setPendingImages] = useState([]); // data URLs to attach on next capture
  const [captureType, setCaptureType] = useLFState('inboxCaptureType', 'unassigned');
  const captureRef = useRef(null);
  const [filter, setFilter] = useLFState('inboxFilter', 'all');
  const [items, setItems] = useLFState('inboxItems', [
    { id: 1, text: 'Schedule contractor payment before EOW — Sarah flagged this', type: 'task', urgency: 'high', project: 'Legal Review' },
    { id: 2, text: 'Idea: build a daily constraint-based focus score using HRV + sleep data', type: 'idea', urgency: 'low', images: [] },
    { id: 3, text: 'Email from Marcus re: Thursday architecture review — needs agenda', type: 'task', urgency: 'medium' },
    { id: 4, text: 'Research: ketogenic diet effect on ADHD prefrontal cortex performance', type: 'reference', urgency: 'low' },
  ]);
  const [convertItem, setConvertItem] = useState(null);

  // Voice capture (Phase 0 of Pocket-style flow)
  const [recState, setRecState] = useState('idle'); // idle | starting | recording | transcribing | error
  const [recError, setRecError] = useState('');
  const [recSeconds, setRecSeconds] = useState(0);
  const [recMode, setRecMode] = useState('mic'); // mic | mic+tab
  const recRef = useRef({ recorder: null, chunks: [], streams: [], ctx: null, timerId: null, mimeType: 'audio/webm' });
  const [falKey] = useLFState('falKey', '');
  const [projects] = useLFState('projects', []);
  const [anthKey] = useLFState('apiKey', '');
  const [anthModel] = useLFState('apiModel', 'claude-sonnet-4-6');
  const [triageNote, setTriageNote] = useState(''); // post-triage summary (one-line)
  const [calOpenFor, setCalOpenFor] = useState(null); // event id whose calendar popover is open
  const [imgLightbox, setImgLightbox] = useState(null); // data URL of image being viewed

  // Derive keyterms from current project/milestone/task names so transcription
  // gets technical / domain words right. Cap at 100 to fit the API limit.
  const keyterms = (() => {
    const out = new Set();
    for (const p of projects) {
      if (p.name) out.add(p.name);
      for (const b of (p.branches || [])) {
        if (b.label) out.add(b.label);
        for (const t of (b.leaves || [])) {
          if (t.label) out.add(t.label);
        }
      }
    }
    return Array.from(out).filter((s) => s && s.length <= 50).slice(0, 100);
  })();

  const startRecording = async () => {
    if (recState !== 'idle') return;
    setRecError('');
    setRecState('starting');
    const r = recRef.current;
    r.chunks = []; r.streams = [];
    try {
      // Always grab mic
      const micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
      r.streams.push(micStream);

      let recordStream = micStream;
      if (recMode === 'mic+tab') {
        // Browser will prompt user to pick a tab/window with audio. Some
        // browsers require the user to also check "Share tab audio".
        try {
          const tabStream = await navigator.mediaDevices.getDisplayMedia({
            video: true, // required by spec, we ignore the video track
            audio: true,
            preferCurrentTab: false,
          });
          // We don't care about the video track — stop it immediately
          tabStream.getVideoTracks().forEach((t) => t.stop());
          if (tabStream.getAudioTracks().length === 0) {
            tabStream.getTracks().forEach((t) => t.stop());
            throw new Error('Tab audio not shared — make sure "Share tab audio" is checked in the picker.');
          }
          r.streams.push(tabStream);

          // Mix mic + tab into a single stream via Web Audio
          const ctx = new (window.AudioContext || window.webkitAudioContext)();
          const dest = ctx.createMediaStreamDestination();
          ctx.createMediaStreamSource(micStream).connect(dest);
          ctx.createMediaStreamSource(tabStream).connect(dest);
          r.ctx = ctx;
          recordStream = dest.stream;
        } catch (e) {
          // Tab share cancelled — fall back to mic-only with a notice
          if (e.name === 'NotAllowedError' || (e.message || '').includes('cancelled')) {
            // User cancelled; fall back silently to mic-only
          } else {
            throw e;
          }
        }
      }

      // Choose a mime type the browser can record
      const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'];
      const mimeType = candidates.find((t) => window.MediaRecorder?.isTypeSupported?.(t)) || 'audio/webm';
      r.mimeType = mimeType;

      const recorder = new MediaRecorder(recordStream, { mimeType });
      r.recorder = recorder;
      recorder.ondataavailable = (e) => { if (e.data && e.data.size > 0) r.chunks.push(e.data); };
      recorder.onstop = handleStop;
      recorder.start(1000); // 1s chunks

      setRecSeconds(0);
      r.timerId = setInterval(() => setRecSeconds((s) => s + 1), 1000);
      setRecState('recording');
    } catch (e) {
      setRecError(e.message || String(e));
      setRecState('error');
      cleanupRecording();
    }
  };

  const stopRecording = () => {
    const r = recRef.current;
    if (r.timerId) { clearInterval(r.timerId); r.timerId = null; }
    if (r.recorder && r.recorder.state !== 'inactive') {
      try { r.recorder.stop(); } catch {}
    } else {
      cleanupRecording();
      setRecState('idle');
    }
  };

  const cancelRecording = () => {
    const r = recRef.current;
    if (r.timerId) { clearInterval(r.timerId); r.timerId = null; }
    if (r.recorder && r.recorder.state !== 'inactive') {
      try { r.recorder.ondataavailable = null; r.recorder.onstop = null; r.recorder.stop(); } catch {}
    }
    cleanupRecording();
    setRecState('idle');
  };

  const cleanupRecording = () => {
    const r = recRef.current;
    r.streams.forEach((s) => s.getTracks().forEach((t) => t.stop()));
    r.streams = [];
    if (r.ctx) { try { r.ctx.close(); } catch {} r.ctx = null; }
    r.recorder = null;
  };

  const handleStop = async () => {
    const r = recRef.current;
    const blob = new Blob(r.chunks, { type: r.mimeType });
    cleanupRecording();
    if (!blob.size) { setRecState('idle'); return; }
    // BYOK fallback is checked server-side now. Free users with a fal key
    // on their profile use their own quota; otherwise the Edge Function
    // uses our server-side FAL_KEY and meters against their plan cap.
    setRecState('transcribing');
    try {
      // Route through Supabase Edge Function. JWT attached automatically.
      // Pass raw audio body, content-type tells fal what format it is.
      if (!window.sbCallFn) throw new Error('Not signed in — please sign in to use voice transcription.');
      const session = await window.sbAuth.getSession();
      if (!session) throw new Error('Session expired — please sign in again.');
      const res = await fetch(
        'https://dorihecfdkhqatrffmul.supabase.co/functions/v1/transcribe',
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${session.access_token}`,
            'apikey': 'sb_publishable_HjuxGI9mMA6l61TW63D-jg_yldDUttF',
            'Content-Type': blob.type || 'audio/webm',
          },
          body: blob,
        }
      );
      if (!res.ok) {
        let msg = `HTTP ${res.status}`;
        try {
          const j = await res.json();
          if (j.error) msg = j.error;
        } catch {
          try { msg = (await res.text()).slice(0, 300); } catch {}
        }
        throw new Error(msg);
      }
      const data = await res.json();
      const text = (data.text || '').trim();
      if (!text) throw new Error('Empty transcript');
      // First — always drop the raw transcript into Inbox as a voice capture
      // so the original is never lost regardless of what triage does next.
      const rawItem = {
        id: crypto.randomUUID(),
        text,
        type: 'unassigned',
        urgency: 'medium',
        images: [],
        voice: true,
        aiCreated: true,
        validated: false,
        fresh: true,
      };
      setItems((prev) => [rawItem, ...prev]);

      // Phase 1: AI triage. If Anthropic key is set, send the transcript to
      // Claude with the project tools + a triage prompt. Claude will either
      // create project/milestone/task/subtask entries (flagged for review)
      // or just leave it as the inbox capture.
      if (anthKey) {
        setRecState('triaging');
        try {
          await runVoiceTriage(text);
        } catch (e) {
          setTriageNote(`Triage failed: ${e.message}`);
        }
      }
      setRecState('idle');
    } catch (e) {
      setRecError(e.message || String(e));
      setRecState('error');
    }
  };

  // Run Claude with the project tools, wrapping each create_* executor so any
  // entity it creates lands with aiCreated:true + validated:false. Returns
  // a summary like "Created 1 task in 'Halifax Infirmary'".
  const runVoiceTriage = async (transcript) => {
    setTriageNote('');
    const counts = { project: 0, milestone: 0, task: 0, subtask: 0, event: 0 };

    // Wrap executors so they mark anything they just made for review
    const origExec = window.LF_TOOL_EXECUTORS;
    const _markProject = (pid) => window.LF.update('projects', (ps = []) => ps.map((p) => p.id === pid ? { ...p, aiCreated: true, validated: false } : p));
    const _markMilestone = (pid, mid) => window.LF.update('projects', (ps = []) => ps.map((p) => p.id !== pid ? p : ({ ...p, branches: (p.branches || []).map((b) => b.id === mid ? { ...b, aiCreated: true, validated: false } : b) })));
    const _markTask = (pid, mid, tid) => window.LF.update('projects', (ps = []) => ps.map((p) => p.id !== pid ? p : ({ ...p, branches: (p.branches || []).map((b) => b.id !== mid ? b : ({ ...b, leaves: (b.leaves || []).map((t) => t.id === tid ? { ...t, aiCreated: true, validated: false } : t) })) })));
    const _markSubtask = (pid, mid, tid, sid) => window.LF.update('projects', (ps = []) => ps.map((p) => p.id !== pid ? p : ({ ...p, branches: (p.branches || []).map((b) => b.id !== mid ? b : ({ ...b, leaves: (b.leaves || []).map((t) => t.id !== tid ? t : ({ ...t, subtasks: (t.subtasks || []).map((s) => s.id === sid ? { ...s, aiCreated: true, validated: false } : s) })) })) })));

    const wrapped = {
      ...origExec,
      create_project: (input) => {
        const r = origExec.create_project(input);
        if (r?.project_id) { counts.project++; _markProject(r.project_id); }
        return r;
      },
      create_milestone: (input) => {
        const r = origExec.create_milestone(input);
        if (r?.milestone_id) { counts.milestone++; _markMilestone(input.project_id, r.milestone_id); }
        return r;
      },
      create_task: (input) => {
        const r = origExec.create_task(input);
        if (r?.task_id) { counts.task++; _markTask(input.project_id, input.milestone_id, r.task_id); }
        return r;
      },
      create_subtask: (input) => {
        const r = origExec.create_subtask(input);
        if (r?.subtask_id) { counts.subtask++; _markSubtask(input.project_id, input.milestone_id, input.task_id, r.subtask_id); }
        return r;
      },
      create_event: (input) => {
        const r = origExec.create_event(input);
        if (r?.event_id) counts.event++;
        // create_event already flags aiCreated:true inside the executor
        return r;
      },
    };

    const triageSystem = window.USER_PROFILE_SYSTEM + (window.lfDateContext ? window.lfDateContext() : '') + `

TRIAGE MODE — you are processing a voice transcript that Ahmed just recorded.

Process:
1. Call list_projects FIRST to see what already exists.
2. Read the transcript and pull out concrete action items, decisions, project signals, AND scheduled events (anything with a specific date/time).
3. For each ACTION ITEM:
   - If it clearly belongs to an EXISTING project & milestone, create_task there.
   - If it belongs to an existing project but needs a NEW milestone, create_milestone then create_task.
   - If it warrants a WHOLE NEW project (a sustained body of work, not a one-off), create_project + create_milestone + create_task. Use sparingly — be conservative.
   - If it's just a stray thought / idea / reference with no clear project home, do NOTHING (it's already in his Inbox as a voice capture).
4. For each EVENT (meeting / appointment / call / deadline with explicit time):
   - Call create_event with title, start (ISO 8601 + timezone offset), end (default +1h), location, attendees, description.
   - If the event is tied to a project/task he already has (or one you just created), pass project_id/milestone_id/task_id for linkage.
   - Resolve relative dates ("Tuesday", "tomorrow", "this Friday") using the DATE/TIMEZONE CONTEXT above.
5. Sub-tasks: break a task down ONLY if the transcript explicitly lists steps. Don't invent sub-tasks the user didn't say.
6. Mode + priority: infer sensibly. Use 'calls' for outreach + meetings, 'production' for build/output, 'learning' for study, 'medical' for health, 'parent' for kids. Default priority medium unless he says urgent / critical / today.
7. When in doubt, do less. He'll validate each AI-created entity manually.

After all tool calls, respond with ONE sentence summarizing what you did. No markdown, no lists — one sentence.`;

    const apiMessages = [
      { role: 'user', content: `Voice transcript:\n\n${transcript}` },
    ];
    let workingMessages = apiMessages;
    let lastText = '';
    for (let turn = 0; turn < 8; turn++) {
      const response = await window.callClaude({
        apiKey: anthKey,
        model: anthModel,
        system: triageSystem,
        messages: workingMessages,
        tools: window.LF_TOOLS,
      });
      const toolUses = (response.content || []).filter((b) => b.type === 'tool_use');
      const textParts = (response.content || []).filter((b) => b.type === 'text').map((b) => b.text).join('');
      if (textParts) lastText = textParts;
      if (toolUses.length === 0 || response.stop_reason === 'end_turn') break;
      const toolResults = [];
      for (const tu of toolUses) {
        let result;
        try {
          const exec = wrapped[tu.name];
          result = exec ? exec(tu.input || {}) : { error: 'unknown tool: ' + tu.name };
        } catch (e) { result = { error: e.message }; }
        toolResults.push({ type: 'tool_result', tool_use_id: tu.id, content: JSON.stringify(result) });
      }
      workingMessages = [
        ...workingMessages,
        { role: 'assistant', content: response.content },
        { role: 'user', content: toolResults },
      ];
    }
    // Build a short summary
    const made = [];
    if (counts.project) made.push(`${counts.project} project${counts.project > 1 ? 's' : ''}`);
    if (counts.milestone) made.push(`${counts.milestone} milestone${counts.milestone > 1 ? 's' : ''}`);
    if (counts.task) made.push(`${counts.task} task${counts.task > 1 ? 's' : ''}`);
    if (counts.subtask) made.push(`${counts.subtask} sub-task${counts.subtask > 1 ? 's' : ''}`);
    if (counts.event) made.push(`${counts.event} event${counts.event > 1 ? 's' : ''}`);
    setTriageNote(made.length ? `✦ Created ${made.join(', ')} — review with the ✓ Validate button on each.` : (lastText || '✦ Triaged. Nothing to auto-create — left as inbox capture.'));
    // Clear note after 12s
    setTimeout(() => setTriageNote(''), 12000);
  };

  const capture = () => {
    if (!input.trim() && !pendingImages.length) return;
    const newItem = {
      id: crypto.randomUUID(),
      text: input.trim() || (pendingImages.length ? '(image)' : ''),
      type: captureType,
      urgency: 'medium',
      images: pendingImages,
      fresh: true,
    };
    setItems((prev) => [newItem, ...prev]);
    setInput('');
    setPendingImages([]);
    // Reset textarea height after submit
    if (captureRef.current) { captureRef.current.style.height = 'auto'; }
  };

  const onPaste = (e) => {
    const its = e.clipboardData?.items || [];
    for (const it of its) {
      if (it.type?.startsWith('image/')) {
        const f = it.getAsFile();
        const reader = new FileReader();
        reader.onload = () => setPendingImages((p) => [...p, reader.result]);
        reader.readAsDataURL(f);
        e.preventDefault();
      }
    }
  };

  const dismiss = (id) => setItems((prev) => prev.filter((it) => it.id !== id));
  const updateItem = (id, patch) => setItems((prev) => prev.map((it) => it.id === id ? { ...it, ...patch } : it));
  const filtered = filter === 'all' ? items : items.filter((it) => it.type === filter);

  // Drag-to-reorder captures. Same pattern as TasksScreen / TodayMission —
  // operates on the full items array, so dragging inside a filtered view
  // preserves the relative order of items in other filters.
  const [draggedId, setDraggedId] = React.useState(null);
  const reorderTo = (fromId, toId) => {
    if (fromId === toId) return;
    setItems((prev) => {
      const fromIdx = prev.findIndex((it) => it.id === fromId);
      const toIdx = prev.findIndex((it) => it.id === toId);
      if (fromIdx === -1 || toIdx === -1) return prev;
      const next = [...prev];
      const [moved] = next.splice(fromIdx, 1);
      next.splice(toIdx, 0, moved);
      return next;
    });
  };

  // After successful convert, drop the source capture from the inbox so it
  // doesn't sit duplicated. Modal calls onConverted() right before onClose.
  const onCaptureConverted = () => { if (convertItem) dismiss(convertItem.id); };

  return (
    <div className="screen screen-enter">
      {/* Capture bar */}
      <div className="card" style={{ padding: '12px 18px', display: 'flex', flexDirection: 'column', gap: 10 }}>
        <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
          <div style={{ color: '#2a2a2a', fontSize: 18, fontWeight: 300, lineHeight: 1.2, paddingTop: 2 }}>+</div>
          <textarea
            ref={captureRef}
            value={input}
            onChange={(e) => { setInput(e.target.value); autoGrow(e.target); }}
            onPaste={onPaste}
            onKeyDown={(e) => {
              // Enter submits, Shift+Enter inserts a newline
              if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); capture(); }
            }}
            placeholder="Capture anything — thought, task, idea. Paste an image to attach. Enter to submit, Shift+Enter for newline."
            rows={1}
            style={{ flex: 1, background: 'transparent', border: 'none', color: '#e8e8e8', fontSize: 14, fontFamily: 'inherit', outline: 'none', resize: 'none', lineHeight: 1.55, overflow: 'hidden', minHeight: 22 }}
          />
          <button className="btn btn-primary btn-sm" onClick={capture} disabled={!input.trim() && !pendingImages.length} style={{ flexShrink: 0 }}>Capture</button>
        </div>

        {/* Voice capture — mic or mic+tab → fal.ai Scribe V2 → inbox item */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, paddingLeft: 32, flexWrap: 'wrap' }}>
          {recState === 'idle' && (
            <>
              <button onClick={startRecording} style={{ background: '#EF444411', border: '1px solid #EF444455', color: '#EF4444', borderRadius: 6, padding: '5px 12px', fontSize: 11, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#EF4444' }} />
                Record voice
              </button>
              <div style={{ display: 'inline-flex', gap: 4, alignItems: 'center' }}>
                <span style={{ fontSize: 9, color: '#3a3a3a', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>Source</span>
                {[['mic', 'Mic only'], ['mic+tab', 'Mic + tab audio']].map(([k, label]) => (
                  <button key={k} onClick={() => setRecMode(k)} style={{ background: recMode === k ? '#EF444422' : 'transparent', color: recMode === k ? '#EF4444' : '#666', border: `1px solid ${recMode === k ? '#EF444455' : '#1c1c1c'}`, borderRadius: 5, padding: '3px 8px', fontSize: 10, fontWeight: recMode === k ? 700 : 600, cursor: 'pointer', fontFamily: 'inherit' }}>{label}</button>
                ))}
              </div>
            </>
          )}
          {recState === 'starting' && (
            <div style={{ fontSize: 11, color: '#F59E0B', fontWeight: 600, letterSpacing: '0.04em' }}>Requesting permission…</div>
          )}
          {recState === 'recording' && (
            <>
              <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '5px 12px', background: '#EF444411', border: '1px solid #EF444455', borderRadius: 6 }}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#EF4444', boxShadow: '0 0 8px #EF4444', animation: 'pulse 1.5s infinite' }} />
                <span style={{ fontSize: 11, color: '#EF4444', fontWeight: 700, letterSpacing: '0.06em', fontVariantNumeric: 'tabular-nums' }}>
                  REC {Math.floor(recSeconds / 60)}:{String(recSeconds % 60).padStart(2, '0')}
                </span>
                <span style={{ fontSize: 9, color: '#EF4444aa', letterSpacing: '0.06em' }}>{recMode === 'mic+tab' ? '· mic+tab' : '· mic'}</span>
              </div>
              <button onClick={stopRecording} className="btn btn-primary btn-sm">⏹ Stop & transcribe</button>
              <button onClick={cancelRecording} className="btn btn-ghost btn-sm">Cancel</button>
            </>
          )}
          {recState === 'transcribing' && (
            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '5px 12px', background: '#8B5CF611', border: '1px solid #8B5CF655', borderRadius: 6 }}>
              <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#8B5CF6', boxShadow: '0 0 8px #8B5CF6', animation: 'pulse 1.2s infinite' }} />
              <span style={{ fontSize: 11, color: '#8B5CF6', fontWeight: 700, letterSpacing: '0.06em' }}>TRANSCRIBING…</span>
            </div>
          )}
          {recState === 'triaging' && (
            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '5px 12px', background: '#8B5CF611', border: '1px solid #8B5CF655', borderRadius: 6 }}>
              <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#8B5CF6', boxShadow: '0 0 8px #8B5CF6', animation: 'pulse 1.2s infinite' }} />
              <span style={{ fontSize: 11, color: '#8B5CF6', fontWeight: 700, letterSpacing: '0.06em' }}>✦ AI TRIAGING…</span>
            </div>
          )}
          {triageNote && recState === 'idle' && (
            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '5px 12px', background: '#8B5CF611', border: '1px solid #8B5CF655', borderRadius: 6, maxWidth: 600 }}>
              <span style={{ fontSize: 11, color: '#8B5CF6', fontWeight: 600, lineHeight: 1.4 }}>{triageNote}</span>
              <button onClick={() => setTriageNote('')} style={{ background: 'transparent', border: 'none', color: '#8B5CF6aa', fontSize: 13, cursor: 'pointer', padding: 0, lineHeight: 1 }}>×</button>
            </div>
          )}
          {recState === 'error' && (
            <>
              <div style={{ fontSize: 11, color: '#EF4444', fontWeight: 600, padding: '4px 10px', background: '#EF444411', border: '1px solid #EF444433', borderRadius: 6, maxWidth: 480, overflow: 'hidden', textOverflow: 'ellipsis' }}>
                ⚠ {recError}
              </div>
              <button onClick={() => { setRecState('idle'); setRecError(''); }} className="btn btn-ghost btn-sm">Dismiss</button>
            </>
          )}
        </div>

        {/* Category picker — set the type at capture time */}
        <div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap', paddingLeft: 32 }}>
          <span style={{ fontSize: 9, color: '#3a3a3a', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', marginRight: 4 }}>Category</span>
          {[['unassigned','Unassigned'],['task','Task'],['idea','Idea'],['reference','Reference'],['event','Event']].map(([k, label]) => {
            const active = captureType === k;
            const col = TYPE_COLOR[k];
            return (
              <button key={k} onClick={() => setCaptureType(k)} style={{
                background: active ? `${col}28` : `${col}10`,
                color: active ? col : col + 'b0',
                border: `1px solid ${active ? col + '88' : col + '33'}`,
                borderRadius: 5, padding: '3px 9px',
                fontSize: 10, fontWeight: active ? 700 : 600,
                cursor: 'pointer', fontFamily: 'inherit',
              }}>{label}</button>
            );
          })}
        </div>
        {pendingImages.length > 0 && (
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {pendingImages.map((src, i) => (
              <div key={i} style={{ position: 'relative' }}>
                <img src={src} alt="" style={{ maxWidth: 80, maxHeight: 60, borderRadius: 4, border: '1px solid #222' }} />
                <button onClick={() => setPendingImages((p) => p.filter((_, j) => j !== i))} style={{ position: 'absolute', top: 2, right: 2, background: 'rgba(0,0,0,0.7)', border: 'none', color: '#EF4444', borderRadius: 3, fontSize: 10, cursor: 'pointer', width: 16, height: 16 }}>×</button>
              </div>
            ))}
          </div>
        )}
      </div>

      {/* Filters — pluralized labels, each half-toned in its type color */}
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        {[['all', 'All', '#666'], ['unassigned', 'Unassigned', TYPE_COLOR.unassigned], ['task', 'Tasks', TYPE_COLOR.task], ['idea', 'Ideas', TYPE_COLOR.idea], ['reference', 'References', TYPE_COLOR.reference], ['event', 'Events', TYPE_COLOR.event]].map(([f, label, col]) => {
          const count = f === 'all' ? items.length : items.filter((it) => it.type === f).length;
          const isActive = filter === f;
          return (
            <button key={f} onClick={() => setFilter(f)} className="btn btn-sm"
              style={{
                background: isActive ? `${col}28` : `${col}10`,
                color: isActive ? col : col + 'b0',
                border: `1px solid ${isActive ? col + '88' : col + '33'}`,
                fontWeight: isActive ? 700 : 600,
              }}>
              {label}
              <span style={{ marginLeft: 6, opacity: 0.65 }}>{count}</span>
            </button>
          );
        })}
        <div style={{ marginLeft: 'auto', fontSize: 12, color: '#2a2a2a', display: 'flex', alignItems: 'center' }}>
          {items.length} items · {items.filter((i) => i.urgency === 'high').length} urgent
        </div>
      </div>

      {/* Items */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {filtered.map((item) => {
          // AI-created items that haven't been validated yet get a special
          // dashed purple border + "✦ AI · review" badge so they stand out.
          const pending = item.aiCreated && !item.validated;
          // Urgency border for orphan task captures with a near-term due date.
          const isUrgentTask = item.type === 'task' && item.dueDate;
          const urgDays = isUrgentTask ? window.daysUntil?.(item.dueDate) : null;
          const showUrgent = isUrgentTask && urgDays !== null && urgDays !== undefined && urgDays <= 5;
          const urgColor = showUrgent ? window.urgencyColor?.(item.dueDate) : null;
          return (
          <div key={item.id} className="card"
            draggable
            onDragStart={(e) => { setDraggedId(item.id); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', String(item.id)); }}
            onDragEnd={() => setDraggedId(null)}
            onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }}
            onDrop={(e) => { e.preventDefault(); const fromId = Number(e.dataTransfer.getData('text/plain')); if (fromId && fromId !== item.id) reorderTo(fromId, item.id); setDraggedId(null); }}
            style={{
            display: 'flex', gap: 14, alignItems: 'flex-start', padding: '13px 18px', cursor: 'grab',
            borderColor: showUrgent ? urgColor : (pending ? '#8B5CF6' : (item.fresh ? 'rgba(139,92,246,0.2)' : '#1c1c1c')),
            borderStyle: pending && !showUrgent ? 'dashed' : 'solid',
            background: showUrgent ? `${urgColor}10` : (pending ? 'rgba(139,92,246,0.06)' : (item.fresh ? 'rgba(139,92,246,0.03)' : '#111')),
            boxShadow: showUrgent ? `0 0 14px ${urgColor}55` : 'none',
            transition: 'all 0.3s',
            position: 'relative',
            opacity: draggedId === item.id ? 0.5 : 1,
          }}>
            {item.voice && (
              <div style={{ position: 'absolute', top: -8, left: 14, background: '#0a0a0a', padding: '0 8px', fontSize: 9, color: '#EF4444', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>
                🎙 Voice
              </div>
            )}
            {pending && (
              <div style={{ position: 'absolute', top: -8, right: 14, background: '#0a0a0a', padding: '0 8px', fontSize: 9, color: '#8B5CF6', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>
                ✦ AI · review
              </div>
            )}
            <div style={{ width: 8, height: 8, borderRadius: '50%', background: TYPE_COLOR[item.type], flex: 'none', marginTop: 6 }} />
            <div style={{ flex: 1, minWidth: 0 }}>
              {/* Editable rich text with formatting hidden by default. Click
                  "Show formatting" inside the editor to reveal the toolbar. */}
              <div style={{ marginBottom: 7 }}>
                <window.RichEditor
                  value={item.text || ''}
                  onChange={(html) => updateItem(item.id, { text: html })}
                  placeholder="Capture text — edit anytime"
                  color={TYPE_COLOR[item.type]}
                  minHeight={24}
                />
              </div>
              {item.images?.length > 0 && (
                <div style={{ display: 'flex', gap: 6, marginBottom: 7, flexWrap: 'wrap' }}>
                  {item.images.map((src, i) => (
                    <img key={i} src={src} alt="" onClick={() => setImgLightbox(src)}
                      style={{ maxWidth: 120, maxHeight: 80, borderRadius: 4, border: '1px solid #222', cursor: 'zoom-in' }} />
                  ))}
                </div>
              )}
              {/* Event details */}
              {item.type === 'event' && (
                <div style={{ marginBottom: 7, padding: '6px 10px', background: 'rgba(245,158,11,0.06)', border: '1px solid rgba(245,158,11,0.18)', borderRadius: 6, fontSize: 12, color: '#F59E0B', lineHeight: 1.5 }}>
                  {item.start ? (
                    <div style={{ fontWeight: 700 }}>📅 {formatEventTime(item)}</div>
                  ) : (
                    <div style={{ fontWeight: 600, fontStyle: 'italic', color: '#F59E0Baa' }}>📅 No date set — pick it in your calendar after adding</div>
                  )}
                  {item.location && <div style={{ color: '#F59E0Bcc', marginTop: 2 }}>📍 {item.location}</div>}
                  {item.attendees?.length > 0 && (
                    <div style={{ color: '#F59E0Baa', fontSize: 11, marginTop: 2 }}>
                      👥 {item.attendees.join(', ')}
                    </div>
                  )}
                  {item.description && <div style={{ color: '#999', fontSize: 11, marginTop: 3, fontStyle: 'italic' }}>{item.description}</div>}
                </div>
              )}
              <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
                {/* Type pill — clickable to cycle */}
                <select value={item.type} onChange={(e) => updateItem(item.id, { type: e.target.value })}
                  style={{ background: `${TYPE_COLOR[item.type]}14`, color: TYPE_COLOR[item.type], border: 'none', borderRadius: 4, fontSize: 10, fontWeight: 700, padding: '2px 6px', cursor: 'pointer', fontFamily: 'inherit', outline: 'none' }}>
                  <option value="unassigned">unassigned</option>
                  <option value="task">task</option>
                  <option value="idea">idea</option>
                  <option value="reference">reference</option>
                  <option value="event">event</option>
                </select>
                {/* Priority dropdown */}
                <select value={item.urgency} onChange={(e) => updateItem(item.id, { urgency: e.target.value })}
                  style={{ background: 'transparent', color: URG_COLOR[item.urgency], border: `1px solid ${URG_COLOR[item.urgency]}33`, borderRadius: 4, fontSize: 10, fontWeight: 600, padding: '2px 6px', cursor: 'pointer', fontFamily: 'inherit', outline: 'none' }}>
                  <option value="high">high</option>
                  <option value="medium">medium</option>
                  <option value="low">low</option>
                </select>
                {/* Due date — only on task captures (and orphans). Native input  +
                    NL parse. Show the urgency chip when set. */}
                {item.type === 'task' && (
                  item.dueDate ? (
                    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                      <window.DueDateChip date={item.dueDate} compact />
                      <button onClick={() => updateItem(item.id, { dueDate: null })}
                        title="Clear due date"
                        style={{ background: 'none', border: 'none', color: '#3a3a3a', cursor: 'pointer', fontSize: 11, padding: 0, lineHeight: 1 }}>×</button>
                    </span>
                  ) : (
                    <InlineDueDateSetter onSet={(d) => updateItem(item.id, { dueDate: d })} />
                  )
                )}
                {item.project && <span style={{ fontSize: 10, color: '#2a2a2a' }}>{item.project}</span>}
              </div>
            </div>
            <div style={{ display: 'flex', gap: 6, flexShrink: 0, alignItems: 'flex-start', position: 'relative' }}>
              {pending && (
                <button onClick={() => updateItem(item.id, { validated: true, aiCreated: false })} title="Mark as validated — removes the AI badge"
                  style={{ background: '#22C55E22', border: '1px solid #22C55E55', color: '#22C55E', borderRadius: 6, padding: '3px 8px', fontSize: 10, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' }}>✓ Validate</button>
              )}
              {item.type === 'event' && (
                <div style={{ position: 'relative' }}>
                  <button onClick={() => setCalOpenFor(calOpenFor === item.id ? null : item.id)} className="btn btn-ghost btn-sm" style={{ whiteSpace: 'nowrap', fontSize: 10, background: '#F59E0B22', border: '1px solid #F59E0B55', color: '#F59E0B' }}
                    title={item.start ? 'Open the calendar picker' : 'No date set yet — pick it in your calendar after the event opens'}>📅 Add to calendar</button>
                  {calOpenFor === item.id && (
                    <div style={{ position: 'absolute', top: '110%', right: 0, zIndex: 50, background: '#0a0a0a', border: '1px solid #F59E0B55', borderRadius: 8, padding: 8, minWidth: 180, boxShadow: '0 8px 24px rgba(0,0,0,0.7)', display: 'flex', flexDirection: 'column', gap: 6 }}>
                      <button onClick={() => { window.open(googleCalendarUrl(item), '_blank', 'noopener'); setCalOpenFor(null); }}
                        style={{ background: 'transparent', border: '1px solid #1c1c1c', color: '#e8e8e8', borderRadius: 6, padding: '6px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left' }}>
                        🌐 Google Calendar
                      </button>
                      <button onClick={() => { downloadIcs(item); setCalOpenFor(null); }}
                        style={{ background: 'transparent', border: '1px solid #1c1c1c', color: '#e8e8e8', borderRadius: 6, padding: '6px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left' }}>
                        📧 Outlook (.ics download)
                      </button>
                      <button onClick={() => { window.open(googleCalendarUrl(item), '_blank', 'noopener'); downloadIcs(item); setCalOpenFor(null); }}
                        style={{ background: '#F59E0B22', border: '1px solid #F59E0B55', color: '#F59E0B', borderRadius: 6, padding: '6px 10px', fontSize: 11, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left' }}>
                        🌐 + 📧 Both
                      </button>
                      <button onClick={() => setCalOpenFor(null)} style={{ background: 'transparent', border: 'none', color: '#3a3a3a', fontSize: 10, cursor: 'pointer', fontFamily: 'inherit', padding: '2px 0', marginTop: 2 }}>cancel</button>
                    </div>
                  )}
                </div>
              )}
              {item.type !== 'event' && (
                <button onClick={() => setConvertItem(item)} className="btn btn-ghost btn-sm" style={{ whiteSpace: 'nowrap', fontSize: 10 }} title="Convert this capture into a task or sub-task — create a new project/milestone/task inline if needed.">→ Convert</button>
              )}
              <button onClick={() => dismiss(item.id)} style={{ background: 'none', border: 'none', color: '#2a2a2a', cursor: 'pointer', fontSize: 16, padding: '0 4px' }}>×</button>
              <span title="Drag to reorder" style={{ color: '#2a2a2a', fontSize: 11, cursor: 'grab', paddingLeft: 2, alignSelf: 'center' }}>⋮⋮</span>
            </div>
          </div>
        );
        })}
      </div>

      {convertItem && <ConvertToTaskModal item={convertItem} onClose={() => setConvertItem(null)} onConverted={onCaptureConverted} />}
      {imgLightbox && window.ImageLightbox && <window.ImageLightbox src={imgLightbox} onClose={() => setImgLightbox(null)} />}
    </div>
  );
}

// ─── TASKS ────────────────────────────────────────────────────────────────────
// Flat list across all projects with Project › Milestone › Task breadcrumb,
// % from subtasks, sprint button. Click any task → opens TaskEditor.
function TasksScreen() {
  const [projects] = useLFState('projects', []);
  const [filter, setFilter] = useLFState('tasksFilter', 'active'); // all | today | active | done
  const [openTask, setOpenTask] = useState(null); // {projectId, milestoneId, taskId}
  const [order, setOrder] = useLFState('tasksOrder', []); // array of "pid:mid:tid"
  const [draggingId, setDraggingId] = useState(null);

  // Flatten all tasks with breadcrumb context
  const flat = [];
  for (const p of projects) {
    for (const m of (p.branches || [])) {
      if (m.archived) continue;
      for (const t of (m.leaves || [])) {
        flat.push({ project: p, milestone: m, task: t });
      }
    }
  }
  // Sort by saved custom order (tasks not in order are appended)
  const keyOf = (r) => `${r.project.id}:${r.milestone.id}:${r.task.id}`;
  const ordered = [...flat].sort((a, b) => {
    const ai = order.indexOf(keyOf(a));
    const bi = order.indexOf(keyOf(b));
    if (ai === -1 && bi === -1) return 0;
    if (ai === -1) return 1;
    if (bi === -1) return -1;
    return ai - bi;
  });

  const filtered = ordered.filter(({ task }) => {
    const tp = taskProgress(task);
    if (filter === 'today') return task.today;
    if (filter === 'done') return task.done || tp === 100;
    if (filter === 'active') return !task.done && tp < 100;
    return true;
  });

  const counts = {
    all: flat.length,
    today: flat.filter(({ task }) => task.today).length,
    active: flat.filter(({ task }) => !task.done && taskProgress(task) < 100).length,
    done: flat.filter(({ task }) => task.done || taskProgress(task) === 100).length,
  };

  const [launchingShow, setLaunchingShow] = useState(null);

  const sprintTask = ({ project, milestone, task }) => {
    setLaunchingShow({
      projectId: project.id, milestoneId: milestone.id, taskId: task.id,
      label: task.label || 'Untitled task',
      defaultMode: task.mode || 'production',
    });
  };
  const toggleDone = ({ project, milestone, task }) => {
    window.setTaskDoneCascade(project.id, milestone.id, task.id, !task.done);
  };

  // Swap two task positions in order array (adding either if missing)
  const swapOrder = (a, b) => {
    if (a === b) return;
    const allKeys = ordered.map(keyOf);
    const idxA = allKeys.indexOf(a);
    const idxB = allKeys.indexOf(b);
    if (idxA === -1 || idxB === -1) return;
    const next = [...allKeys];
    [next[idxA], next[idxB]] = [next[idxB], next[idxA]];
    setOrder(next);
  };

  const activeTaskRow = openTask ? flat.find((r) => r.project.id === openTask.projectId && r.milestone.id === openTask.milestoneId && r.task.id === openTask.taskId) : null;

  const PRI_COLOR = { high: '#EF4444', medium: '#F59E0B', low: '#22C55E' };

  return (
    <div className="screen screen-enter">
      {/* Filters */}
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 4, flexWrap: 'wrap' }}>
        {[['active', 'Active'], ['today', 'Today'], ['all', 'All'], ['done', 'Done']].map(([id, label]) => (
          <button key={id} onClick={() => setFilter(id)} className="btn btn-sm" style={{
            background: filter === id ? '#1c1c1c' : 'transparent',
            color: filter === id ? '#e8e8e8' : '#666',
            border: `1px solid ${filter === id ? '#2a2a2a' : '#1a1a1a'}`,
          }}>{label} <span style={{ marginLeft: 6, color: '#444' }}>{counts[id]}</span></button>
        ))}
        <div style={{ marginLeft: 'auto', fontSize: 10, color: '#3a3a3a', letterSpacing: '0.1em', textTransform: 'uppercase' }}>Drag tile onto another to swap positions</div>
      </div>

      {/* Cards grid */}
      {filtered.length === 0 ? (
        <div style={{ padding: 50, textAlign: 'center', color: '#444', border: '1px dashed #1c1c1c', borderRadius: 12 }}>
          {filter === 'all' ? 'No tasks yet — add some in Projetecture.' : `No ${filter} tasks.`}
        </div>
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 12 }}>
          {filtered.map((row) => {
            const { project, milestone, task } = row;
            const key = keyOf(row);
            const m = modeOf(task.mode);
            const tp = taskProgress(task);
            const isDone = task.done || tp === 100;
            const pri = task.priority || 'medium';
            const pending = task.aiCreated && !task.validated;
            // Urgency: own due_date OR earliest subtask due_date if task has none
            const taskDays = window.daysUntil?.(task.dueDate);
            const taskUrgent = !isDone && taskDays !== null && taskDays !== undefined && taskDays <= 5;
            const taskUrgColor = taskUrgent ? window.urgencyColor?.(task.dueDate) : null;
            const subMin = window.descendantMinDays?.({ subtasks: task.subtasks || [] });
            const subUrgent = !taskUrgent && !isDone && subMin !== null && subMin !== undefined && subMin <= 5;
            const subColor = subUrgent ? (subMin <= 0 ? '#FF003F' : window.URGENCY_COLORS?.[Math.max(0, Math.min(5, subMin))]) : null;
            const sideBorder = taskUrgent ? taskUrgColor : (pending ? '#8B5CF6' : (draggingId === key ? m.color : '#1c1c1c'));
            return (
              <div key={key}
                draggable
                onDragStart={(e) => { setDraggingId(key); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', key); }}
                onDragEnd={() => setDraggingId(null)}
                onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }}
                onDrop={(e) => { e.preventDefault(); const from = e.dataTransfer.getData('text/plain'); if (from && from !== key) swapOrder(from, key); setDraggingId(null); }}
                onClick={() => setOpenTask({ projectId: project.id, milestoneId: milestone.id, taskId: task.id })}
                style={{
                  background: taskUrgent ? `${taskUrgColor}08` : (pending ? 'rgba(139,92,246,0.06)' : '#0d0d0d'),
                  borderTop: `1px ${pending && !taskUrgent ? 'dashed' : 'solid'} ${sideBorder}`,
                  borderRight: `1px ${pending && !taskUrgent ? 'dashed' : 'solid'} ${sideBorder}`,
                  borderBottom: `1px ${pending && !taskUrgent ? 'dashed' : 'solid'} ${sideBorder}`,
                  borderLeft: `3px solid ${PRI_COLOR[pri]}`,
                  borderRadius: 12, padding: 14, cursor: 'pointer',
                  position: 'relative', overflow: 'hidden',
                  display: 'flex', flexDirection: 'column', gap: 10,
                  minHeight: 150,
                  opacity: draggingId === key ? 0.5 : (isDone ? 0.6 : 1),
                  boxShadow: taskUrgent ? `0 0 14px ${taskUrgColor}55` : 'none',
                  transition: 'all 0.15s',
                }}
                onMouseEnter={(e) => { if (draggingId !== key && !pending && !taskUrgent) { e.currentTarget.style.borderTopColor = m.color + '55'; e.currentTarget.style.borderRightColor = m.color + '55'; e.currentTarget.style.borderBottomColor = m.color + '55'; } }}
                onMouseLeave={(e) => { if (draggingId !== key && !pending && !taskUrgent) { e.currentTarget.style.borderTopColor = '#1c1c1c'; e.currentTarget.style.borderRightColor = '#1c1c1c'; e.currentTarget.style.borderBottomColor = '#1c1c1c'; } }}
              >
                {pending && (
                  <div style={{ position: 'absolute', top: -8, right: 14, background: '#0a0a0a', padding: '0 8px', fontSize: 9, color: '#8B5CF6', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>
                    ✦ AI · review
                  </div>
                )}
                {/* Top row: checkbox + breadcrumb + grab indicator */}
                <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
                  <input type="checkbox" checked={!!task.done} onChange={() => toggleDone(row)} onClick={(e) => e.stopPropagation()}
                    style={{ width: 16, height: 16, accentColor: taskUrgent ? taskUrgColor : m.color, cursor: 'pointer', flexShrink: 0, marginTop: 2 }} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 8, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: 3, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                      <span style={{ color: project.color }}>{project.name}</span>
                      <span style={{ margin: '0 5px', color: '#222' }}>›</span>
                      <span style={{ color: milestone.color }}>{milestone.label}</span>
                    </div>
                  </div>
                  <div style={{ fontSize: 11, color: '#2a2a2a', cursor: 'grab' }} title="Drag to reorder">⋮⋮</div>
                </div>

                {/* Title */}
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, flex: 1 }}>
                  <span style={{ fontSize: 14, fontWeight: 700, color: isDone ? '#555' : '#e8e8e8', textDecoration: isDone ? 'line-through' : 'none', lineHeight: 1.4 }}>
                    {task.label}
                  </span>
                  {subUrgent && <window.UrgencyTriangle item={{ subtasks: task.subtasks || [] }} includeSelf={false} size={12} />}
                </div>

                {/* Footer row: pri + mode + today + due + progress + sprint */}
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                  <span style={{ fontSize: 9, fontWeight: 700, color: PRI_COLOR[pri], textTransform: 'uppercase', letterSpacing: '0.06em' }}>● {pri}</span>
                  <span style={{ fontSize: 9, color: m.color, textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 600 }}>{m.label}</span>
                  {task.today && <span style={{ fontSize: 9, color: '#EAB308', fontWeight: 700 }}>★ TODAY</span>}
                  {task.dueDate && <window.DueDateChip date={task.dueDate} compact />}
                  {task.subtasks?.length > 0 && (
                    <span style={{ fontSize: 9, color: '#666', fontWeight: 600 }}>{task.subtasks.filter((s) => s.done).length}/{task.subtasks.length} ✓</span>
                  )}
                  <div style={{ flex: 1 }} />
                  {tp > 0 && tp < 100 && (
                    <div style={{ width: 50, height: 4, background: '#161616', borderRadius: 2, overflow: 'hidden' }}>
                      <div style={{ height: '100%', width: `${tp}%`, background: m.color }} />
                    </div>
                  )}
                  <span style={{ fontSize: 11, color: tp === 100 ? '#22C55E' : '#666', fontWeight: 700 }}>{tp}%</span>
                  {pending && (
                    <button onClick={(e) => { e.stopPropagation(); window.updateTaskById(project.id, milestone.id, task.id, { aiCreated: false, validated: true }); }}
                      title="Mark as validated — removes the AI border"
                      style={{ background: '#22C55E22', color: '#22C55E', border: '1px solid #22C55E55', borderRadius: 6, padding: '4px 8px', fontSize: 10, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit' }}>✓</button>
                  )}
                  <button onClick={(e) => { e.stopPropagation(); sprintTask(row); }}
                    style={{ background: m.color, color: '#fff', border: 'none', borderRadius: 6, padding: '4px 10px', fontSize: 11, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit' }}>▶</button>
                </div>
              </div>
            );
          })}
        </div>
      )}

      {/* Editor */}
      {activeTaskRow && (
        <window.TaskEditor
          projectId={activeTaskRow.project.id}
          milestoneId={activeTaskRow.milestone.id}
          milestoneColor={activeTaskRow.milestone.color}
          milestoneLabel={`${activeTaskRow.project.name} › ${activeTaskRow.milestone.label}`}
          task={activeTaskRow.task}
          onClose={() => setOpenTask(null)}
          onDelete={() => {
            window.LF.update('projects', (ps = []) => ps.map((p) => p.id !== activeTaskRow.project.id ? p : ({
              ...p,
              branches: p.branches.map((b) => b.id !== activeTaskRow.milestone.id ? b : ({
                ...b,
                leaves: b.leaves.filter((lf) => lf.id !== activeTaskRow.task.id),
              })),
            })));
          }}
        />
      )}

      {/* Sprint launcher */}
      {launchingShow && <window.SprintLauncherModal launch={launchingShow} onClose={() => setLaunchingShow(null)} />}
    </div>
  );
}

// ─── TASK PICKER MODAL ─────────────────────────────────────────────
// Pick which tasks land on tomorrow's mission. Multi-select with checkboxes.
function TaskPickerModal({ onClose, onConfirm, title = 'Pick tasks' }) {
  const [projects] = useLFState('projects', []);
  const flat = [];
  for (const p of projects) for (const m of (p.branches || [])) {
    if (m.archived) continue;
    for (const t of (m.leaves || [])) {
      if (t.done) continue;
      flat.push({ project: p, milestone: m, task: t });
    }
  }
  const [picked, setPicked] = useState(new Set());
  const toggle = (key) => setPicked((s) => { const n = new Set(s); n.has(key) ? n.delete(key) : n.add(key); return n; });
  const commit = () => {
    const picks = Array.from(picked).map((k) => k.split(':'));
    onConfirm(picks.map(([pid, mid, tid]) => ({ projectId: pid, milestoneId: mid, taskId: tid })));
    onClose();
  };
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 700, background: 'rgba(0,0,0,0.85)', backdropFilter: 'blur(10px)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: 600, maxWidth: '92vw', maxHeight: '85vh', background: '#0a0a0a', borderRadius: 14, border: '1px solid #1e1e1e', display: 'flex', flexDirection: 'column' }}>
        <div style={{ padding: '16px 22px', borderBottom: '1px solid #161616' }}>
          <div style={{ fontSize: 13, fontWeight: 700, letterSpacing: '0.08em' }}>{title}</div>
          <div style={{ fontSize: 11, color: '#555', marginTop: 3 }}>{picked.size} selected · only active (incomplete) tasks shown</div>
        </div>
        <div style={{ overflowY: 'auto', padding: '14px 22px', flex: 1 }}>
          {flat.length === 0 ? (
            <div style={{ padding: 40, textAlign: 'center', color: '#444', fontSize: 12 }}>No active tasks. Add some in Projetecture first.</div>
          ) : flat.map((r) => {
            const key = `${r.project.id}:${r.milestone.id}:${r.task.id}`;
            const m = modeOf(r.task.mode);
            return (
              <div key={key} onClick={() => toggle(key)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 8, cursor: 'pointer', background: picked.has(key) ? '#1a1a1a' : 'transparent', marginBottom: 4 }}>
                <input type="checkbox" checked={picked.has(key)} readOnly style={{ width: 16, height: 16, accentColor: m.color, pointerEvents: 'none', flexShrink: 0 }} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: 3 }}>
                    <span style={{ color: r.project.color }}>{r.project.name}</span>
                    <span style={{ margin: '0 5px', color: '#222' }}>›</span>
                    <span style={{ color: r.milestone.color }}>{r.milestone.label}</span>
                  </div>
                  <div style={{ fontSize: 13, color: '#d0d0d0' }}>{r.task.label}</div>
                </div>
                <div style={{ width: 8, height: 8, borderRadius: '50%', background: m.color, opacity: 0.6 }} />
              </div>
            );
          })}
        </div>
        <div style={{ padding: '12px 22px', borderTop: '1px solid #161616', display: 'flex', gap: 8 }}>
          <button onClick={onClose} className="btn btn-ghost btn-sm">Cancel</button>
          <button onClick={commit} className="btn btn-primary btn-sm" disabled={picked.size === 0} style={{ marginLeft: 'auto' }}>Confirm ({picked.size})</button>
        </div>
      </div>
    </div>
  );
}
window.TaskPickerModal = TaskPickerModal;

// ─── DEBRIEF ──────────────────────────────────────────────────────────────────
function DebriefScreen() {
  // Two distinct fields: free-form note for tomorrow + the actual task list
  const [tomorrowNote, setTomorrowNote] = useLFState('tomorrowNote', '');
  const [tomorrowNoteDraft, setTomorrowNoteDraft] = useState(tomorrowNote);
  const [, setMissionTopText] = useLFState('missionTopText', ''); // visible on Mission Control
  const [showPicker, setShowPicker] = useState(false);

  // Pull today's score from the metrics store (resets at midnight automatically)
  const [metrics, setMetrics] = useLFState('dailyMetrics', null);
  useEffect(() => { window.lfMetricsToday(); }, []); // trigger date-flip check on mount
  const m = metrics && metrics.date === new Date().toISOString().slice(0, 10) ? metrics : window.lfMetricsToday();

  const [projects] = useLFState('projects', []);
  // Drift events = tasks marked today=true that aren't done yet (still pending late in day)
  let todayTotal = 0, todayDone = 0;
  // Deadline pressure — open items overdue / due today, across the whole tree
  let overdueOpen = 0, dueTodayOpen = 0, dueTodayClosed = 0;
  for (const p of projects) for (const b of (p.branches || [])) {
    if (b.archived) continue;
    // Milestone-level pressure
    const md = window.daysUntil?.(b.dueDate);
    if (md !== null && md !== undefined) {
      if (md < 0) overdueOpen++;
      else if (md === 0) dueTodayOpen++;
    }
    for (const t of (b.leaves || [])) {
      if (t.today) { todayTotal++; if (t.done || window.taskProgress(t) === 100) todayDone++; }
      const td = window.daysUntil?.(t.dueDate);
      if (td !== null && td !== undefined) {
        if (t.done) { if (td === 0) dueTodayClosed++; }
        else if (td < 0) overdueOpen++;
        else if (td === 0) dueTodayOpen++;
      }
      for (const s of (t.subtasks || [])) {
        const sd = window.daysUntil?.(s.dueDate);
        if (sd === null || sd === undefined) continue;
        if (s.done) { if (sd === 0) dueTodayClosed++; }
        else if (sd < 0) overdueOpen++;
        else if (sd === 0) dueTodayOpen++;
      }
    }
  }
  const dueTodayTotal = dueTodayOpen + dueTodayClosed;
  const completionPct = todayTotal ? Math.round(todayDone / todayTotal * 100) : 0;
  const deepWorkMins = Math.round((m.totalSprintMs || 0) / 60000);
  const deepHrs = Math.floor(deepWorkMins / 60), deepMin = deepWorkMins % 60;
  const deepWorkStr = deepHrs ? `${deepHrs}h ${deepMin}m` : `${deepMin}m`;
  // Day score: 70% from mission completion, 20% from sprint discipline, 10% interruption penalty
  const sprintDisc = m.sprintsCompleted + m.sprintsAbandoned > 0 ? Math.round(m.sprintsCompleted / (m.sprintsCompleted + m.sprintsAbandoned) * 100) : 0;
  const intPenalty = Math.min(20, (m.interruptions || 0) * 4);
  const rawScore = Math.round(completionPct * 0.7 + sprintDisc * 0.3) - intPenalty;
  const dayScore = Math.max(0, Math.min(100, rawScore));
  const scoreColor = dayScore >= 75 ? '#22C55E' : dayScore >= 50 ? '#F59E0B' : '#EF4444';
  const scoreLabel = dayScore >= 75 ? 'Strong Day' : dayScore >= 50 ? 'Solid' : dayScore >= 25 ? 'Mixed' : 'Rough';

  const done = m.completedTitles || [];
  const drifted = m.driftedTitles || [];

  // AI pattern analysis — runs once per day on first view
  const [assessment, setAssessment] = useLFState('dailyAssessment', null);
  const [apiKey] = useLFState('apiKey', '');
  const [apiModel] = useLFState('apiModel', 'claude-sonnet-4-6');
  const [historyMetrics] = useLFState('dailyMetricsHistory', []);
  const [assessing, setAssessing] = useState(false);
  const today = new Date().toISOString().slice(0, 10);
  const todaysAssessment = assessment && assessment.date === today ? assessment : null;

  useEffect(() => {
    if (todaysAssessment || assessing) return;
    if (!apiKey) return; // no key, skip silently
    setAssessing(true);
    (async () => {
      try {
        const summary = `Today's metrics:
- Completed tasks: ${m.completedTasks}${done.length ? ' — ' + done.slice(-5).join(', ') : ''}
- Sprints completed: ${m.sprintsCompleted}, abandoned: ${m.sprintsAbandoned}
- Total deep-work time: ${deepWorkStr}
- Interruptions logged: ${m.interruptions}
- Mission tasks: ${todayDone}/${todayTotal} done (${completionPct}%)
- Drift events: ${m.sprintsAbandoned + (drifted?.length || 0)}
${historyMetrics?.length ? '\nPast week sample:\n' + historyMetrics.slice(0, 7).map((h) => `  ${h.date}: ${h.completedTasks} done, ${h.sprintsCompleted} sprints, ${h.interruptions} interruptions`).join('\n') : ''}`;
        const result = await window.callClaude({
          apiKey, model: apiModel,
          system: window.USER_PROFILE_SYSTEM + '\n\nGive a sharp 2-3 sentence pattern analysis of the day below. Then one concrete suggestion for tomorrow. Direct and brief — no preamble.',
          messages: [{ role: 'user', content: summary }],
        });
        const text = (result.content || []).filter((b) => b.type === 'text').map((b) => b.text).join('') || 'No analysis available.';
        setAssessment({ date: today, text });
      } catch (e) {
        setAssessment({ date: today, text: `⚠ AI assessment failed: ${e.message}` });
      } finally {
        setAssessing(false);
      }
    })();
  }, [todaysAssessment, apiKey, today]);

  const aiText = todaysAssessment?.text || (assessing ? 'Running pattern analysis…' : (apiKey ? 'Analysis will appear here.' : 'Add API key in Settings to enable AI pattern analysis.'));

  const setMissionFromTasks = (picks) => {
    // Mark each picked task as today=true
    window.LF.update('projects', (ps = []) => ps.map((p) => {
      const pick = picks.find((x) => x.projectId === p.id);
      if (!picks.some((x) => x.projectId === p.id)) return p;
      return {
        ...p,
        branches: p.branches.map((b) => ({
          ...b,
          leaves: b.leaves.map((t) => {
            const isPicked = picks.some((x) => x.projectId === p.id && x.milestoneId === b.id && x.taskId === t.id);
            return isPicked ? { ...t, today: true } : t;
          }),
        })),
      };
    }));
  };

  const noteChanged = tomorrowNoteDraft !== tomorrowNote;
  const commitNote = () => { setTomorrowNote(tomorrowNoteDraft); setMissionTopText(tomorrowNoteDraft); };

  return (
    <div className="screen screen-enter">
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 14 }}>
        <div className="card" style={{ minWidth: 200, flex: '0 0 200px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 10, background: `${scoreColor}08`, borderColor: `${scoreColor}28` }}>
          <div className="card-label">Day Score</div>
          <div style={{ fontSize: 64, fontWeight: 800, color: scoreColor, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{dayScore}</div>
          <div style={{ fontSize: 11, color: '#3a3a3a' }}>out of 100</div>
          <span className="badge" style={{ background: `${scoreColor}1a`, color: scoreColor }}>{scoreLabel}</span>
        </div>
        <div className="card" style={{ flex: '1 1 360px', minWidth: 320 }}>
          <div className="card-label">Today's Metrics · {m.date}</div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))', gap: 18 }}>
            {[
              ['Deep work', deepWorkStr, '#3B82F6'],
              ['Completion', `${completionPct}%`, '#22C55E'],
              ['Tasks closed', m.completedTasks, '#8B5CF6'],
              ['Sprints', m.sprintsCompleted, '#3B82F6'],
              ['Abandoned', m.sprintsAbandoned, '#F59E0B'],
              ['Interruptions', m.interruptions, '#EF4444'],
              ['Due today', dueTodayTotal ? `${dueTodayClosed}/${dueTodayTotal}` : '0', dueTodayOpen > 0 ? '#FF003F' : '#22C55E'],
              ['Overdue', overdueOpen, overdueOpen > 0 ? '#FF003F' : '#666'],
            ].map(([label, val, col]) => (
              <div key={label}>
                <div style={{ fontSize: 10, color: '#3a3a3a', marginBottom: 5, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase' }}>{label}</div>
                <div style={{ fontSize: 22, fontWeight: 800, color: col, fontVariantNumeric: 'tabular-nums' }}>{val}</div>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 14 }}>
        <div className="card" style={{ flex: '1 1 280px', minWidth: 260 }}>
          <div className="card-label" style={{ color: '#22C55E' }}>Completed</div>
          {done.length === 0 ? (
            <div style={{ fontSize: 12, color: '#3a3a3a', fontStyle: 'italic', padding: '8px 0' }}>Nothing closed yet today.</div>
          ) : done.map((d, i) => (
            <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'flex-start', padding: '9px 0', borderBottom: i < done.length - 1 ? '1px solid #141414' : 'none' }}>
              <div style={{ color: '#22C55E', fontWeight: 700, flex: 'none', marginTop: 1 }}>✓</div>
              <div style={{ fontSize: 13, color: '#aaa', lineHeight: 1.45 }}>{d}</div>
            </div>
          ))}
        </div>
        <div className="card" style={{ flex: '1 1 280px', minWidth: 260 }}>
          <div className="card-label" style={{ color: '#F59E0B' }}>Drifted</div>
          {drifted.length === 0 ? (
            <div style={{ fontSize: 12, color: '#3a3a3a', fontStyle: 'italic', padding: '8px 0' }}>No drift events today.</div>
          ) : drifted.map((d, i) => (
            <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'flex-start', padding: '9px 0', borderBottom: i < drifted.length - 1 ? '1px solid #141414' : 'none' }}>
              <div style={{ color: '#F59E0B', flex: 'none', marginTop: 1 }}>→</div>
              <div style={{ fontSize: 13, color: '#aaa', lineHeight: 1.45 }}>{d}</div>
            </div>
          ))}
        </div>
        <div className="card" style={{ flex: '1 1 280px', minWidth: 260, background: 'rgba(139,92,246,0.04)', borderColor: 'rgba(139,92,246,0.15)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 12 }}>
            <div style={{ width: 6, height: 6, borderRadius: '50%', background: '#8B5CF6', boxShadow: '0 0 5px #8B5CF6', animation: assessing ? 'pulse 2s infinite' : 'none' }} />
            <div className="card-label" style={{ color: '#8B5CF6', marginBottom: 0 }}>AI Pattern Analysis</div>
          </div>
          <div style={{ fontSize: 13, lineHeight: 1.7, color: '#999', marginBottom: 18, whiteSpace: 'pre-wrap' }}>
            {aiText}
          </div>

          {/* Tomorrow's mission note — appears on top of mission card in Mission Control */}
          <div style={{ marginBottom: 14 }}>
            <div className="card-label" style={{ marginBottom: 8 }}>Tomorrow's Note · top of mission card</div>
            <textarea
              value={tomorrowNoteDraft}
              onChange={(e) => setTomorrowNoteDraft(e.target.value)}
              placeholder="What's tomorrow about? One line — appears at the top of Mission Control."
              style={{ width: '100%', minHeight: 60, background: '#0a0a0a', border: '1px solid #1c1c1c', borderRadius: 8, padding: '9px 12px', color: '#e8e8e8', fontFamily: 'inherit', fontSize: 13, outline: 'none', boxSizing: 'border-box', resize: 'vertical', lineHeight: 1.5 }} />
            {noteChanged && (
              <button onClick={commitNote} className="btn btn-primary btn-sm" style={{ marginTop: 8, width: '100%' }}>
                ↑ Add to top of tomorrow's mission
              </button>
            )}
          </div>

          {/* Pick the actual tasks for tomorrow */}
          <button onClick={() => setShowPicker(true)} className="btn btn-sm" style={{ width: '100%', background: '#8B5CF6', color: '#fff', border: 'none', fontWeight: 700 }}>
            ★ Set tomorrow's mission tasks
          </button>
        </div>
      </div>

      {showPicker && <TaskPickerModal onClose={() => setShowPicker(false)} onConfirm={setMissionFromTasks} title="Pick tomorrow's mission" />}
    </div>
  );
}

Object.assign(window, { InboxScreen, TasksScreen, DebriefScreen });
