/* global React, Icon, IconBtn, db, useKvRecord */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ============================================================
// Time Tracker — shared helpers, timer engine, pickers
// ------------------------------------------------------------
// Data model (Task -> Session):
//   db.timeTasks   — { id, description, projectId, tags[], todoId,
//                      notes, archived, lastActiveAt, createdAt, updatedAt }
//                    A task is a unit of work; its total time is the sum
//                    of all its sessions. Resuming a task adds a session,
//                    it never creates a second task.
//   db.timeEntries — SESSIONS: { id, taskId, startedAt, stoppedAt, createdAt, updatedAt }
//   db.projects    — { id, name, color, client, archived }
//   db.kv 'timeTrackerRunning' — { taskId, description, projectId,
//                      tags[], todoId, startedAt } | null
//
// The running timer is NOT a session: it lives in kv with an absolute
// startedAt timestamp (elapsed survives reloads/sleep, computed from
// startedAt vs now). It points at a real task (taskId) and mirrors that
// task's fields for cheap reads. Stopping materializes a session under
// the task; the mirrored fields stay in sync via ttUpdateRunning.
// ============================================================

const TT_RUNNING_KEY  = 'timeTrackerRunning';
const TT_FILTERS_KEY  = 'timeTrackerFilters';
const TT_SETTINGS_KEY = 'timeTrackerSettings';
const TT_VIEW_KEY     = 'timeTrackerView';

const TT_PALETTE = ['#60a5fa','#fbbf24','#34d399','#f87171','#c4a0ff','#06b6d4','#f472b6','#a78bfa','#94a3b8'];

const TT_DEFAULT_SETTINGS = {
  weekStartsMonday: true,
  listSeconds: false,   // show seconds in list/summary durations
};

// ------------------------------------------------------------
// Formatting
// ------------------------------------------------------------
function ttFmtDuration(ms, { seconds = true } = {}) {
  const total = Math.max(0, Math.round(ms / 1000));
  const h = Math.floor(total / 3600);
  const m = Math.floor((total % 3600) / 60);
  const s = total % 60;
  if (!seconds) {
    if (h === 0) return `${m}m`;
    return `${h}h ${String(m).padStart(2, '0')}m`;
  }
  return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
}

function ttFmtClock(ts) {
  return new Date(ts).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
}

function ttDayKey(ts) {
  const d = new Date(ts);
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}

function ttStartOfDay(ts) {
  const d = new Date(ts); d.setHours(0, 0, 0, 0); return d.getTime();
}

function ttStartOfWeek(ts, weekStartsMonday = true) {
  const d = new Date(ts); d.setHours(0, 0, 0, 0);
  const day = d.getDay(); // 0 = Sun
  const diff = weekStartsMonday ? (day === 0 ? 6 : day - 1) : day;
  d.setDate(d.getDate() - diff);
  return d.getTime();
}

function ttFmtDayLabel(ts) {
  const today = ttStartOfDay(Date.now());
  const day = ttStartOfDay(ts);
  if (day === today) return 'Today';
  if (day === today - 86400000) return 'Yesterday';
  return new Date(ts).toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' });
}

function ttFmtWeekLabel(weekStart, weekStartsMonday = true) {
  const thisWeek = ttStartOfWeek(Date.now(), weekStartsMonday);
  if (weekStart === thisWeek) return 'This week';
  if (weekStart === thisWeek - 7 * 86400000) return 'Last week';
  const end = new Date(weekStart + 6 * 86400000);
  const s = new Date(weekStart);
  return `${s.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${end.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })}`;
}

// HTML <input type="date"> / <input type="time"> helpers (local time)
function ttDateInputVal(ts) { return ttDayKey(ts); }
function ttTimeInputVal(ts) {
  const d = new Date(ts);
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}
function ttParseDateTime(dateStr, timeStr) {
  if (!dateStr || !timeStr) return null;
  const [y, mo, da] = dateStr.split('-').map(Number);
  const [h, mi] = timeStr.split(':').map(Number);
  return new Date(y, mo - 1, da, h, mi, 0, 0).getTime();
}

