// SoloTube — main app shell + state
const { useReducer, useEffect: useEffectApp } = React;

const PRESET_WATCHED = new Set(window.SOLO_DATA.videos.filter(v => v.watched).map(v => v.id));

// Some seed notes so the timeline shows something on first load
const SEED_NOTES = {
  'v-002': [
    { id: 'n-1', atSec: 112, text: 'The scale from heat death back to now — good perspective anchor.', createdAt: Date.now() - 55*86400000 },
    { id: 'n-2', atSec: 290, text: 'Meaning as something you create, not find. Worth re-reading Camus after this.', createdAt: Date.now() - 54*86400000 },
  ],
};

const INITIAL = {
  channels: window.SOLO_DATA.channels,
  videos: window.SOLO_DATA.videos,
  watched: PRESET_WATCHED,
  hidden: new Set(),
  route: { type: 'feed', feed: 'all' },
  prevRoute: null,
  lastWatchId: null,
  theme: 'light',
  dimWatched: true,
  addOpen: false,
  notifOpen: false,
  notifSeen: new Set(),
  focusMode: false,
  briefs: {},
  tagEditor: null,
  // NEW:
  userTags: [],                                  // manually-created tag names (kept even if unattached)
  sidebarCollapsed: { views: false, tags: false, channels: false },
  sidebarRail: false,                            // full sidebar collapsed to a thin rail
  notes: SEED_NOTES,                              // { [videoId]: [{ id, atSec|null, text, createdAt }] } — atSec null = general note
  playhead: {},                                   // { [videoId]: secondsAsFloat }
  favorites: new Set(),                           // video ids starred by the user
  archived: new Set(),                            // video ids hidden from the main feed
  lastDigestSent: null,                           // timestamp of last auto-sent digest
  watchLog: {},                                   // { 'YYYY-MM-DD': seconds actually played }
  scoutOpen: false,
  importOpen: false,
  mnav: false,                                    // mobile nav drawer open
  settingsOpen: false,
  // Zen-leaning defaults: hide chrome, let the user dial it up.
  settings: {
    pageEyebrow: false,
    pageSubtitle: false,
    pageStats: false,
    inbox: true,
    railChannels: false,
    railUnwatchedDots: false,
    sectionHeaders: true,
    progressBars: true,
    viewIndices: true,
    filterBar: true,
    pageStatsAlwaysOnChannel: true,    // even when stats off globally, show ch stats? keep small
    briefLength: 'm',                  // s | m | l — AI brief length
    dailyLimitMin: 0,                  // attention budget in minutes; 0 = off
    deepZen: true,                     // focus mode strips stats/desc/brief, not just the side rail
    thumbnails: 'real',                // 'real' | 'generated' | false
    videoSnippets: false,              // show description snippet in feed cards
    digestEmail: '',                   // recipient for scheduled digest
    digestScheduleEnabled: false,
    digestScheduleDay: 1,              // 0=Sun 1=Mon … 6=Sat
    digestScheduleHour: 9,             // 0–23 local time
    digestTimezone: '',                // '' = auto-detect via Intl
    autoArchiveWatched: false,         // auto-move watched videos to archive
  },
};

