/* ══════════════════════════════════════════════════════════════════
   ATLAS COCKPIT — CORE: store, helpers, icons, toast
   ══════════════════════════════════════════════════════════════════ */
const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React;

/* ───────────────────────────  STORE  ────────────────────────────── */
const STORE_KEY = 'atlas-cockpit-v1';
const LEGACY_KEY = 'atlas-state-v4';
const DEFAULT_STATE = {
  theme: 'system', sidebarCollapsed: false, focusMode: false, density: 'comfortable',
  accent: '#016AFF', radius: 'default', landingView: 'today',
  blockers: {}, checklists: {}, notes: {},
  decisions: { actions: {}, recs: {}, ideas: {}, risks: {} },
  kpis: {}, costs: {}, week: {}, actionOrder: null, log: [],
  /* Pass-1 additions — actions/ideas become mutable; briefs/connections read-mostly. */
  actions: null, ideas: null, briefs: null, connections: {},
  dateScope: 'mtd', projectTargets: {}, orchestratorReachable: true,
  /* Pass-2 additions — weekly snapshots rolling window of last 4. */
  snapshots: {}, snapshotMeta: { lastAt: null }
};

/* ─── one-time migration from the pre-cockpit monolith storage key ─── */
function migrateLegacy() {
  try {
    const raw = localStorage.getItem(LEGACY_KEY);
    if (!raw) return null;
    const old = JSON.parse(raw);
    const carry = ['notes', 'decisions', 'kpis', 'costs', 'checklists', 'blockers', 'week'];
    const out = {};
    carry.forEach(k => { if (old && old[k] && typeof old[k] === 'object') out[k] = old[k]; });
    return Object.keys(out).length ? out : null;
  } catch (err) {
    setTimeout(() => toast.error('Could not read legacy state', { detail: err.message }), 800);
    return null;
  }
}