// ------------------------------------------------------------
// Timer engine — all mutations go through these
// ------------------------------------------------------------
// The running state is a LIST of concurrent timers (kv TT_RUNNING_KEY).
// Each entry: { id, taskId, description, projectId, tags, todoId, cycleId,
// startedAt }. Multiple tasks can be tracked at once; each has its own
// absolute startedAt so elapsed survives reloads. Legacy single-object or
// null values are normalized to a list on read.
function ttRunId() {
  return (self.crypto?.randomUUID?.() || ('r' + Date.now() + Math.random().toString(36).slice(2, 8)));
}
function ttNormalizeRunning(v) {
  if (!v) return [];
  if (Array.isArray(v)) return v.map(e => (e.id ? e : { ...e, id: ttRunId() }));
  // legacy single object
  return [{ ...v, id: v.id || ttRunId() }];
}
async function ttGetRunningList() {
  const r = await db.kv.get(TT_RUNNING_KEY);
  return ttNormalizeRunning(r?.v);
}
async function ttSetRunningList(list) {
  await db.kv.put({ k: TT_RUNNING_KEY, v: list });
}
// Back-compat: the most-recently-started single timer, or null.
async function ttGetRunning() {
  const list = await ttGetRunningList();
  return list.length ? list[list.length - 1] : null;
}

// Find an open (non-archived) task matching description + project + todo,
// or create a fresh one. A blank description never merges — each blank
// start gets its own task. Returns the task record.
async function ttFindOrCreateTask({ description = '', projectId = null, tags = [], todoId = null, notes = '', cycleId = null } = {}) {
  const desc = (description || '').trim();
  if (desc) {
    const tasks = await db.timeTasks.list();
    const found = tasks.find(t =>
      !t.archived &&
      (t.description || '').trim().toLowerCase() === desc.toLowerCase() &&
      (t.projectId || null) === (projectId || null) &&
      (t.todoId || null) === (todoId || null)
    );
    if (found) {
      const patch = {};
      const merged = [...new Set([...(found.tags || []), ...tags])];
      if (merged.length !== (found.tags || []).length) patch.tags = merged;
      // adopt a cycle if the task has none yet and one was supplied
      if (cycleId && !found.cycleId) patch.cycleId = cycleId;
      if (Object.keys(patch).length) {
        return db.timeTasks.put({ ...found, ...patch, lastActiveAt: Date.now() });
      }
      return found;
    }
  }
  return db.timeTasks.put({
    description: desc, projectId: projectId || null, tags: tags || [],
    todoId: todoId || null, notes: notes || '', cycleId: cycleId || null, archived: false,
    lastActiveAt: Date.now(),
  });
}

// Build a running-timer entry from a task (mirrors task fields).
function ttRunningFromTask(task) {
  return {
    id: ttRunId(),
    taskId: task.id,
    description: task.description || '',
    projectId: task.projectId || null,
    tags: task.tags || [],
    todoId: task.todoId || null,
    cycleId: task.cycleId || null,
    startedAt: Date.now(),
  };
}

// Stop ONE running timer (by entry id, or the most recent if omitted) and
// save a session under its task. Other running timers keep going.
async function ttStopTimer(runId) {
  const list = await ttGetRunningList();
  if (!list.length) return null;
  const entry = runId ? list.find(e => e.id === runId) : list[list.length - 1];
  if (!entry) return null;
  const stoppedAt = Date.now();
  const startedAt = Math.min(entry.startedAt, stoppedAt - 1000);
  let taskId = entry.taskId;
  if (!taskId) {
    const t = await ttFindOrCreateTask(entry);
    taskId = t.id;
  }
  const session = await db.timeEntries.put({ taskId, startedAt, stoppedAt });
  const task = await db.timeTasks.get(taskId);
  if (task) await db.timeTasks.put({ ...task, lastActiveAt: stoppedAt });
  await ttSetRunningList(list.filter(e => e.id !== entry.id));
  return session;
}

// Stop every running timer, saving a session for each.
async function ttStopAll() {
  const list = await ttGetRunningList();
  for (const e of list) await ttStopTimer(e.id);
  return list.length;
}