function reducer(state, action) {
  switch (action.type) {
    case 'route': {
      const next = { ...state, prevRoute: state.route, route: action.route };
      next.notifOpen = false;
      next.mnav = false;
      if (action.route.type === 'watch') next.lastWatchId = action.route.id;
      return next;
    }
    case 'toggle-mnav': return { ...state, mnav: !state.mnav };
    case 'back': {
      const prev = state.prevRoute || { type: 'feed', feed: 'all' };
      return { ...state, route: prev, prevRoute: null };
    }
    case 'mark-watched': {
      const w = new Set(state.watched); w.add(action.id);
      if (state.settings.autoArchiveWatched) {
        const ar = new Set(state.archived); ar.add(action.id);
        return { ...state, watched: w, archived: ar };
      }
      return { ...state, watched: w };
    }
    case 'archive-video': {
      const ar = new Set(state.archived); ar.add(action.id);
      return { ...state, archived: ar };
    }
    case 'unarchive-video': {
      const ar = new Set(state.archived); ar.delete(action.id);
      return { ...state, archived: ar };
    }
    case 'set-last-digest-sent': {
      return { ...state, lastDigestSent: action.timestamp };
    }
    case 'mark-all-read': {
      const w = new Set(state.watched);
      // scope to current view if channel
      const scope = state.route.type === 'channel'
        ? state.videos.filter(v => v.channelId === state.route.id)
        : state.videos;
      scope.forEach(v => w.add(v.id));
      return { ...state, watched: w };
    }
    case 'theme': {
      return { ...state, theme: action.theme };
    }
    case 'toggle-dim': {
      return { ...state, dimWatched: !state.dimWatched };
    }
    // addOpen is true | a prefill URL string (Scout hands off a handle to follow)
    case 'open-add': return { ...state, addOpen: action.url || true, addIntent: action.intent || null };
    case 'close-add': return { ...state, addOpen: false, addIntent: null };
    case 'open-scout': return { ...state, scoutOpen: true };
    case 'close-scout': return { ...state, scoutOpen: false };
    case 'open-import': return { ...state, importOpen: true };
    case 'close-import': return { ...state, importOpen: false };
    case 'add-channel': {
      const incomingVids = Array.isArray(action.videos) ? action.videos : [];
      // de-dupe by id; if already present, merge any newly fetched videos
      if (state.channels.find(c => c.id === action.channel.id)) {
        if (!incomingVids.length) return state;
        const existing = new Set(state.videos.map(v => v.id));
        const merged = [...state.videos, ...incomingVids.filter(v => !existing.has(v.id))];
        return { ...state, videos: merged };
      }
      const channels = [...state.channels, { ...action.channel, tags: action.channel.tags || [] }];
      return { ...state, channels, videos: [...state.videos, ...incomingVids] };
    }
    case 'merge-videos': {
      // Refresh path: append any video IDs we don't already know about.
      const incoming = Array.isArray(action.videos) ? action.videos : [];
      if (!incoming.length) return state;
      const existing = new Set(state.videos.map(v => v.id));
      const fresh = incoming.filter(v => !existing.has(v.id));
      if (!fresh.length) return state;
      return { ...state, videos: [...state.videos, ...fresh] };
    }
    case 'add-tag': {
      const channels = state.channels.map(c => {
        if (c.id !== action.channelId) return c;
        const tags = c.tags || [];
        if (tags.includes(action.tag)) return c;
        return { ...c, tags: [...tags, action.tag] };
      });
      return { ...state, channels };
    }
    case 'remove-tag': {
      const channels = state.channels.map(c => {
        if (c.id !== action.channelId) return c;
        return { ...c, tags: (c.tags || []).filter(t => t !== action.tag) };
      });
      return { ...state, channels };
    }
    case 'open-tag-editor': return { ...state, tagEditor: action.channelId };
    case 'close-tag-editor': return { ...state, tagEditor: null };
    case 'toggle-focus': return { ...state, focusMode: !state.focusMode };
    case 'brief-start': {
      return { ...state, briefs: { ...state.briefs, [action.videoId]: { status: 'loading' } } };
    }
    case 'brief-ready': {
      return { ...state, briefs: { ...state.briefs, [action.videoId]: { status: 'ready', text: action.text } } };
    }
    case 'brief-error': {
      return { ...state, briefs: { ...state.briefs, [action.videoId]: { status: 'error', text: action.text } } };
    }
    // ---- NEW: collapse sidebar sections ----
    case 'toggle-section': {
      return {
        ...state,
        sidebarCollapsed: {
          ...state.sidebarCollapsed,
          [action.section]: !state.sidebarCollapsed[action.section],
        },
      };
    }
    case 'toggle-rail': {
      return { ...state, sidebarRail: !state.sidebarRail };
    }
    case 'open-settings': return { ...state, settingsOpen: true };
    case 'close-settings': return { ...state, settingsOpen: false };
    case 'set-setting': {
      return { ...state, settings: { ...state.settings, [action.key]: action.value } };
    }
    // ---- NEW: create/delete a tag globally ----
    case 'create-tag': {
      const tag = action.tag;
      if (state.userTags.includes(tag)) return state;
      return { ...state, userTags: [...state.userTags, tag] };
    }
    case 'delete-tag': {
      // remove from every channel and from userTags
      const channels = state.channels.map(c => ({
        ...c, tags: (c.tags || []).filter(t => t !== action.tag),
      }));
      const userTags = state.userTags.filter(t => t !== action.tag);
      // if currently on that tag's page, go home
      const onTag = state.route.type === 'tag' && state.route.tag === action.tag;
      const route = onTag ? { type: 'feed', feed: 'all' } : state.route;
      return { ...state, channels, userTags, route };
    }
    // ---- NEW: remove a channel ----
    case 'remove-channel': {
      const channels = state.channels.filter(c => c.id !== action.channelId);
      const videos = state.videos.filter(v => v.channelId !== action.channelId);
      const onChannel = state.route.type === 'channel' && state.route.id === action.channelId;
      const onWatch = state.route.type === 'watch' && !videos.find(v => v.id === state.route.id);
      const route = (onChannel || onWatch) ? { type: 'feed', feed: 'all' } : state.route;
      return { ...state, channels, videos, route };
    }
    // ---- NEW: notes ----
    case 'add-note': {
      const list = state.notes[action.videoId] || [];
      // atSec null = general note (not pinned to the timeline); sort timed first, general last by recency
      const note = { id: 'n-' + Date.now() + '-' + Math.floor(Math.random()*1000), atSec: action.atSec, text: action.text, createdAt: Date.now() };
      const sorted = [...list, note].sort((a, b) => {
        if (a.atSec == null && b.atSec == null) return a.createdAt - b.createdAt;
        if (a.atSec == null) return 1;
        if (b.atSec == null) return -1;
        return a.atSec - b.atSec;
      });
      return { ...state, notes: { ...state.notes, [action.videoId]: sorted } };
    }
    case 'update-note': {
      const list = (state.notes[action.videoId] || []).map(n => n.id === action.id ? { ...n, text: action.text } : n);
      return { ...state, notes: { ...state.notes, [action.videoId]: list } };
    }
    case 'remove-note': {
      const list = (state.notes[action.videoId] || []).filter(n => n.id !== action.id);
      return { ...state, notes: { ...state.notes, [action.videoId]: list } };
    }
    // ---- NEW: seek playhead ----
    case 'seek': {
      return { ...state, playhead: { ...state.playhead, [action.videoId]: action.sec } };
    }
    // ---- NEW: favorites ----
    case 'toggle-favorite': {
      const f = new Set(state.favorites);
      f.has(action.id) ? f.delete(action.id) : f.add(action.id);
      return { ...state, favorites: f };
    }
    // ---- NEW: attention budget — accumulate real playback seconds per local day ----
    case 'log-watch': {
      const key = action.day;
      const cur = state.watchLog[key] || 0;
      return { ...state, watchLog: { ...state.watchLog, [key]: cur + action.sec } };
    }
    // ---- NEW: add a single video without following the channel ----
    case 'add-single-video': {
      if (state.videos.find(v => v.id === action.video.id)) return state;
      let channels = state.channels;
      if (!channels.find(c => c.id === 'c-singles')) {
        channels = [...channels, {
          id: 'c-singles', handle: '@saved', name: 'Saved Videos',
          desc: 'Single videos you pulled in without following the channel.',
          subs: '—', joined: '—', accent: '#6e6c64', mark: '◇', tags: [],
        }];
      }
      const video = { ...action.video, channelId: 'c-singles', sourceName: action.sourceName || '' };
      return { ...state, channels, videos: [...state.videos, video] };
    }
    // ---- NEW: bulk-import videos (playlist import) into Saved Videos ----
    case 'add-videos-bulk': {
      const incoming = (action.videos || []).filter(v => !state.videos.find(x => x.id === v.id));
      if (!incoming.length) return state;
      let channels = state.channels;
      if (!channels.find(c => c.id === 'c-singles')) {
        channels = [...channels, {
          id: 'c-singles', handle: '@saved', name: 'Saved Videos',
          desc: 'Single videos you pulled in without following the channel.',
          subs: '—', joined: '—', accent: '#6e6c64', mark: '◇', tags: [],
        }];
      }
      const videos = incoming.map(v => ({ ...v, channelId: 'c-singles', sourceName: v.sourceName || '' }));
      return { ...state, channels, videos: [...state.videos, ...videos] };
    }
    case 'notif': {
      const next = { ...state, notifOpen: action.open };
      if (action.open) {
        // mark all current items as seen
        const seen = new Set(state.notifSeen);
        state.videos
          .filter(v => !state.watched.has(v.id) && !v.watched)
          .forEach(v => seen.add(v.id));
        next.notifSeen = seen;
      }
      return next;
    }
    case 'notif-mark-seen': {
      const seen = new Set(state.notifSeen);
      state.videos.forEach(v => seen.add(v.id));
      return { ...state, notifSeen: seen };
    }
    default: return state;
  }
}

