/* Clipshelf core — shared toast + top nav + plugin surfaces. Single local user. */
(() => {
  /* The sprite is injected once per page and every icon is a <use> of it,
     so an icon costs one element and inherits the theme through
     currentColor — which is the whole reason these are not emoji: an
     emoji is a coloured picture from the system font and cannot be told
     to match anything. */
  const sprite = () => {
    if (document.getElementById('app-icons') || !window.__ICON_SPRITE) return;
    const host = document.createElement('div');
    host.id = 'app-icons';
    host.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden';
    host.innerHTML = window.__ICON_SPRITE;
    document.body.prepend(host);
  };

  window.APP = {
    plugins: { games: [], providers: [], publishers: [], themes: [], markers: {} },
    /* APP.icon('delete') -> markup for one icon. Names are the app's own
       (scripts/build_icons.py maps them onto Lucide), so swapping the
       drawing never touches a call site. */
    icon(name, cls) {
      return `<svg class="ic${cls ? ' ' + cls : ''}" aria-hidden="true"><use href="#i-${name}"/></svg>`;
    },
    pluginsReady: null,   // promise — pages await this before drawing plugin UI
    toast(msg, kind) {
      let box = document.getElementById('app-toasts');
      if (!box) {
        box = document.createElement('div');
        box.id = 'app-toasts';
        box.style.cssText = 'position:fixed;bottom:18px;left:50%;transform:translateX(-50%);z-index:999;display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none';
        document.body.appendChild(box);
      }
      const t = document.createElement('div');
      t.textContent = msg;
      t.style.cssText = 'background:var(--surface);border:1px solid ' +
        (kind === 'error' ? 'var(--accent)' : kind === 'success' ? 'var(--green)' : 'var(--border-hi)') +
        ';color:var(--text);font:600 0.78rem ui-monospace,monospace;padding:10px 16px;border-radius:12px;' +
        'box-shadow:0 8px 30px rgba(0,0,0,0.5);max-width:80vw;transition:opacity 0.3s';
      box.appendChild(t);
      setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 350); }, 3500);
    },
  };

  /* Downloads. Inside the app window the WebView drops page-initiated
     downloads on the floor — an <a download> looks like it worked and
     nothing lands anywhere. There, ask the app to run a native save dialog
     instead; in a browser the ordinary link is right. */
  APP.isDesktop = false;
  APP.download = async (src, name) => {
    if (!APP.isDesktop) {
      const a = document.createElement('a');
      a.href = src; a.download = name || '';
      document.body.appendChild(a); a.click(); a.remove();
      return;
    }
    const r = await fetch('/api/clips/save_as', { method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ src, name }) }).catch(() => null);
    if (!r || !r.ok) return APP.toast('could not save that file', 'error');
    const j = await r.json();
    if (j.status === 'cancelled') return;
    APP.toast('saved to ' + j.path, 'success');
  };

  const nav = () => {
    const here = location.pathname.split('/').pop() || 'index.html';
    if (here === 'setup.html') return;   // wizard owns the first run
    const links = [['index.html', 'Clipshelf'], ['vods.html', 'vods'],
                   ['clips.html', 'clips'], ['editor.html', 'CUT editor'],
                   ['settings.html', 'settings']];
    if (here === 'enroll.html') links.splice(4, 0, ['enroll.html', 'teach']);
    const bar = document.createElement('nav');
    bar.style.cssText = 'display:flex;gap:18px;align-items:center;padding:12px 22px;' +
      'border-bottom:1px solid rgba(255,255,255,0.07);font:700 0.72rem ui-monospace,monospace';
    bar.innerHTML = links.map(([href, label], i) =>
      `<a href="/${href}" style="text-decoration:none;letter-spacing:0.06em;` +
      `color:${here === href ? 'var(--primary)' : 'var(--text-dim)'};` +
      `${i === 0 ? 'font-size:0.82rem;' : ''}">${label}` +
      (href === 'clips.html' ? '<span id="nav-tray"></span>' : '') + '</a>').join('');
    document.body.prepend(bar);

    /* captures waiting in the ingest tray. A hotkey press that produces no
       visible change anywhere is indistinguishable from a hotkey that did
       not fire — so the count rides the nav on every page. */
    const tray = () => fetch('/api/ingest').then(r => r.json()).then(j => {
      const n = (j.captures || []).length, el = document.getElementById('nav-tray');
      if (!el) return;
      el.textContent = n ? ` ${n}` : '';
      el.style.cssText = n
        ? 'background:var(--primary);color:#14100a;border-radius:9px;padding:1px 6px;' +
          'margin-left:6px;font-size:0.62rem'
        : '';
      el.title = n ? `${n} capture${n === 1 ? '' : 's'} waiting to be trimmed` : '';
    }).catch(() => {});
    tray();
    setInterval(tray, 20000);
  };
  const boot = () => { sprite(); nav(); };
  document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', boot) : boot();

  /* unconfigured install → the setup wizard owns the first run */
  const here = location.pathname.split('/').pop() || 'index.html';
  if (here !== 'setup.html') {
    fetch('/api/health/ping').then(r => r.json()).then(j => {
      APP.isDesktop = !!j.desktop;
      if (!j.configured) location.replace('/setup.html');
    }).catch(() => {});
  }

  window.APP.pluginsReady = fetch('/api/plugins/ui')
    .then(r => r.json())
    .then(j => { Object.assign(window.APP.plugins, j); return window.APP.plugins; })
    .catch(() => window.APP.plugins);
})();