// Start a timer. ADDS a concurrent timer (does not stop others).
// If the task is already running, focuses it instead of duplicating.
async function ttStartTimer({ description = '', projectId = null, tags = [], todoId = null, cycleId = null } = {}) {
  const task = await ttFindOrCreateTask({ description, projectId, tags, todoId, cycleId });
  const list = await ttGetRunningList();
  if (list.some(e => e.taskId === task.id)) return task; // already running
  await ttSetRunningList([...list, ttRunningFromTask(task)]);
  return task;
}

// Resume an existing task — adds a NEW concurrent session for it (or
// focuses it if it's already running).
async function ttResumeTask(taskId) {
  const task = await db.timeTasks.get(taskId);
  if (!task) return null;
  const list = await ttGetRunningList();
  if (list.some(e => e.taskId === taskId)) return task; // already running
  await db.timeTasks.put({ ...task, lastActiveAt: Date.now() });
  await ttSetRunningList([...list, ttRunningFromTask(task)]);
  return task;
}

// Patch one running timer (by id, or the most recent). Task-level fields
// propagate to the task record so live edits stick to the task.
async function ttUpdateRunning(runId, patch) {
  // back-compat: ttUpdateRunning(patch) updates the most recent
  if (patch === undefined) { patch = runId; runId = null; }
  const list = await ttGetRunningList();
  if (!list.length) return;
  const entry = runId ? list.find(e => e.id === runId) : list[list.length - 1];
  if (!entry) return;
  await ttSetRunningList(list.map(e => e.id === entry.id ? { ...e, ...patch } : e));
  const TASK_FIELDS = ['description', 'projectId', 'tags', 'todoId', 'notes', 'cycleId'];
  if (entry.taskId && TASK_FIELDS.some(k => k in patch)) {
    const task = await db.timeTasks.get(entry.taskId);
    if (task) {
      const taskPatch = {};
      for (const k of TASK_FIELDS) if (k in patch) taskPatch[k] = patch[k];
      await db.timeTasks.put({ ...task, ...taskPatch });
    }
  }
}

// Discard one running timer without saving. If its task has no sessions
// (freshly created this start), remove the task too.
async function ttDiscardRunning(runId) {
  const list = await ttGetRunningList();
  const entry = runId ? list.find(e => e.id === runId) : list[list.length - 1];
  await ttSetRunningList(list.filter(e => e.id !== (entry && entry.id)));
  if (entry?.taskId) {
    const sessions = await db.timeEntries.list({ where: s => s.taskId === entry.taskId });
    if (sessions.length === 0) await db.timeTasks.delete(entry.taskId);
  }
}

// Delete a task and all of its sessions.
async function ttDeleteTask(taskId) {
  const sessions = await db.timeEntries.list({ where: s => s.taskId === taskId });
  for (const s of sessions) await db.timeEntries.delete(s.id);
  await db.timeTasks.delete(taskId);
}

// Set a single task's archived flag.
async function ttSetTaskArchived(taskId, archived) {
  const t = await db.timeTasks.get(taskId);
  if (!t) return;
  await db.timeTasks.put({ ...t, archived: !!archived, archivedAt: archived ? Date.now() : null });
}

// ------------------------------------------------------------
// Cycles — a named grouping of tasks (e.g. a payrun period).
// ------------------------------------------------------------
async function ttEnsureCycle(name) {
  const clean = (name || '').trim();
  if (!clean) return null;
  const cycles = await db.timeCycles.list();
  const found = cycles.find(c => (c.name || '').toLowerCase() === clean.toLowerCase());
  if (found) return found;
  return db.timeCycles.put({
    name: clean,
    color: TT_PALETTE[cycles.length % TT_PALETTE.length],
    archived: false,
  });
}

function ttCycleById(cycles, id) {
  if (!id) return null;
  return (cycles || []).find(c => c.id === id) || null;
}

// Archive a cycle: archive every task in it, then the cycle itself.
async function ttArchiveCycle(cycleId) {
  const c = await db.timeCycles.get(cycleId);
  if (!c) return;
  const tasks = await db.timeTasks.list({ where: t => t.cycleId === cycleId });
  for (const t of tasks) {
    if (!t.archived) await db.timeTasks.put({ ...t, archived: true, archivedAt: Date.now() });
  }
  await db.timeCycles.put({ ...c, archived: true, archivedAt: Date.now() });
}