// ------ Tweak defaults (host-rewritable) ------
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "direction": "grid",
  "accent": "#d83a1f",
  "dimAmount": 0.42,
  "showThumbnails": true,
  "compactRows": false
}/*EDITMODE-END*/;

// ------ localStorage persistence ------
const STORAGE_KEY = 'solotube/v3';

function loadPersisted(initial) {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return initial;
    const saved = JSON.parse(raw);
    return {
      ...initial,
      channels: Array.isArray(saved.channels) ? saved.channels : initial.channels,
      videos: Array.isArray(saved.videos) ? saved.videos : initial.videos,
      watched: new Set(saved.watched || []),
      hidden: new Set(saved.hidden || []),
      notifSeen: new Set(saved.notifSeen || []),
      theme: saved.theme ?? initial.theme,
      dimWatched: saved.dimWatched ?? initial.dimWatched,
      userTags: Array.isArray(saved.userTags) ? saved.userTags : initial.userTags,
      sidebarCollapsed: saved.sidebarCollapsed || initial.sidebarCollapsed,
      sidebarRail: saved.sidebarRail ?? initial.sidebarRail,
      notes: saved.notes || initial.notes,
      playhead: saved.playhead || initial.playhead,
      favorites: new Set(saved.favorites || []),
      archived: new Set(saved.archived || []),
      lastDigestSent: saved.lastDigestSent || null,
      watchLog: saved.watchLog || initial.watchLog,
      settings: { ...initial.settings, ...(saved.settings || {}) },
    };
  } catch {
    return initial;
  }
}