function loadStore() {
  const D = window.ATLAS_DATA || {};
  try {
    let raw = JSON.parse(localStorage.getItem(STORE_KEY) || '{}');
    let migrated = false;
    if (!Object.keys(raw).length) {
      const legacy = migrateLegacy();
      if (legacy) {
        raw = legacy;
        migrated = true;
        try { localStorage.removeItem(LEGACY_KEY); } catch (_) {}
      }
    }
    const s = Object.assign({}, DEFAULT_STATE, raw, {
      decisions: Object.assign({ actions: {}, recs: {}, ideas: {}, risks: {} }, raw.decisions || {}),
      connections: Object.assign({}, raw.connections || {}),
      projectTargets: Object.assign({}, raw.projectTargets || {}),
      snapshots: Object.assign({}, raw.snapshots || {}),
      snapshotMeta: Object.assign({ lastAt: null }, raw.snapshotMeta || {})
    });
    /* Seed mutable collections once from data.js, then never re-merge. */
    if (!Array.isArray(s.actions)) s.actions = (D.architectActions || []).map(a => ({ ...a }));
    if (!Array.isArray(s.ideas)) s.ideas = (D.businessIdeas || []).map(i => ({ ...i }));
    if (!s.briefs) s.briefs = Object.assign({}, D.briefsSeed || {});
    if (migrated) setTimeout(() => toast('Migrated previous session state.'), 700);
    /* Persist the migrated/merged shape immediately so legacy is fully retired. */
    if (migrated) { try { localStorage.setItem(STORE_KEY, JSON.stringify(s)); } catch (_) {} }
    return s;
  } catch (err) {
    setTimeout(() => toast.error('Could not load saved state', {
      detail: (err && err.message) || 'Falling back to defaults — previous session may be unreadable.'
    }), 800);
    const s = { ...DEFAULT_STATE };
    s.actions = (D.architectActions || []).map(a => ({ ...a }));
    s.ideas = (D.businessIdeas || []).map(i => ({ ...i }));
    s.briefs = Object.assign({}, D.briefsSeed || {});
    return s;
  }
}
function uuid() { return 'x-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 7); }

const Store = (() => {
  let state = loadStore();
  const listeners = new Set();
  const get = () => state;
  const persist = () => {
    try { localStorage.setItem(STORE_KEY, JSON.stringify(state)); }
    catch (err) { toast.error('Save failed', { detail: (err && err.message) || 'localStorage write rejected.' }); }
  };
  const set = (patch) => {
    state = typeof patch === 'function' ? patch(state) : Object.assign({}, state, patch);
    persist();
    listeners.forEach(l => l());
  };
  const subscribe = (fn) => { listeners.add(fn); return () => listeners.delete(fn); };
  const reset = () => { localStorage.removeItem(STORE_KEY); state = loadStore(); listeners.forEach(l => l()); };
  return { get, set, subscribe, reset };
})();

function useStore(selector) {
  const sel = selector || (s => s);
  const [, force] = useState(0);
  const ref = useRef(sel(Store.get()));
  useEffect(() => Store.subscribe(() => {
    const next = sel(Store.get());
    ref.current = next;
    force(n => n + 1);
  }), []);
  ref.current = sel(Store.get());
  return ref.current;
}

/* mutators */
function setKV(patch) { Store.set(patch); }

function logDecision(kind, store, id, decision, title, note) {
  const uid = Date.now() + '-' + Math.random().toString(36).slice(2, 6);
  const at = new Date().toISOString();
  Store.set(s => {
    const log = [{ uid, kind, store, id, decision, title, note: note || '', ts: at }, ...s.log].slice(0, 200);
    const decisions = { ...s.decisions, [store]: { ...s.decisions[store], [id]: { decision, note: note || '', ts: at } } };
    return { ...s, log, decisions };
  });
  // Fire-and-forget mirror to orchestrator (silent on failure — local log is source of truth).
  if (window.ORCHESTRATOR_URL) {
    api('/api/decisions', { method: 'POST', body: JSON.stringify({
      id: uid, kind, entityType: store, entityId: id, payload: { decision, title, note: note || '' }, at
    })}).catch(() => {});
  }
}
function undoDecision(store, id) {
  Store.set(s => {
    const d = { ...s.decisions[store] }; delete d[id];
    const log = s.log.filter(e => !(e.store === store && e.id === id));
    return { ...s, decisions: { ...s.decisions, [store]: d }, log };
  });
}
function isDecided(store, id) { return !!(Store.get().decisions[store] || {})[id]; }
function saveNote(store, id, note) {
  Store.set(s => ({ ...s, notes: { ...s.notes, [store]: { ...(s.notes[store] || {}), [id]: note } } }));
}
function loadNote(store, id) { return ((Store.get().notes[store] || {})[id]) || ''; }

/* ─── mutable collections (actions / ideas) ─── */
function getActions() { const a = Store.get().actions; return Array.isArray(a) ? a : []; }
function getIdeas() { const i = Store.get().ideas; return Array.isArray(i) ? i : []; }

/* Generic event-log appender — adds new kinds without touching logDecision's shape.
   Stays render-compatible with LogView (uid/decision/store/title/note/ts). */
function logEvent(kind, { store, id, title, note } = {}) {
  Store.set(s => ({ ...s, log: [{
    uid: Date.now() + '-' + Math.random().toString(36).slice(2, 6),
    kind, store: store || kind, id: id || '', decision: kind, title: title || '', note: note || '',
    ts: new Date().toISOString()
  }, ...s.log].slice(0, 400) }));
}

function addAction(data) {
  const PRIO = { high: 'high', med: 'medium', low: 'low' };
  const action = {
    id: uuid(), title: (data.title || '').trim(), urgency: PRIO[data.priority] || 'medium',
    why: (data.notes || '').trim() || 'Captured from the cockpit.', source: 'Quick capture',
    project: data.projectId || null, due: data.due || null,
    checklist: (data.checklist || '').split('\n').map(l => l.trim()).filter(Boolean),
    suggestedMove: null, custom: true
  };
  if (!action.checklist.length) delete action.checklist;
  Store.set(s => {
    const actions = [...(Array.isArray(s.actions) ? s.actions : []), action];
    const actionOrder = Array.isArray(s.actionOrder) ? [action.id, ...s.actionOrder] : s.actionOrder;
    return { ...s, actions, actionOrder };
  });
  logEvent('action_added', { store: 'actions', id: action.id, title: action.title, note: action.project ? 'on ' + action.project : '' });
  toast('Action added');
  return action;
}

function addIdea(data) {
  const idea = {
    id: uuid(), name: (data.title || '').trim(), category: (data.category || 'uncategorized').trim(),
    score: Math.max(1, Math.min(10, Math.round(data.score || 5))) * 10,
    timeToRevenue: (data.timeToRevenue || '').trim() || '—', ceiling: (data.ceiling || '').trim() || '—',
    thesis: (data.notes || '').trim() || 'Captured from the cockpit.', confidence: 'low',
    src: 'Quick capture', validation: 'Define the single next validation step.', rubric: null, custom: true
  };
  Store.set(s => ({ ...s, ideas: [...(Array.isArray(s.ideas) ? s.ideas : []), idea] }));
  logEvent('idea_added', { store: 'ideas', id: idea.id, title: idea.name });
  toast('Idea captured');
  return idea;
}

/* ─── orchestrator API client ─── */
function apiBase() { return (window.ORCHESTRATOR_URL || '').replace(/\/$/, ''); }
function _sanitizeToken(t) {
  if (!t) return '';
  // Strip whitespace, newlines, zero-width chars, trailing periods.
  return String(t).replace(/[\s​-‍﻿]+/g, '').replace(/\.+$/, '').trim();
}
function getApiToken() {
  let t = localStorage.getItem('cockpitApiToken');
  if (!t) {
    t = window.prompt('Orchestrator API token (one-time — paste from orchestrator/.env ORCHESTRATOR_API_TOKEN):');
    t = _sanitizeToken(t);
    if (t) localStorage.setItem('cockpitApiToken', t);
  }
  return t;
}
function clearApiToken() {
  localStorage.removeItem('cockpitApiToken');
  toast('API token cleared — will re-prompt on next save');
}
async function api(path, opts = {}) {
  const base = apiBase();
  if (!base) throw new TypeError('ORCHESTRATOR_URL not configured');
  const send = (tok) => fetch(base + path, {
    ...opts,
    headers: { ...(opts.headers || {}), 'Authorization': `Bearer ${tok}`, 'Content-Type': 'application/json' }
  });
  let token = getApiToken();
  if (!token) {
    const err = new Error('Auth required'); err.authFailed = true; throw err;
  }
  let res = await send(token);
  if (res.status === 401 || res.status === 403) {
    /* Bad token. Clear, re-prompt, retry once. */
    localStorage.removeItem('cockpitApiToken');
    toast.error('Token rejected — re-enter to continue');
    const fresh = getApiToken();
    if (!fresh) { const err = new Error('Auth required'); err.authFailed = true; throw err; }
    res = await send(fresh);
    if (res.status === 401 || res.status === 403) {
      localStorage.removeItem('cockpitApiToken');
      const err = new Error('Token rejected twice — try Settings → Clear API token then retry.');
      err.authFailed = true;
      throw err;
    }
  }
  return res;
}

/* ─── connections ─── */
function tailOf(payload) {
  const vals = Object.values(payload || {}).filter(Boolean);
  const last = vals[vals.length - 1] || '';
  return '…' + String(last).slice(-3);
}
async function saveConnection(name, payload) {
  try {
    const res = await api(`/api/connections/${encodeURIComponent(name)}`, {
      method: 'POST', body: JSON.stringify(payload)
    });
    if (!res.ok) throw new Error((await res.text()) || ('HTTP ' + res.status));
    const { tail } = await res.json();
    Store.set(s => ({ ...s, orchestratorReachable: true, connections: { ...s.connections, [name]: { status: 'connected', tail: tail || tailOf(payload), savedAt: new Date().toISOString() } } }));
    toast(`${name} connected`);
  } catch (err) {
    /* Auth failures live in a different bucket — the provider isn't errored, the
       cockpit's token was. Don't taint the row, just surface the error inline. */
    if (err && err.authFailed) {
      toast.error(`${name} not saved — token rejected`, { detail: 'Retry the save; the next prompt accepts the new token.' });
      return;
    }
    const network = (err instanceof TypeError);
    if (network) {
      // Orchestrator unreachable — surface the banner, do not persist secrets or a fake status.
      Store.set(s => ({ ...s, orchestratorReachable: false }));
      toast.error('Orchestrator unreachable', { detail: 'Paste keys into orchestrator/.env or stand up the orchestrator first.' });
    } else {
      Store.set(s => ({ ...s, connections: { ...s.connections, [name]: { status: 'errored', lastErrorMsg: err.message, savedAt: new Date().toISOString() } } }));
      toast.error(`${name} save failed`, { detail: err.message });
    }
  }
}
function connectionsAggregate() {
  const D = window.ATLAS_DATA, conns = Store.get().connections || {};
  /* Optional providers (e.g. Alert hook) don't block the "Live" aggregate. */
  const required = (D.connections || []).filter(c => !c.optional).map(c => c.name);
  const entries = required.map(n => conns[n]).filter(Boolean);
  if (entries.some(e => e.status === 'errored')) return { label: 'Errored', tone: 'errored' };
  const live = entries.filter(e => e.status === 'connected');
  if (live.length === 0) return { label: 'Local only', tone: 'local' };
  if (live.length === required.length) return { label: 'Live', tone: 'live' };
  return { label: 'Partial', tone: 'partial' };
}

/* ─── copy-link + downloads ─── */
function copyEntityLink(kind, id) {
  const url = `https://atlas.local/#/${kind}/${id}`;
  const done = () => toast('Link copied');
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(url).then(done, () => { fallbackCopy(url); done(); });
  } else { fallbackCopy(url); done(); }
}
function fallbackCopy(text) {
  try { const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); } catch (_) {}
}
function downloadFile(filename, content, mime) {
  const blob = new Blob([content], { type: mime || 'text/plain' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = filename; document.body.appendChild(a); a.click();
  setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 200);
}
function isoDay() { return new Date().toISOString().slice(0, 10); }


/* ─────────────────────────  HELPERS  ────────────────────────────── */
function fmt$(n, dash = '—') { if (n == null || n === '') return dash; if (n === 0) return '$0'; return '$' + Number(n).toLocaleString('en-US'); }
function fmtPct(n) { return n == null ? '—' : n + '%'; }
function effectiveTheme(t) {
  if (t === 'system') return (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
  return t === 'dark' ? 'dark' : 'light';
}
function cls(...a) { return a.filter(Boolean).join(' '); }
function daysUntil(dateStr) { const d = new Date(dateStr + 'T00:00:00'); return Math.ceil((d - startOfToday()) / 86400000); }
function startOfToday() { const d = new Date(); d.setHours(0, 0, 0, 0); return d; }
function fmtDate(dateStr, opts) {
  if (!dateStr) return '—';
  const d = new Date(dateStr + 'T00:00:00');
  return d.toLocaleDateString('en-US', opts || { month: 'short', day: 'numeric' });
}
function relDays(dateStr) {
  const n = daysUntil(dateStr);
  if (n === 0) return 'today';
  if (n === 1) return 'tomorrow';
  if (n === -1) return 'yesterday';
  if (n < 0) return `${-n}d overdue`;
  return `in ${n}d`;
}
function relTime(iso) {
  const diff = (Date.now() - new Date(iso)) / 1000;
  if (diff < 60) return 'just now';
  if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
  if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
  return Math.floor(diff / 86400) + 'd ago';
}
const URGENCY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };

/* ───────────────────────────  ICONS  ────────────────────────────── */
const ICONS = {
  today: '<path d="M12 3v2M12 19v2M5 12H3M21 12h-2M6 6l-1.4-1.4M19.4 19.4 18 18M18 6l1.4-1.4M4.6 19.4 6 18"/><circle cx="12" cy="12" r="4"/>',
  projects: '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>',
  finance: '<path d="M3 17l5-5 3 3 7-7"/><path d="M16 8h5v5"/>',
  risks: '<path d="M12 3 2.5 19.5h19L12 3Z"/><path d="M12 10v4M12 17.5v.5"/>',
  ideas: '<path d="M9 18h6M10 21h4"/><path d="M12 3a6 6 0 0 0-4 10.5c.8.8 1 1.3 1 2.5h6c0-1.2.2-1.7 1-2.5A6 6 0 0 0 12 3Z"/>',
  calendar: '<rect x="3" y="4.5" width="18" height="16" rx="2"/><path d="M3 9h18M8 2.5v4M16 2.5v4"/>',
  settings: '<path d="M4 7h10M18 7h2M4 17h2M10 17h10"/><circle cx="16" cy="7" r="2.2"/><circle cx="8" cy="17" r="2.2"/>',
  search: '<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>',
  command: '<path d="M9 6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3Z"/>',
  sun: '<circle cx="12" cy="12" r="4.5"/><path d="M12 1.5v2.5M12 20v2.5M3.5 12H1M23 12h-2.5M5 5l1.7 1.7M17.3 17.3 19 19M19 5l-1.7 1.7M6.7 17.3 5 19"/>',
  moon: '<path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5Z"/>',
  focus: '<path d="M3 8V5a2 2 0 0 1 2-2h3M16 3h3a2 2 0 0 1 2 2v3M21 16v3a2 2 0 0 1-2 2h-3M8 21H5a2 2 0 0 1-2-2v-3"/><circle cx="12" cy="12" r="2.5"/>',
  chevronR: '<path d="m9 6 6 6-6 6"/>',
  chevronD: '<path d="m6 9 6 6 6-6"/>',
  chevronL: '<path d="m15 6-6 6 6 6"/>',
  grip: '<circle cx="9" cy="6" r="1.4"/><circle cx="15" cy="6" r="1.4"/><circle cx="9" cy="12" r="1.4"/><circle cx="15" cy="12" r="1.4"/><circle cx="9" cy="18" r="1.4"/><circle cx="15" cy="18" r="1.4"/>',
  check: '<path d="m5 12 5 5L20 7"/>',
  checkCircle: '<circle cx="12" cy="12" r="9"/><path d="m8.5 12 2.5 2.5L16 9"/>',
  xCircle: '<circle cx="12" cy="12" r="9"/><path d="m9 9 6 6M15 9l-6 6"/>',
  clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/>',
  plus: '<path d="M12 5v14M5 12h14"/>',
  x: '<path d="M6 6l12 12M18 6 6 18"/>',
  arrowR: '<path d="M5 12h14M13 6l6 6-6 6"/>',
  filter: '<path d="M3 5h18l-7 8.5V20l-4-2.5v-4Z"/>',
  sort: '<path d="M7 4v16M7 4 4 7M7 4l3 3M17 20V4M17 20l-3-3M17 20l3-3"/>',
  alert: '<path d="M12 3 2.5 19.5h19L12 3Z"/><path d="M12 10v4M12 17.5v.5"/>',
  link: '<path d="M9 15l6-6M10.5 6.5 12 5a4 4 0 0 1 5.7 5.7l-1.5 1.5M13.5 17.5 12 19a4 4 0 0 1-5.7-5.7l1.5-1.5"/>',
  download: '<path d="M12 3v12M7 11l5 5 5-5M5 20h14"/>',
  copy: '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h8"/>',
  moon2: '<path d="M12 3a9 9 0 1 0 9 9c-5 0-9-4-9-9Z"/>',
  refresh: '<path d="M3 12a9 9 0 0 1 15-6.7L21 8M21 3v5h-5M21 12a9 9 0 0 1-15 6.7L3 16M3 21v-5h5"/>',
  grid: '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>',
  list: '<path d="M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01"/>',
  zap: '<path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/>',
  flag: '<path d="M4 22V4M4 4h13l-2.5 4L17 12H4"/>',
  trash: '<path d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6 7l1 13a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1l1-13"/>',
  defer: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/>',
  external: '<path d="M14 4h6v6M20 4l-9 9M19 13v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h6"/>',
  bell: '<path d="M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6ZM10 20a2 2 0 0 0 4 0"/>',
  collapse: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16"/>',
  user: '<circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/>',
  undo: '<path d="M9 14 4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-4"/>',
  doc: '<path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9l-6-6Z"/><path d="M14 3v6h6M8 13h8M8 17h6"/>',
  target: '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.4"/>',
  trending: '<path d="M3 17l6-6 4 4 8-8M21 7v5h-5"/>',
  wallet: '<path d="M3 7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2M3 7v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2H5a2 2 0 0 1-2-2Z"/><circle cx="17" cy="13" r="1.2"/>',
  scale: '<path d="M12 3v18M5 7h14M7 7l-3 6h6l-3-6ZM17 7l-3 6h6l-3-6Z"/>',
  sparkle: '<path d="M12 3l2 6 6 2-6 2-2 6-2-6-6-2 6-2 2-6Z"/>',
  pin: '<path d="M12 17v5M8 3h8l-1 6 3 3H6l3-3-1-6Z"/>',
  monitor: '<rect x="3" y="4" width="18" height="13" rx="2"/><path d="M8 21h8M12 17v4"/>',
  panelClose: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M10 4v16"/><path d="m16 9-2 3 2 3"/>',
  panelOpen: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M10 4v16"/><path d="m14 9 2 3-2 3"/>',
};
function Icon({ name, size = 18, sw = 1.7, style, className }) {
  const path = ICONS[name] || '';
  return React.createElement('svg', {
    width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor',
    strokeWidth: sw, strokeLinecap: 'round', strokeLinejoin: 'round', style, className,
    dangerouslySetInnerHTML: { __html: path }
  });
}
function BrandMark({ size = 30 }) {
  return React.createElement('svg', {
    width: size, height: size, viewBox: '0 0 1254 1254', fill: 'currentColor',
    dangerouslySetInnerHTML: { __html: `<path fill-rule="evenodd" d="${window.ATLAS_MARK}"/>` }
  });
}

/* ───────────────────────────  TOAST  ────────────────────────────── */
let toastSeq = 0;
const toastBus = new Set();
function toast(msg, opts = {}) {
  const t = {
    id: ++toastSeq, msg, undo: opts.undo, detail: opts.detail || null,
    tone: opts.tone || 'success', ts: Date.now()
  };
  toastBus.forEach(fn => fn(t));
}
toast.error = function (msg, opts = {}) { toast(msg, { ...opts, tone: 'error' }); };
function ToastHost() {
  const [items, setItems] = useState([]);
  useEffect(() => {
    const fn = (t) => {
      setItems(list => [...list, t]);
      const life = t.undo ? 9000 : (t.tone === 'error' ? 6500 : 3200);
      setTimeout(() => setItems(list => list.map(x => x.id === t.id ? { ...x, out: true } : x)), life);
      setTimeout(() => setItems(list => list.filter(x => x.id !== t.id)), life + 220);
    };
    toastBus.add(fn);
    return () => toastBus.delete(fn);
  }, []);
  return React.createElement('div', { className: 'toast-host' },
    items.map(t => React.createElement('div', { key: t.id, className: cls('toast', `t-${t.tone}`, t.out && 'out') },
      React.createElement('div', { className: 't-body' },
        React.createElement('span', { className: 't-msg' }, t.msg),
        t.detail && React.createElement('span', { className: 't-detail' }, t.detail)),
      t.undo && React.createElement('button', {
        className: 'undo', onClick: () => { t.undo(); setItems(list => list.filter(x => x.id !== t.id)); }
      }, 'Undo')
    ))
  );
}

/* ───────────────────────────  SNAPSHOTS  ────────────────────────── */
function takeSnapshot(reason = 'auto') {
  const day = isoDay();
  Store.set(s => {
    const { snapshots, snapshotMeta, ...rest } = s;
    const snap = JSON.parse(JSON.stringify(rest));
    snap._takenAt = new Date().toISOString();
    snap._reason = reason;
    const next = { ...snapshots, [day]: snap };
    const keys = Object.keys(next).sort();
    while (keys.length > 4) { delete next[keys.shift()]; }
    return { ...s, snapshots: next, snapshotMeta: { lastAt: new Date().toISOString() } };
  });
  if (reason !== 'silent') toast(reason === 'auto' ? 'Auto-snapshot saved' : 'Snapshot saved');
}
function restoreSnapshot(day) {
  const snap = (Store.get().snapshots || {})[day];
  if (!snap) { toast.error('Snapshot not found'); return; }
  const restored = JSON.parse(JSON.stringify(snap));
  delete restored._takenAt; delete restored._reason;
  Store.set(s => Object.assign({}, DEFAULT_STATE, restored, {
    snapshots: s.snapshots, snapshotMeta: s.snapshotMeta
  }));
  toast(`Restored snapshot from ${day}`);
}
function deleteSnapshot(day) {
  Store.set(s => {
    const next = { ...(s.snapshots || {}) };
    delete next[day];
    return { ...s, snapshots: next };
  });
  toast('Snapshot deleted');
}
function listSnapshots() {
  const m = Store.get().snapshots || {};
  return Object.keys(m).sort().reverse().map(day => ({
    day, takenAt: m[day]._takenAt || null, reason: m[day]._reason || 'manual'
  }));
}
function downloadSnapshot(day) {
  const snap = (Store.get().snapshots || {})[day];
  if (!snap) { toast.error('Snapshot not found'); return; }
  downloadFile(`atlas-snapshot-${day}.json`, JSON.stringify(snap, null, 2), 'application/json');
}
function lastSundayElevenPm() {
  const now = new Date();
  const day = now.getDay();          // 0=Sun
  const sun = new Date(now);
  sun.setDate(now.getDate() - day);  // last Sunday (today if Sun)
  sun.setHours(23, 0, 0, 0);
  if (day === 0 && now < sun) sun.setDate(sun.getDate() - 7);
  return sun;
}
function maybeAutoSnapshot() {
  try {
    const meta = Store.get().snapshotMeta || {};
    const lastAt = meta.lastAt ? new Date(meta.lastAt) : null;
    const trigger = lastSundayElevenPm();
    const now = new Date();
    const ageOK = !lastAt || (now - lastAt) > 7 * 86400000;
    const pastTrigger = now >= trigger;
    const newerThanLast = !lastAt || lastAt < trigger;
    if (ageOK && pastTrigger && newerThanLast) takeSnapshot('auto');
  } catch (err) { toast.error('Auto-snapshot failed', { detail: err.message }); }
}
/* Wait a tick so ToastHost is mounted before any messages fire. */
setTimeout(() => { try { maybeAutoSnapshot(); } catch (_) {} }, 1800);

/* ─── briefs sync ─── pulls latest briefs from orchestrator into state.briefs
   on load. Silent on failure — local seeds remain. */
async function syncBriefs() {
  if (!apiBase()) return;
  if (!localStorage.getItem('cockpitApiToken')) return;  // don't prompt at load
  try {
    const res = await api('/api/briefs?limit=20');
    if (!res.ok) return;
    const { briefs } = await res.json();
    if (!Array.isArray(briefs) || !briefs.length) return;
    Store.set(s => {
      const next = { ...(s.briefs || {}) };
      briefs.forEach(b => { next[b.id] = b; });
      return { ...s, briefs: next };
    });
  } catch (_) {}
}
setTimeout(syncBriefs, 2200);

Object.assign(window, {
  Store, useStore, setKV, logDecision, undoDecision, isDecided, saveNote, loadNote,
  getActions, getIdeas, uuid, logEvent, addAction, addIdea,
  saveConnection, connectionsAggregate, copyEntityLink, downloadFile, isoDay,
  takeSnapshot, restoreSnapshot, deleteSnapshot, listSnapshots, downloadSnapshot,
  api, apiBase, getApiToken, clearApiToken,
  fmt$, fmtPct, effectiveTheme, cls, daysUntil, startOfToday, fmtDate, relDays, relTime, URGENCY_RANK,
  Icon, BrandMark, toast, ToastHost,
  useState, useEffect, useRef, useMemo, useCallback, createContext, useContext
});