// Restore a cycle: unarchive the cycle and all of its tasks.
async function ttRestoreCycle(cycleId) {
  const c = await db.timeCycles.get(cycleId);
  if (!c) return;
  const tasks = await db.timeTasks.list({ where: t => t.cycleId === cycleId });
  for (const t of tasks) {
    if (t.archived) await db.timeTasks.put({ ...t, archived: false, archivedAt: null });
  }
  await db.timeCycles.put({ ...c, archived: false, archivedAt: null });
}

// Delete a cycle: detach its tasks (cycleId → null), keep the tasks.
async function ttDeleteCycle(cycleId) {
  const tasks = await db.timeTasks.list({ where: t => t.cycleId === cycleId });
  for (const t of tasks) await db.timeTasks.put({ ...t, cycleId: null });
  await db.timeCycles.delete(cycleId);
}

function useTtRunning() {
  const v = useKvRecord(TT_RUNNING_KEY);
  const list = ttNormalizeRunning(v);
  return list.length ? list[list.length - 1] : null;
}

// All concurrent running timers (most-recent last).
function useTtRunningList() {
  const v = useKvRecord(TT_RUNNING_KEY);
  return useMemo(() => ttNormalizeRunning(v), [v]);
}

function useTtSettings() {
  const v = useKvRecord(TT_SETTINGS_KEY);
  const settings = { ...TT_DEFAULT_SETTINGS, ...(v || {}) };
  const update = useCallback((patch) => {
    db.kv.get(TT_SETTINGS_KEY).then(rec => {
      db.kv.put({ k: TT_SETTINGS_KEY, v: { ...TT_DEFAULT_SETTINGS, ...(rec?.v || {}), ...patch } });
    });
  }, []);
  return [settings, update];
}

// 1 Hz tick — returns `now`, only ticking while `active`
function useTtNow(active) {
  const [now, setNow] = useState(Date.now());
  useEffect(() => {
    if (!active) return;
    setNow(Date.now());
    const t = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(t);
  }, [active]);
  return now;
}

// ------------------------------------------------------------
// Projects — keep tracker projects in sync with todo project names.
// Idempotent: safe to call on every tracker mount. This is the
// integration point — Todos' string `project` maps to a project
// record by (case-insensitive) name.
// ------------------------------------------------------------
async function ttEnsureProjectsFromTodos() {
  const [todos, projects] = await Promise.all([db.todos.list(), db.projects.list()]);
  const existing = new Set(projects.map(p => (p.name || '').toLowerCase()));
  const names = [...new Set(todos.map(t => t.project).filter(Boolean))];
  let i = projects.length;
  for (const name of names) {
    if (existing.has(name.toLowerCase())) continue;
    existing.add(name.toLowerCase());
    await db.projects.put({ name, color: TT_PALETTE[i++ % TT_PALETTE.length], client: null, archived: false });
  }
}

// Find-or-create a project by name. Returns the project record or null.
async function ttEnsureProject(name) {
  const clean = (name || '').trim();
  if (!clean) return null;
  const projects = await db.projects.list();
  const found = projects.find(p => (p.name || '').toLowerCase() === clean.toLowerCase());
  if (found) return found;
  return db.projects.put({
    name: clean,
    color: TT_PALETTE[projects.length % TT_PALETTE.length],
    client: null,
    archived: false,
  });
}

function ttProjectById(projects, id) {
  if (!id) return null;
  return (projects || []).find(p => p.id === id) || null;
}

// ------------------------------------------------------------
// Duration helpers
//   ttSessionMs  — one session (running session, no stoppedAt, uses now)
//   ttTaskMs     — sum of a list of sessions
//   ttEntryMs    — back-compat alias (a session is the old "entry")
// ------------------------------------------------------------
function ttSessionMs(s) {
  return Math.max(0, (s.stoppedAt || Date.now()) - s.startedAt);
}
function ttTaskMs(sessions) {
  return (sessions || []).reduce((a, s) => a + ttSessionMs(s), 0);
}
function ttEntryMs(e) {
  return Math.max(0, (e.stoppedAt || Date.now()) - e.startedAt);
}
function ttTaskById(tasks, id) {
  if (!id) return null;
  return (tasks || []).find(t => t.id === id) || null;
}