function persistState(state) {
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify({
      channels: state.channels,
      videos: state.videos,
      watched: [...state.watched],
      hidden: [...state.hidden],
      notifSeen: [...state.notifSeen],
      theme: state.theme,
      dimWatched: state.dimWatched,
      userTags: state.userTags,
      sidebarCollapsed: state.sidebarCollapsed,
      sidebarRail: state.sidebarRail,
      notes: state.notes,
      playhead: state.playhead,
      favorites: [...state.favorites],
      archived: [...state.archived],
      lastDigestSent: state.lastDigestSent,
      watchLog: state.watchLog,
      settings: state.settings,
    }));
  } catch {
    /* quota or private mode — ignore */
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, INITIAL, loadPersisted);

  // Persist on any state change worth keeping across reloads
  useEffectApp(() => { persistState(state); }, [
    state.channels, state.videos, state.watched, state.hidden, state.notifSeen,
    state.theme, state.dimWatched, state.userTags,
    state.sidebarCollapsed, state.sidebarRail,
    state.notes, state.playhead, state.settings,
    state.favorites, state.archived, state.watchLog,
  ]);
  const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);

  // Apply direction + theme to <body>
  useEffectApp(() => {
    document.body.setAttribute('data-direction', t.direction);
    document.body.setAttribute('data-theme', state.theme);
  }, [t.direction, state.theme]);

  // Apply accent + dim CSS variable
  useEffectApp(() => {
    document.documentElement.style.setProperty('--accent', t.accent);
    document.documentElement.style.setProperty('--new', t.accent);
    document.documentElement.style.setProperty('--dim', String(state.dimWatched ? t.dimAmount : 1));
  }, [t.accent, t.dimAmount, state.dimWatched]);

  // Apply compact rows + show-thumbnails
  useEffectApp(() => {
    if (t.compactRows) {
      document.documentElement.style.setProperty('--row-pad', '12px');
      document.documentElement.style.setProperty('--thumb-w', '160px');
      document.documentElement.style.setProperty('--thumb-h', '90px');
    } else {
      document.documentElement.style.removeProperty('--row-pad');
      document.documentElement.style.removeProperty('--thumb-w');
      document.documentElement.style.removeProperty('--thumb-h');
    }
    // Hidden if either the user setting or the dev tweak says so.
    const showThumbs = t.showThumbnails && state.settings.thumbnails !== false;
    document.documentElement.style.setProperty('--thumb-display', showThumbs ? 'block' : 'none');
  }, [t.compactRows, t.showThumbnails, state.settings.thumbnails]);

  // Deep link: /?add=<videoId or YouTube URL> opens the add flow prefilled.
  // This is the entry point for the browser extension and share links.
  useEffectApp(() => {
    const params = new URLSearchParams(window.location.search);
    const add = params.get('add');
    if (add) {
      const url = /^[\w-]{11}$/.test(add) ? 'https://www.youtube.com/watch?v=' + add : add;
      const intent = params.get('intent') || null; // 'single' | 'channel'
      dispatch({ type: 'open-add', url, intent });
      window.history.replaceState({}, '', window.location.pathname);
    }
  }, []);

  // Process extension quick-saves relayed by content_solotube.js via postMessage
  useEffectApp(() => {
    function handleMsg(e) {
      if (e.source !== window || e.data?.type !== 'SOLOTUBE_QUICK_QUEUE') return;
      const items = e.data.items || [];
      items.forEach(item => {
        if (item.type === 'video' && item.videoId) {
          fetch(`/api/video?id=${encodeURIComponent(item.videoId)}`)
            .then(r => r.ok ? r.json() : null)
            .then(data => { if (data?.video?.id) dispatch({ type: 'add-single-video', video: data.video }); })
            .catch(() => {});
        } else if (item.type === 'channel' && item.target) {
          fetch('/api/resolve-channel?url=' + encodeURIComponent(item.target))
            .then(r => r.ok ? r.json() : null)
            .then(parsed => {
              if (!parsed?.id) return;
              const u = new URL('/api/channel-videos', window.location.origin);
              u.searchParams.set('channelId', parsed.id);
              if (parsed.uploadsPlaylistId) u.searchParams.set('uploadsPlaylistId', parsed.uploadsPlaylistId);
              return fetch(u).then(r => r.ok ? r.json() : null)
                .then(body => {
                  dispatch({ type: 'add-channel', channel: { ...parsed, tags: [] }, videos: body?.videos || [] });
                });
            })
            .catch(() => {});
        }
      });
    }
    window.addEventListener('message', handleMsg);
    return () => window.removeEventListener('message', handleMsg);
  }, []);

  // Auto-send weekly digest when the schedule day + hour arrives
  useEffectApp(() => {
    const { settings, lastDigestSent } = state;
    if (!settings.digestScheduleEnabled || !settings.digestEmail?.trim()) return;
    // Don't re-send if already sent in the last 6 days
    if (lastDigestSent && (Date.now() - lastDigestSent) < 6 * 24 * 60 * 60 * 1000) return;
    const tz = settings.digestTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
    try {
      const parts = new Intl.DateTimeFormat('en-US', {
        timeZone: tz, weekday: 'short', hour: 'numeric', hour12: false,
      }).formatToParts(new Date());
      const dayMap = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
      const currentDay = dayMap[parts.find(p => p.type === 'weekday')?.value] ?? -1;
      const currentHour = parseInt(parts.find(p => p.type === 'hour')?.value || '-1', 10);
      if (currentDay !== settings.digestScheduleDay) return;
      if (currentHour < settings.digestScheduleHour) return;
      (async () => {
        const stats = window.compileDigestStats ? window.compileDigestStats(state) : null;
        if (!stats) return;
        const r = await fetch('/api/digest', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email: settings.digestEmail, stats }),
        });
        if (r.ok) dispatch({ type: 'set-last-digest-sent', timestamp: Date.now() });
      })().catch(() => {});
    } catch {}
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  // Escape closes modal / popovers
  useEffectApp(() => {
    function onKey(e) {
      if (e.key === 'Escape') {
        if (state.addOpen) dispatch({ type: 'close-add' });
        if (state.scoutOpen) dispatch({ type: 'close-scout' });
        if (state.importOpen) dispatch({ type: 'close-import' });
        if (state.notifOpen) dispatch({ type: 'notif', open: false });
      }
    }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [state.addOpen, state.scoutOpen, state.importOpen, state.notifOpen]);

  return (
    <>
      <div className={`app ${state.focusMode ? 'focus' : ''} ${state.sidebarRail ? 'rail' : ''} ${state.mnav ? 'mnav' : ''}`}>
        <div className="mobile-bar">
          <button className="mb-menu" onClick={() => dispatch({ type: 'toggle-mnav' })} aria-label="Menu">
            {state.mnav ? '✕' : '☰'}
          </button>
          <button className="mb-brand" onClick={() => dispatch({ type: 'route', route: { type: 'feed', feed: 'all' } })}>
            <img src="/assets/solotube.png" width="22" height="22" alt="SoloTube" style={{display:'inline-block',flexShrink:0}} />
            SOLOTUBE
          </button>
          <button className="mb-add" onClick={() => dispatch({ type: 'open-add' })} aria-label="Add channel">+</button>
        </div>
        {state.mnav && <div className="mnav-back" onClick={() => dispatch({ type: 'toggle-mnav' })}></div>}
        <window.Sidebar state={state} dispatch={dispatch} />
        {state.route.type === 'watch'
          ? <window.Watch state={state} dispatch={dispatch} />
          : state.route.type === 'stats'
          ? <window.Stats state={state} dispatch={dispatch} />
          : <window.Feed state={state} dispatch={dispatch} />}

        <nav className="bottom-nav" aria-label="Main navigation">
          <button
            className={`bn-tab ${(state.route.type === 'feed' || state.route.type === 'channel' || state.route.type === 'tag') ? 'active' : ''}`}
            onClick={() => dispatch({ type: 'route', route: { type: 'feed', feed: 'all' } })}
          >
            <svg className="bn-svg bn-feed" viewBox="0 0 24 24" fill="none">
              <rect x="3" y="3" width="8" height="8" rx="1.5" stroke="currentColor" strokeWidth="1.6"/>
              <rect x="13" y="3" width="8" height="8" rx="1.5" stroke="currentColor" strokeWidth="1.6"/>
              <rect x="3" y="13" width="8" height="8" rx="1.5" stroke="currentColor" strokeWidth="1.6"/>
              <rect x="13" y="13" width="8" height="8" rx="1.5" stroke="currentColor" strokeWidth="1.6"/>
            </svg>
            <span className="bn-label">Feed</span>
          </button>
          <button
            className={`bn-tab ${state.route.type === 'watch' ? 'active' : ''}`}
            style={{ opacity: state.lastWatchId ? 1 : 0.35 }}
            onClick={() => state.lastWatchId && dispatch({ type: 'route', route: { type: 'watch', id: state.lastWatchId } })}
          >
            <svg className="bn-svg bn-watch" viewBox="0 0 24 24" fill="none">
              <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.6"/>
              <path className="bn-play" d="M10.5 8.5l5.5 3.5-5.5 3.5V8.5z" fill="currentColor"/>
            </svg>
            <span className="bn-label">Watch</span>
          </button>
          <button
            className={`bn-tab ${state.scoutOpen ? 'active' : ''}`}
            onClick={() => dispatch({ type: 'open-scout' })}
          >
            <svg className="bn-svg bn-scout" viewBox="0 0 24 24" fill="none">
              <circle cx="10.5" cy="10.5" r="6.5" stroke="currentColor" strokeWidth="1.6"/>
              <path d="M15.5 15.5L19.5 19.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
            </svg>
            <span className="bn-label">Scout</span>
          </button>
          <button
            className={`bn-tab ${state.route.type === 'stats' ? 'active' : ''}`}
            onClick={() => dispatch({ type: 'route', route: { type: 'stats' } })}
          >
            <svg className="bn-svg bn-library" viewBox="0 0 24 24" fill="none">
              <path d="M4 6h16" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
              <path d="M4 12h16" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
              <path d="M4 18h10" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
            </svg>
            <span className="bn-label">Library</span>
          </button>
        </nav>
      </div>

      {state.addOpen && <window.AddChannel state={state} dispatch={dispatch} />}
      {state.scoutOpen && <window.Scout state={state} dispatch={dispatch} />}
      {state.importOpen && <window.ImportModal state={state} dispatch={dispatch} />}
      {state.tagEditor && <window.TagEditor state={state} dispatch={dispatch} />}
      {state.settingsOpen && <window.SettingsPopover state={state} dispatch={dispatch} />}

      <window.TweaksPanel title="Tweaks">
        <window.TweakSection label="Visual direction">
          <window.TweakSelect
            label="Direction"
            value={t.direction}
            options={[
              { value: 'grid',  label: 'Grid · grotesk + hairlines' },
              { value: 'mono',  label: 'Mono · terminal index' },
              { value: 'bold',  label: 'Bold · oversized swiss' },
            ]}
            onChange={(v) => setTweak('direction', v)}
          />
        </window.TweakSection>

        <window.TweakSection label="Watched videos">
          <window.TweakSlider
            label="Dim amount" value={Math.round(t.dimAmount * 100)} min={15} max={85} step={1} unit="%"
            onChange={(v) => setTweak('dimAmount', v / 100)}
          />
          <window.TweakToggle
            label="Compact rows" value={t.compactRows}
            onChange={(v) => setTweak('compactRows', v)}
          />
          <window.TweakToggle
            label="Show thumbnails" value={t.showThumbnails}
            onChange={(v) => setTweak('showThumbnails', v)}
          />
        </window.TweakSection>

        <window.TweakSection label="Accent (NEW indicator)">
          <window.TweakColor
            label="Accent" value={t.accent}
            options={['#d83a1f', '#ff5a3c', '#0a5cff', '#1f7a3a', '#a3a3a3']}
            onChange={(v) => setTweak('accent', v)}
          />
        </window.TweakSection>
      </window.TweaksPanel>
    </>
  );
}

window.App = App;