// ------------------------------------------------------------
// <TtProjectDot> — the colored dot used everywhere
// ------------------------------------------------------------
function TtProjectDot({ project, size = 8 }) {
  return (
    <span
      className="trk-proj-dot"
      style={{ background: project ? project.color : 'var(--text-faint)', width: size, height: size }}
    ></span>
  );
}

// ------------------------------------------------------------
// <TtProjectPicker> — popover selector with inline create
// ------------------------------------------------------------
function TtProjectPicker({ projects, value, onChange, compact = false, allowCreate = true }) {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState('');
  const ref = useRef(null);
  const selected = ttProjectById(projects, value);

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);
  useEffect(() => { if (!open) setQ(''); }, [open]);

  const visible = (projects || [])
    .filter(p => !p.archived)
    .filter(p => !q || (p.name || '').toLowerCase().includes(q.toLowerCase()))
    .sort((a, b) => (a.name || '').localeCompare(b.name || ''));

  const create = async () => {
    const p = await ttEnsureProject(q);
    if (p) { onChange(p.id); setOpen(false); }
  };

  return (
    <div className="trk-picker" ref={ref}>
      <button
        type="button"
        className={`trk-picker-btn ${selected ? 'has-value' : ''} ${compact ? 'is-compact' : ''}`}
        onClick={() => setOpen(o => !o)}
        title="Project"
      >
        <TtProjectDot project={selected}></TtProjectDot>
        <span className="trk-picker-label">{selected ? selected.name : 'Project'}</span>
        <Icon name="expand_more" className="trail"></Icon>
      </button>
      {open && (
        <div className="trk-picker-menu">
          <div className="trk-picker-search">
            <Icon name="search"></Icon>
            <input
              autoFocus
              placeholder="Find or create…"
              value={q}
              onChange={e => setQ(e.target.value)}
              onKeyDown={e => {
                if (e.key === 'Enter' && allowCreate && q.trim() && visible.length === 0) create();
                if (e.key === 'Escape') setOpen(false);
              }}
            />
          </div>
          <div className="trk-picker-scroll">
            <button
              type="button"
              className={`trk-picker-item ${!value ? 'is-active' : ''}`}
              onClick={() => { onChange(null); setOpen(false); }}
            >
              <TtProjectDot project={null}></TtProjectDot>
              <span>No project</span>
              {!value && <Icon name="check" className="trail"></Icon>}
            </button>
            {visible.map(p => (
              <button
                type="button"
                key={p.id}
                className={`trk-picker-item ${value === p.id ? 'is-active' : ''}`}
                onClick={() => { onChange(p.id); setOpen(false); }}
              >
                <TtProjectDot project={p}></TtProjectDot>
                <span>{p.name}</span>
                {p.client && <em className="trk-picker-client">{p.client}</em>}
                {value === p.id && <Icon name="check" className="trail"></Icon>}
              </button>
            ))}
            {visible.length === 0 && !q && (
              <div className="trk-picker-empty">No projects yet</div>
            )}
          </div>
          {allowCreate && q.trim() && !visible.some(p => (p.name || '').toLowerCase() === q.trim().toLowerCase()) && (
            <button type="button" className="trk-picker-item is-new" onClick={create}>
              <Icon name="add"></Icon>
              <span>Create “{q.trim()}”</span>
            </button>
          )}
        </div>
      )}
    </div>
  );
}

// ------------------------------------------------------------
// <TtTagPicker> — popover; reuses the workspace's free-form tags.
// Suggestions are pooled from todos + existing entries.
// ------------------------------------------------------------
function TtTagPicker({ value = [], onChange, knownTags = [], compact = false }) {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState('');
  const ref = useRef(null);

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);
  useEffect(() => { if (!open) setQ(''); }, [open]);

  const toggle = (tag) => {
    if (value.includes(tag)) onChange(value.filter(t => t !== tag));
    else onChange([...value, tag]);
  };
  const addNew = () => {
    const t = q.trim().replace(/^#/, '');
    if (!t) return;
    if (!value.includes(t)) onChange([...value, t]);
    setQ('');
  };

  const pool = [...new Set([...knownTags, ...value])].sort();
  const visible = pool.filter(t => !q || t.toLowerCase().includes(q.toLowerCase()));

  return (
    <div className="trk-picker" ref={ref}>
      <button
        type="button"
        className={`trk-picker-btn ${value.length ? 'has-value' : ''} ${compact ? 'is-compact' : ''}`}
        onClick={() => setOpen(o => !o)}
        title="Tags"
      >
        <Icon name="tag"></Icon>
        <span className="trk-picker-label">{value.length ? value.join(', ') : 'Tags'}</span>
        <Icon name="expand_more" className="trail"></Icon>
      </button>
      {open && (
        <div className="trk-picker-menu">
          <div className="trk-picker-search">
            <Icon name="tag"></Icon>
            <input
              autoFocus
              placeholder="Find or add a tag…"
              value={q}
              onChange={e => setQ(e.target.value)}
              onKeyDown={e => {
                if (e.key === 'Enter') { e.preventDefault(); addNew(); }
                if (e.key === 'Escape') setOpen(false);
              }}
            />
          </div>
          <div className="trk-picker-scroll">
            {visible.map(t => (
              <button type="button" key={t} className="trk-picker-item" onClick={() => toggle(t)}>
                <span className={`trk-tag-check ${value.includes(t) ? 'is-on' : ''}`}>
                  {value.includes(t) && <Icon name="check"></Icon>}
                </span>
                <span>#{t}</span>
              </button>
            ))}
            {visible.length === 0 && !q.trim() && <div className="trk-picker-empty">No tags yet — type to add one</div>}
          </div>
          {q.trim() && !pool.includes(q.trim().replace(/^#/, '')) && (
            <button type="button" className="trk-picker-item is-new" onClick={addNew}>
              <Icon name="add"></Icon>
              <span>Add “#{q.trim().replace(/^#/, '')}”</span>
            </button>
          )}
        </div>
      )}
    </div>
  );
}

// ------------------------------------------------------------
// <TtCyclePicker> — popover; pick or create a cycle for a task.
// ------------------------------------------------------------
function TtCyclePicker({ cycles, value, onChange, compact = false, allowCreate = true }) {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState('');
  const ref = useRef(null);
  const selected = ttCycleById(cycles, value);

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);
  useEffect(() => { if (!open) setQ(''); }, [open]);

  const visible = (cycles || [])
    .filter(c => !c.archived)
    .filter(c => !q || (c.name || '').toLowerCase().includes(q.toLowerCase()))
    .sort((a, b) => (a.name || '').localeCompare(b.name || ''));

  const create = async () => {
    const c = await ttEnsureCycle(q);
    if (c) { onChange(c.id); setOpen(false); }
  };

  return (
    <div className="trk-picker" ref={ref}>
      <button
        type="button"
        className={`trk-picker-btn ${selected ? 'has-value' : ''} ${compact ? 'is-compact' : ''}`}
        onClick={() => setOpen(o => !o)}
        title="Cycle"
      >
        <Icon name="cycle"></Icon>
        <span className="trk-picker-label">{selected ? selected.name : 'Cycle'}</span>
        <Icon name="expand_more" className="trail"></Icon>
      </button>
      {open && (
        <div className="trk-picker-menu">
          <div className="trk-picker-search">
            <Icon name="search"></Icon>
            <input
              autoFocus
              placeholder="Find or create…"
              value={q}
              onChange={e => setQ(e.target.value)}
              onKeyDown={e => {
                if (e.key === 'Enter' && allowCreate && q.trim() && visible.length === 0) create();
                if (e.key === 'Escape') setOpen(false);
              }}
            />
          </div>
          <div className="trk-picker-scroll">
            <button
              type="button"
              className={`trk-picker-item ${!value ? 'is-active' : ''}`}
              onClick={() => { onChange(null); setOpen(false); }}
            >
              <span className="trk-proj-dot" style={{ background: 'var(--text-faint)', width: 8, height: 8 }}></span>
              <span>No cycle</span>
              {!value && <Icon name="check" className="trail"></Icon>}
            </button>
            {visible.map(c => (
              <button
                type="button"
                key={c.id}
                className={`trk-picker-item ${value === c.id ? 'is-active' : ''}`}
                onClick={() => { onChange(c.id); setOpen(false); }}
              >
                <span className="trk-proj-dot" style={{ background: c.color || 'var(--text-faint)', width: 8, height: 8 }}></span>
                <span>{c.name}</span>
                {value === c.id && <Icon name="check" className="trail"></Icon>}
              </button>
            ))}
            {visible.length === 0 && !q && (
              <div className="trk-picker-empty">No cycles yet</div>
            )}
          </div>
          {allowCreate && q.trim() && !visible.some(c => (c.name || '').toLowerCase() === q.trim().toLowerCase()) && (
            <button type="button" className="trk-picker-item is-new" onClick={create}>
              <Icon name="add"></Icon>
              <span>Create “{q.trim()}”</span>
            </button>
          )}
        </div>
      )}
    </div>
  );
}

// ------------------------------------------------------------
// Home widget — running timer, or quick-resume the latest task and
// start a new one without leaving home.
// ------------------------------------------------------------
function TimeTrackerWidget({ ctx }) {
  const { setTool } = ctx;
  const runningList = useTtRunningList();
  const now = useTtNow(runningList.length > 0);
  const entries = useLiveQuery('timeEntries');
  const tasks = useLiveQuery('timeTasks');
  const cycles = useLiveQuery('timeCycles');
  const projects = useLiveQuery('projects');
  const [draft, setDraft] = useState('');

  const todayStart = ttStartOfDay(Date.now());
  const todayMs = useMemo(() => {
    let ms = (entries || []).filter(e => e.startedAt >= todayStart).reduce((a, e) => a + ttEntryMs(e), 0);
    for (const r of runningList) ms += Date.now() - Math.max(r.startedAt, todayStart);
    return ms;
  }, [entries, runningList, now, todayStart]);

  const latest = useMemo(() => {
    const runningTaskIds = new Set(runningList.map(r => r.taskId));
    return (tasks || [])
      .filter(t => !t.archived && !runningTaskIds.has(t.id))
      .sort((a, b) => (b.lastActiveAt || 0) - (a.lastActiveAt || 0))[0] || null;
  }, [tasks, runningList]);
  const latestProj = ttProjectById(projects || [], latest && latest.projectId);

  // Active (non-archived) cycles with their total hours
  const cycleRows = useMemo(() => {
    const taskCycle = new Map();
    for (const t of tasks || []) if (t.cycleId) taskCycle.set(t.id, t.cycleId);
    const ms = new Map();
    for (const s of entries || []) {
      const c = taskCycle.get(s.taskId);
      if (c) ms.set(c, (ms.get(c) || 0) + ttSessionMs(s));
    }
    for (const r of runningList) {
      const c = taskCycle.get(r.taskId);
      if (c) ms.set(c, (ms.get(c) || 0) + (Date.now() - r.startedAt));
    }
    return (cycles || [])
      .filter(c => !c.archived)
      .map(c => ({ ...c, ms: ms.get(c.id) || 0 }))
      .sort((a, b) => b.ms - a.ms);
  }, [cycles, tasks, entries, runningList, now]);

  const openCycle = (id) => {
    db.kv.put({ k: TT_FILTERS_KEY, v: { range: 'all', from: null, to: null, projectId: null, cycleId: id, tag: null, q: '', showArchived: true } });
    setTool('timetracker');
  };

  const startNew = () => {
    ttStartTimer({ description: draft.trim() });
    setDraft('');
  };

  const sortedRunning = [...runningList].sort((a, b) => a.startedAt - b.startedAt);

  return (
    <div className="trk-widget">
      <div className="trk-widget-topline">
        <button className="trk-widget-total" onClick={() => setTool('timetracker')} title="Open time tracker">
          <span className="v">{ttFmtDuration(todayMs, { seconds: false })}</span>
          <span className="l">tracked today</span>
        </button>
        {runningList.length > 0 && (
          <button className="trk-widget-stopall" onClick={() => ttStopAll()} title="Stop all running timers">
            <Icon name="stop"></Icon>Stop all
          </button>
        )}
      </div>

      {sortedRunning.length > 0 && (
        <div className="trk-widget-runlist">
          {sortedRunning.map(r => {
            const proj = ttProjectById(projects || [], r.projectId);
            return (
              <div className="trk-widget-run" key={r.id}>
                <span className="trk-widget-pulse"></span>
                <button className="trk-widget-run-main" onClick={() => setTool('timetracker')} title="Open time tracker">
                  <span className="trk-widget-run-desc">{r.description || '(no description)'}</span>
                  {r.projectId && (
                    <span className="trk-widget-run-proj">
                      <TtProjectDot project={proj}></TtProjectDot>{proj ? proj.name : '(deleted)'}
                    </span>
                  )}
                </button>
                <span className="trk-widget-run-ms">{ttFmtDuration(now - r.startedAt)}</span>
                <button className="trk-widget-run-stop" onClick={() => ttStopTimer(r.id)} title="Stop">
                  <Icon name="stop"></Icon>
                </button>
              </div>
            );
          })}
        </div>
      )}

      <div className="trk-widget-start">
        <input
          className="trk-widget-start-input"
          placeholder={runningList.length ? 'Start another task…' : 'Start something new…'}
          value={draft}
          onChange={e => setDraft(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter') startNew(); }}
        />
        <button className="lh-btn primary" onClick={startNew} title="Start a new timer">
          <Icon name="play_arrow"></Icon>Start
        </button>
      </div>

      {latest && (
        <button className="trk-widget-latest" onClick={() => ttResumeTask(latest.id)} title="Resume this task">
          <span className="trk-widget-latest-play"><Icon name="play_arrow"></Icon></span>
          <span className="trk-widget-latest-main">
            <span className="trk-widget-latest-desc">{latest.description || '(no description)'}</span>
            <span className="trk-widget-latest-meta">
              {latest.projectId && (
                <span className="trk-widget-latest-proj">
                  <TtProjectDot project={latestProj}></TtProjectDot>
                  {latestProj ? latestProj.name : '(deleted project)'}
                </span>
              )}
              <span className="trk-widget-latest-cta">Resume</span>
            </span>
          </span>
        </button>
      )}

      {cycleRows.length > 0 && (
        <div className="trk-widget-cycles">
          <div className="trk-widget-cycles-head">
            <Icon name="cycle"></Icon>Active cycles
          </div>
          {cycleRows.slice(0, 4).map(c => (
            <button key={c.id} className="trk-widget-cycle" onClick={() => openCycle(c.id)} title={`View ${c.name}`}>
              <span className="trk-cycle-dot" style={{ background: c.color || 'var(--text-faint)' }}></span>
              <span className="trk-widget-cycle-name">{c.name}</span>
              <span className="trk-widget-cycle-ms">{ttFmtDuration(c.ms, { seconds: false })}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  TT_RUNNING_KEY, TT_FILTERS_KEY, TT_SETTINGS_KEY, TT_VIEW_KEY, TT_PALETTE,
  ttFmtDuration, ttFmtClock, ttDayKey, ttStartOfDay, ttStartOfWeek,
  ttFmtDayLabel, ttFmtWeekLabel, ttDateInputVal, ttTimeInputVal, ttParseDateTime,
  ttGetRunning, ttGetRunningList, ttStartTimer, ttStopTimer, ttStopAll, ttResumeTask,
  ttUpdateRunning, ttDiscardRunning,
  ttFindOrCreateTask, ttRunningFromTask, ttDeleteTask, ttSetTaskArchived,
  ttEnsureCycle, ttCycleById, ttArchiveCycle, ttRestoreCycle, ttDeleteCycle,
  useTtRunning, useTtRunningList, useTtSettings, useTtNow,
  ttEnsureProjectsFromTodos, ttEnsureProject, ttProjectById, ttTaskById,
  ttEntryMs, ttSessionMs, ttTaskMs,
  TtProjectDot, TtProjectPicker, TtTagPicker, TtCyclePicker, TimeTrackerWidget,
});
