<script>
  import { onMount, tick } from 'svelte';
  
  let videos = $state([]);
  let selectedVideo = $state(null);
  let activeTab = $state('pending');
  let isLoading = $state(false);
  let isSyncing = $state(false);
  let syncMessage = $state('');
  let pendingCount = $state(0);
  let theme = $state(localStorage.getItem('theme') || 'dark');
  let apiToken = $state(localStorage.getItem('t-yt-token') || '');
  let apiTokenInput = $state('');
  let showTokenSetup = $state(false);
  let summaryPanel = $state(null);
  let notes = $state({});
  let activeNote = $state(null);
  let queueToAgent = $state(false);
  let showHelp = $state(false);
  let channelFilter = $state('');
  let allChannels = $state([]);
  let editingCategories = $state(false);
  let prevSelectedId = $state(null);
  
  // Search state
  let searchQuery = $state('');
  let page = $state(0);
  let hasMore = $state(true);
  let isMoreLoading = $state(false);
  let showTranscript = $state(false);
  let searchResults = $state([]);
  let isSearching = $state(false);
  let showSearchResults = $state(false);
  
  // Batch select state
  let selectedVideoIds = $state(new Set());
  let showBatchBar = $state(false);
  
  // Stats state
  let statsData = $state(null);
  let showStats = $state(false);
  
  // Agent queue state
  let agentItems = $state([]);
  let agentLoading = $state(false);
  
  // Stats fetcher
  async function loadStats() {
    try {
      const res = await fetch('/api/stats');
      if (res.ok) statsData = await res.json();
    } catch(e) {}
  }
  
  $effect(() => {
    if (activeTab === 'stats' && !statsData) loadStats();
  });
  

  
  // Auto-generated categories (AI-categorized)
  const AUTO_CATEGORIES = {"AI Engineer":"AI & Tech","AI-Fan AI研究室-帆哥":"AI & Tech","AI大模型":"AI & Tech","AI探索与发现":"AI & Tech","Aurora Tech":"AI & Tech","Claude":"AI & Tech","ColdFusion":"AI & Tech","Data Is Beautiful":"AI & Tech","DeepLearningAI":"AI & Tech","Google":"AI & Tech","Google Cloud Tech":"AI & Tech","Lex Fridman":"AI & Tech","NiceKate AI":"AI & Tech","PC Sim":"AI & Tech","Stanford Online":"AI & Tech","The Linux Foundation":"AI & Tech","回到Axton":"AI & Tech","回形针PaperClip":"AI & Tech","林亦LYi":"AI & Tech","硅谷101播客":"AI & Tech","老石谈芯":"AI & Tech","鲲鹏Talk":"AI & Tech","3D Beast":"3D & Design","Arrimus3D Modeling and Design":"3D & Design","Blender":"3D & Design","Blender Guru":"3D & Design","Creators 3D":"3D & Design","Designfusion":"3D & Design","Foundry":"3D & Design","Grant Abbitt (Gabbitt)":"3D & Design","Gravity Sketch":"3D & Design","Hyperganic":"3D & Design","MSLattice":"3D & Design","Manolo Remiddi":"3D & Design","Rhino 3D (Rhinoceros3d official)":"3D & Design","Shaper3d":"3D & Design","nTop":"3D & Design","spherene AG":"3D & Design","魔界造物":"3D & Design","Chuck Severance":"Programming","Coursera":"Programming","freeCodeCamp.org":"Programming","Kevin Stratvert":"Programming","Leila Gharani":"Programming","LinkedIn Learning":"Programming","Metabase":"Programming","n8n":"Programming","The Coding Gopher":"Programming","Brian Casel":"Programming","code秘密花园":"Programming","cs tech":"Programming","fequalsf":"Programming","openscreencast":"Programming","技术爬爬虾  TechShrimp":"Programming","林本兔Limbuntu":"Programming","Best Choice Review":"Hardware & Reviews","Marques Brownlee":"Hardware & Reviews","TESTV":"Hardware & Reviews","先看评测":"Hardware & Reviews","极客湾Geekerwan":"Hardware & Reviews","硬核拆解":"Hardware & Reviews","笔吧评测室":"Hardware & Reviews","差评硬件部":"Hardware & Reviews","充电头网":"Hardware & Reviews","Apple Developer":"Apple Dev","Made by Google":"Apple Dev","Allen的分享":"Apple Dev","Sypnotix":"Apple Dev","8K World":"Photography","Can Fiona":"Photography","小天fotos":"Photography","影像极客Fotogeeker":"Photography","摄影师PHiL":"Photography","虚空光影CosmosFilm":"Photography","路易斯 LouisDrone":"Photography","Bruno Mars":"Music","GraceLeeMusic":"Music","Katie Melua":"Music","The Weeknd":"Music","TheWeekndVEVO":"Music","杰威爾歌詞MV頻道JVR Lyric MV":"Music","薛汀哲":"Music","Salah Trainer":"Sports","Skills N Talents (swimming)":"Sports","TotalSports TV":"Sports","WorldSBK":"Sports","Everyday Cycling":"Sports","爱羽客羽毛球网":"Sports","adidas":"Sports","Sneaks & Feet极客鞋谈":"Automotive","TOP的遥控玩具":"Automotive","悟空的日常":"Automotive"};
  
  let categories = $state({...AUTO_CATEGORIES});
  
  let channels = $derived([...new Set(videos.map(v => v.channel_name))].sort());
  let filteredVideos = $derived(
    searchQuery.trim().length >= 2
      ? searchResults
      : channelFilter
        ? channelFilter.startsWith('📁')
          ? videos.filter(v => (categories[v.channel_name] || '') === channelFilter.replace('📁 ', ''))
          : videos.filter(v => v.channel_name === channelFilter)
        : videos
  );
  let catList = $derived([...new Set(Object.values(categories).flat())].sort());
  
  let noteIds = $derived(new Set(Object.keys(notes).filter(k => notes[k] && notes[k].trim())));
  
  // Search debouncer
  let searchTimeout = null;
  async function handleSearch(query) {
    searchQuery = query;
    if (searchTimeout) clearTimeout(searchTimeout);
    if (!query || query.trim().length < 2) {
      showSearchResults = false;
      searchResults = [];
      return;
    }
    searchTimeout = setTimeout(async () => {
      isSearching = true;
      try {
        const res = await fetch(`/api/videos/search?q=${encodeURIComponent(query)}&limit=50`);
        if (res.ok) {
          searchResults = await res.json();
          showSearchResults = true;
        }
      } catch(e) {
        console.error('Search failed:', e);
      } finally {
        isSearching = false;
      }
    }, 250);
  }

  async function handleSidebarScroll(e) {
    if (searchQuery.trim().length >= 2) return;
    if (isLoading || isMoreLoading || !hasMore) return;
    const container = e.target;
    const threshold = 100;
    const atBottom = (container.scrollHeight - container.scrollTop - container.clientHeight) < threshold;
    if (atBottom) {
      isMoreLoading = true;
      page += 1;
      await loadVideos(true);
      isMoreLoading = false;
    }
  }
  
  // Batch operations
  function toggleSelect(videoId) {
    const newSet = new Set(selectedVideoIds);
    if (newSet.has(videoId)) {
      newSet.delete(videoId);
    } else {
      newSet.add(videoId);
    }
    selectedVideoIds = newSet;
    showBatchBar = newSet.size > 0;
  }
  
  function selectAll() {
    selectedVideoIds = new Set(filteredVideos.map(v => v.video_id));
    showBatchBar = true;
  }
  
  function clearSelection() {
    selectedVideoIds = new Set();
    showBatchBar = false;
  }
  
  async function batchUpdate(status) {
    if (selectedVideoIds.size === 0) return;
    const ids = Array.from(selectedVideoIds);
    try {
      const res = await fetch('/api/videos/batch', {
        method: 'POST',
        headers: getAuthHeaders({ 'Content-Type': 'application/json' }),
        body: JSON.stringify({ video_ids: ids, status })
      });
      if (res.ok) {
        // Remove selected from local list
        videos = videos.filter(v => !ids.includes(v.video_id));
        clearSelection();
        loadPendingCount();
      }
    } catch(e) {
      console.error('Batch update failed:', e);
    }
  }
  
  function formatDuration(seconds) {
    if (!seconds || seconds < 60) return '';
    const m = Math.floor(seconds / 60);
    const s = seconds % 60;
    return `${m}:${s.toString().padStart(2, '0')}`;
  }
  
  function formatViews(count) {
    if (!count || count < 1000) return count || '';
    if (count < 1000000) return `${(count / 1000).toFixed(1)}K`;
    return `${(count / 1000000).toFixed(1)}M`;
  }
  
  function toggleTheme() {
    theme = theme === 'dark' ? 'light' : 'dark';
    localStorage.setItem('theme', theme);
    document.documentElement.className = theme;
  }
  
  async function loadVideos(isAppend = false) {
    if (!isAppend) {
      page = 0;
      hasMore = true;
      isLoading = true;
    }
    try {
      const limit = 50;
      const offset = page * limit;
      
      if (activeTab === 'notes') {
        const params = new URLSearchParams({ status: 'pending', limit: limit.toString(), offset: offset.toString() });
        const res = await fetch(`/api/videos?${params}`);
        if (res.ok) {
          const fetched = await res.json();
          const filtered = fetched.filter(v => noteIds.has(v.video_id));
          if (isAppend) {
            videos = [...videos, ...filtered];
          } else {
            videos = filtered;
            if (videos.length > 0) selectedVideo = videos[0];
            else selectedVideo = null;
          }
          if (fetched.length < limit) hasMore = false;
        }
      } else if (activeTab !== 'stats' && activeTab !== '🤖 Agent') {
        const params = new URLSearchParams({ status: activeTab, limit: limit.toString(), offset: offset.toString() });
        if (channelFilter && !channelFilter.startsWith('📁')) {
          params.set('channel', channelFilter);
        }
        const res = await fetch(`/api/videos?${params}`);
        if (res.ok) {
          const fetched = await res.json();
          if (isAppend) {
            videos = [...videos, ...fetched];
          } else {
            videos = fetched;
            if (videos.length > 0) {
              const currentId = selectedVideo?.video_id;
              const found = videos.find(v => v.video_id === currentId);
              selectedVideo = found || videos[0];
            } else {
              selectedVideo = null;
            }
          }
          if (fetched.length < limit) hasMore = false;
        }
      }
    } catch (err) {
      console.error("Error loading videos:", err);
    } finally {
      isLoading = false;
    }
  }
  
  async function syncSubscriptions() {
    isSyncing = true;
    syncMessage = 'Syncing...';
    try {
      const res = await fetch('/api/videos/fetch', { method: 'POST', headers: getAuthHeaders() });
      if (res.ok) {
        syncMessage = 'Sync started...';
        setTimeout(() => { loadVideos(); isSyncing = false; syncMessage = ''; }, 6000);
      } else {
        isSyncing = false;
        syncMessage = 'Sync failed';
      }
    } catch (err) { isSyncing = false; syncMessage = 'Network error'; }
  }
  
  async function updateStatus(videoId, newStatus, silent = false) {
    await fetch(`/api/videos/${videoId}/status`, { method: 'POST', headers: getAuthHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ status: newStatus }) });
    if (!silent) {
      videos = videos.filter(v => v.video_id !== videoId);
      if (selectedVideo?.video_id === videoId) selectedVideo = videos[0] || null;
      if (videos.length < 20) loadVideos();
    }
  }
  
  async function generateSummary(videoId) {
    if (selectedVideo?.video_id === videoId) selectedVideo.summary = "Generating...";
    await fetch(`/api/videos/${videoId}/summarize`, { method: 'POST', headers: getAuthHeaders() });
    setTimeout(() => loadVideos(), 3000);
  }
  
  // ---- Server-side notes ----
  
  async function loadNotes() {
    try {
      const res = await fetch('/api/notes');
      if (res.ok) {
        const all = await res.json();
        const map = {};
        for (const item of all) map[item.video_id] = item.note_text;
        notes = map;
      }
    } catch(e) { console.error('Failed to load notes:', e); }
  }
  
  async function migrateLocalNotes() {
    try {
      const local = JSON.parse(localStorage.getItem('t-yt-notes') || '{}');
      const keys = Object.keys(local).filter(k => local[k].trim());
      if (keys.length === 0) return;
      const missing = keys.filter(k => !notes[k]);
      if (missing.length === 0) return;
      const payload = {};
      for (const k of missing) payload[k] = local[k];
      await fetch('/api/notes/migrate', {
        method: 'POST',
        headers: getAuthHeaders({ 'Content-Type': 'application/json' }),
        body: JSON.stringify({ notes: payload })
      });
      await loadNotes();
    } catch(e) {}
  }
  
  async function saveNote() {
    const vid = selectedVideo?.video_id;
    if (!vid) return;
    const textarea = document.querySelector('#note-textarea');
    const current = textarea ? textarea.value : (notes[vid] || '');
    if (!current.trim()) return;
    // Save to server
    await fetch(`/api/notes/${vid}`, {
      method: 'POST',
      headers: getAuthHeaders({ 'Content-Type': 'application/json' } ),
      body: JSON.stringify({ note_text: current, queue_to_agent: queueToAgent })
    });
    // Update local state
    notes = {...notes, [vid]: current};
    activeNote = null;
    queueToAgent = false;
  }
  
  // ---- Agent queue ----
  
  async function loadAgentQueue() {
    agentLoading = true;
    try {
      const res = await fetch('/api/agent-queue');
      if (res.ok) agentItems = await res.json();
    } catch(e) { console.error('Failed to load agent queue:', e); }
    finally { agentLoading = false; }
  }
  
  async function removeAgentItem(id) {
    await fetch(`/api/agent-queue/${id}`, { method: 'DELETE', headers: getAuthHeaders() });
    agentItems = agentItems.filter(i => i.id !== id);
  }
  
  async function initAuth() {
    // Check if auth is enabled on the backend
    try {
      const res = await fetch('/api/config');
      if (res.ok) {
        const config = await res.json();
        if (config.auth_enabled && !apiToken) {
          showTokenSetup = true;
          return false;
        }
      }
    } catch(e) {}
    showTokenSetup = false;
    return true;
  }
  
  async function setupToken() {
    if (apiTokenInput.trim()) {
      apiToken = apiTokenInput.trim();
      localStorage.setItem('t-yt-token', apiToken);
      showTokenSetup = false;
    }
  }
  
  function getAuthHeaders(extraHeaders = {}) {
    const headers = { ...extraHeaders };
    if (apiToken) {
      headers['Authorization'] = 'Bearer ' + apiToken;
    }
    return headers;
  }
  
  // ---- Lifecycle ----
  
  
  $effect(() => {
    if (activeTab && activeTab !== 'stats' && activeTab !== '🤖 Agent') {
      // Clear search when changing tab or filter
      searchQuery = '';
      searchResults = [];
      showSearchResults = false;
      loadVideos(false);
    }
  });

  $effect(() => {
    // Scroll to top when switching videos
    if (selectedVideo && summaryPanel) {
      tick().then(() => summaryPanel.scrollTop = 0);
    }
    const prevId = prevSelectedId;
    prevSelectedId = selectedVideo?.video_id || null;
    showTranscript = false; // Reset accordion on video switch
  });
  
  // Scroll-based auto-mark-read: mark video as read when user scrolls to bottom of summary
  let scrollReadTimer = $state(null);
  function handleSummaryScroll() {
    if (!summaryPanel || !selectedVideo || activeTab !== 'pending') return;
    const threshold = 100; // px from bottom
    const atBottom = (summaryPanel.scrollHeight - summaryPanel.scrollTop - summaryPanel.clientHeight) < threshold;
    if (atBottom) {
      // Debounce to avoid rapid-fire updates
      if (scrollReadTimer) clearTimeout(scrollReadTimer);
      scrollReadTimer = setTimeout(() => {
        const sv = selectedVideo;
        if (sv && sv.summary && !sv.summary.includes('Error') && !sv.summary.includes('📺 Short clip')) {
          updateStatus(sv.video_id, 'read', true);
        }
      }, 500);
    }
  }
  $effect(() => {
    if (activeTab === 'agent') loadAgentQueue();
  });
  
  onMount(async () => {
    document.documentElement.className = theme;
    await initAuth();
    await loadNotes();
    // Migrate localStorage notes to server (non-blocking)
    migrateLocalNotes();
    await loadVideos();
    loaded = true;
    loadPendingCount();
    updateSyncTimer();
    loadChannels();
    window.addEventListener('keydown', handleKeydown);
    return () => window.removeEventListener('keydown', handleKeydown);
  });
  
  function updateSyncTimer() {
    const el = document.getElementById('sync-timer');
    if (!el) return;
    const now = new Date();
    const next = new Date(now);
    next.setHours(3, 0, 0, 0);
    if (now > next) next.setDate(next.getDate() + 1);
    const diff = next - now;
    const h = Math.floor(diff / 3600000);
    const m = Math.floor((diff % 3600000) / 60000);
    el.textContent = `Next sync in ${h}h ${m}m`;
    setTimeout(updateSyncTimer, 60000);
  }
  
  async function loadPendingCount() {
    try {
      const res = await fetch('/api/videos?status=pending&limit=201');
      if (res.ok) {
        const all = await res.json();
        pendingCount = all.length >= 201 ? '200+' : all.length;
      }
    } catch(e) {}
  }
  
  async function loadChannels() {
    try {
      const res = await fetch('/api/channels');
      if (res.ok) allChannels = await res.json();
    } catch(e) {}
  }
  
  function handleKeydown(e) {
    if (!videos.length) return;
    if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') return;
    // If no video selected, let keyboard scroll the page naturally
    if (!selectedVideo) return;
    const idx = filteredVideos.findIndex(v => v.video_id === selectedVideo.video_id);
    let nextIdx = idx;
    if (e.key === 'j' || e.key === 'ArrowDown') {
      e.preventDefault();
      if (idx < filteredVideos.length - 1) nextIdx = idx + 1; else return;
    } else if (e.key === 'k' || e.key === 'ArrowUp') {
      e.preventDefault();
      if (idx > 0) nextIdx = idx - 1; else return;
    } else if (e.key === 'r' || e.key === 'm') {
      e.preventDefault();
      if (selectedVideo) updateStatus(selectedVideo.video_id, 'read');
      return;
    } else if (e.key === 's') {
      e.preventDefault();
      if (selectedVideo) updateStatus(selectedVideo.video_id, 'skipped');
      return;
    } else if (e.key === 'n') {
      e.preventDefault();
      if (selectedVideo) activeNote = selectedVideo.video_id;
      return;
    } else if (e.key === 'Enter') {
      e.preventDefault();
      if (activeNote && selectedVideo && activeNote === selectedVideo.video_id) {
        saveNote();
      } else if (selectedVideo) {
        updateStatus(selectedVideo.video_id, 'read');
      }
      return;
    } else if (e.key === '?') {
      e.preventDefault();
      showHelp = !showHelp;
      return;
    } else {
      return;
    }
    selectedVideo = filteredVideos[nextIdx];
    scrollToVideo(nextIdx);
  }
  
  function scrollToVideo(index) {
    const el = document.querySelector(`[data-video-index="${index}"]`);
    if (el) el.scrollIntoView({ block: 'nearest', behavior: 'instant' });
  }
  
  function autoFocus(node) {
    requestAnimationFrame(() => node.focus());
  }
  
  function formatMarkdown(text) {
    if (!text) return '<p class="opacity-50 italic">No summary yet.</p>';
    let html = text.replace(/\*\*(.*?)\*\*/g, '<strong class="font-bold" style="color:var(--accent)">$1</strong>');
    html = html.replace(/^\s*[-*]\s+(.*)$/gm, '<li class="ml-5 list-disc mb-1" style="color:var(--text-primary)">$1</li>');
    html = html.split('\n').map(line => {
      if (line.trim().startsWith('<li')) return line;
      if (line.trim() === '') return '<div class="h-2"></div>';
      return `<p class="mb-2 leading-relaxed">${line}</p>`;
    }).join('');
    return html;
  }
  
  function noteWordCount(noteText) {
    if (!noteText) return 0;
    return noteText.trim().split(/\s+/).length;
  }
  
  function formatDate(d) {
    if (!d) return '';
    return d.slice(0, 10);
  }
  
  // Stats display helpers
  function formatStatsNumber(n) {
    if (n >= 1000000) return `${(n/1000000).toFixed(1)}M`;
    if (n >= 1000) return `${(n/1000).toFixed(1)}K`;
    return n.toString();
  }
</script>

<div style="background:var(--bg-primary);color:var(--text-primary);height:100vh;overflow:hidden;display:flex;flex-direction:column">
    <!-- Token Setup Modal -->
    {#if showTokenSetup}
    <div class="fixed inset-0 z-50 flex items-center justify-center" style="background:rgba(0,0,0,0.5)">
      <div class="bg-[var(--bg-secondary)] p-6 rounded-lg max-w-md w-full mx-4"
           style="border:1px solid var(--border)">
        <h2 class="text-xl font-bold mb-2">API Token Required</h2>
        <p class="text-sm mb-4" style="color:var(--text-secondary)">
          This app requires an API token to save notes and manage videos.
          Please enter the token configured on the server.
        </p>
        <input type="text" bind:value={apiTokenInput}
               placeholder="Enter API token..." class="w-full px-3 py-2 rounded"
               style="background:var(--bg-primary);border:1px solid var(--border);color:var(--text-primary)"
               onkeydown={(e) => e.key === 'Enter' && setupToken()} />
        <div class="flex space-x-2 mt-4">
          <button onclick={setupToken}
                  class="px-4 py-2 rounded text-white font-bold"
                  style="background:var(--accent)">
            Submit
          </button>
        </div>
      </div>
    </div>
    {/if}

  <header style="background:var(--bg-secondary);border-bottom:1px solid var(--border);" class="flex items-center justify-between px-5 py-2.5">
    <div class="flex items-center space-x-3">
      <span class="text-lg font-black tracking-wider" style="color:var(--accent)">T YouTube</span>
      <span class="text-[10px] px-2 py-0.5 rounded font-semibold uppercase tracking-widest" style="background:var(--bg-tertiary);color:var(--text-muted)">text feed</span>
    </div>
    <div class="flex items-center space-x-3">
      <button onclick={syncSubscriptions} disabled={isSyncing}
        class="px-3 py-1.5 text-xs font-bold rounded-lg transition"
        style="background:var(--accent);color:white">
        {isSyncing ? 'Syncing...' : 'Sync'}
      </button>
      <button onclick={toggleTheme}
        class="text-sm px-2 py-1 rounded transition" style="color:var(--text-muted)"
        title="Toggle theme">
        {theme === 'dark' ? '☀' : '☾'}
      </button>
      {#if !isSyncing}
        <span class="text-[10px]" style="color:var(--text-muted)" id="sync-timer"></span>
      {/if}
    </div>
  </header>

  <div class="flex flex-1 overflow-hidden">
    <!-- Left sidebar -->
    <aside style="width:280px;background:var(--bg-secondary);border-right:1px solid var(--border)" class="flex flex-col flex-shrink-0">
      <!-- Tabs -->
      <div class="flex p-1.5" style="border-bottom:1px solid var(--border)">
        {#each ['pending','read','skipped','notes','stats','🤖 Agent'] as tab}
          <button onclick={() => activeTab = tab}
            class="flex-1 py-1.5 text-center text-[11px] font-bold rounded uppercase transition"
            style="color: {activeTab === tab ? 'var(--accent)' : 'var(--text-muted)'}">
            {tab === 'pending' && pendingCount ? `Pending (${pendingCount})` : tab === 'stats' ? '📊 Stats' : tab === '🤖 Agent' ? '🤖 Agent' : tab}
          </button>
        {/each}
      </div>

      <!-- Search bar (shown in pending/read/skipped tabs) -->
      {#if activeTab !== '🤖 Agent' && activeTab !== 'stats' && activeTab !== 'notes'}
        <div class="px-2 py-1.5" style="border-bottom:1px solid var(--border)">
          <div class="relative">
            <input type="text" 
                   value={searchQuery}
                   oninput={(e) => handleSearch(e.target.value)}
                   placeholder="Search videos..."
                   class="w-full text-[11px] p-1.5 pr-8 rounded border"
                   style="background:var(--bg-primary);color:var(--text-primary);border-color:var(--border)">
            {#if searchQuery}
              <button onclick={() => { searchQuery = ''; searchResults = []; showSearchResults = false; }}
                      class="absolute right-7 top-2 text-[10px] text-gray-500 hover:text-white transition">✕</button>
            {/if}
            {#if isSearching}
              <div class="absolute right-2 top-2 text-[9px] animate-spin" style="color:var(--accent)">↻</div>
            {/if}
          </div>
        </div>
      {/if}

      <!-- Agent Queue tab content -->
      {#if activeTab === '🤖 Agent'}
        <div class="flex-1 overflow-y-auto" style="border-color:var(--border)">
          {#if agentLoading}
            <div class="flex items-center justify-center py-8 text-xs" style="color:var(--text-muted)">Loading...</div>
          {:else if agentItems.length === 0}
            <div class="flex items-center justify-center py-8 text-xs" style="color:var(--text-muted)">
              <p>No items queued.<br>Write a note on a video and check "Send to Agent" to add one.</p>
            </div>
          {:else}
            {#each agentItems as item}
              <div class="p-3 border-b" style="border-color:var(--border)">
                <div class="flex items-start space-x-2">
                  <div class="min-w-0 flex-1">
                    <p class="text-[11px] font-bold leading-tight line-clamp-2" style="color:var(--text-primary)">{item.title || 'Unknown'}</p>
                    <p class="text-[10px] mt-1" style="color:var(--accent)">{item.channel_name || ''}</p>
                    <p class="text-[10px] mt-1" style="color:var(--text-muted)">Prompt: {item.user_prompt.slice(0, 120)}{item.user_prompt.length > 120 ? '...' : ''}</p>
                    <div class="flex items-center space-x-2 mt-1.5">
                      <span class="text-[9px] px-1.5 py-0.5 rounded font-bold uppercase"
                        style="background:{item.status === 'done' ? '#10b981' : item.status === 'processing' ? '#f59e0b' : item.status === 'failed' ? '#ef4444' : 'var(--bg-tertiary)'};color:white">
                        {item.status}
                      </span>
                      <span class="text-[9px]" style="color:var(--text-muted)">{formatDate(item.created_at)}</span>
                    </div>
                    {#if item.status === 'pending'}
                      <button onclick={() => removeAgentItem(item.id)}
                        class="text-[9px] mt-1 px-2 py-0.5 rounded border"
                        style="color:var(--text-muted);border-color:var(--border)">
                        Remove
                      </button>
                    {/if}
                  </div>
                </div>
              </div>
            {/each}
          {/if}
        </div>
      {:else if activeTab === 'stats'}
        <!-- Stats Dashboard -->
        <div class="flex-1 overflow-y-auto p-3 space-y-3">
          {#if !statsData}
            <div class="flex items-center justify-center py-8 text-xs" style="color:var(--text-muted)">Loading stats...</div>
          {:else}
            <!-- Summary cards -->
            <div class="grid grid-cols-2 gap-2">
              <div class="p-2 rounded border text-center" style="background:var(--bg-tertiary);border-color:var(--border)">
                <div class="text-lg font-black" style="color:var(--accent)">{formatStatsNumber(statsData.total || 0)}</div>
                <div class="text-[9px]" style="color:var(--text-muted)">Total Videos</div>
              </div>
              <div class="p-2 rounded border text-center" style="background:var(--bg-tertiary);border-color:var(--border)">
                <div class="text-lg font-black" style="color:var(--accent)">{statsData.summary_rate || 0}%</div>
                <div class="text-[9px]" style="color:var(--text-muted)">Summary Rate</div>
              </div>
              <div class="p-2 rounded border text-center" style="background:var(--bg-tertiary);border-color:var(--border)">
                <div class="text-lg font-black" style="color:var(--accent)">{statsData.channels || 0}</div>
                <div class="text-[9px]" style="color:var(--text-muted)">Channels</div>
              </div>
              <div class="p-2 rounded border text-center" style="background:var(--bg-tertiary);border-color:var(--border)">
                <div class="text-lg font-black" style="color:var(--accent)">{statsData.last_7_days || 0}</div>
                <div class="text-[9px]" style="color:var(--text-muted)">Last 7 Days</div>
              </div>
            </div>
            
            <!-- Status breakdown -->
            <div class="p-2 rounded border" style="border-color:var(--border)">
              <div class="text-[10px] font-bold mb-2" style="color:var(--text-muted)">STATUS BREAKDOWN</div>
              {#each Object.entries(statsData.status_breakdown || {}) as [status, count]}
                <div class="flex justify-between items-center py-1">
                  <span class="text-[11px]" style="color:var(--text-primary)">{status}</span>
                  <span class="text-[11px] font-bold" style="color:var(--accent)">{count}</span>
                </div>
              {/each}
            </div>
            
            <!-- Top channels -->
            <div class="p-2 rounded border" style="border-color:var(--border)">
              <div class="text-[10px] font-bold mb-2" style="color:var(--text-muted)">TOP CHANNELS</div>
              {#each (statsData.top_channels || []).slice(0, 8) as ch}
                <div class="flex justify-between items-center py-1">
                  <span class="text-[10px] truncate flex-1 mr-2" style="color:var(--text-primary)">{ch.name}</span>
                  <span class="text-[10px]" style="color:var(--text-muted)">{ch.count}</span>
                </div>
              {/each}
            </div>
          {/if}
        </div>
      {:else}
        <!-- Channel filter bar -->
        {#if (allChannels.length || channels.length) > 1}
          <div class="px-2 py-1.5 space-y-1" style="border-bottom:1px solid var(--border)">
            <select bind:value={channelFilter}
              class="w-full text-[11px] p-1.5 rounded border bg-transparent"
              style="color:var(--text-primary);border-color:var(--border);background:var(--bg-tertiary)">
              <option value="">All Channels</option>
              {#if catList.length}
                <option disabled>── Categories ──</option>
                {#each catList as cat}
                  <option value={'📁 ' + cat}>📁 {cat}</option>
                {/each}
                <option disabled>── Channels ──</option>
              {/if}
              {#each allChannels.length ? allChannels.map(c => c.channel_name) : channels as ch}
                <option value={ch}>{ch}</option>
              {/each}
            </select>
            <button onclick={() => editingCategories = !editingCategories}
              class="w-full text-[10px] py-1 rounded border transition text-center"
              style="color:var(--text-muted);border-color:var(--border)">
              {editingCategories ? 'Done' : '✎ Edit Categories'}
            </button>
            <button onclick={async () => {
              const res = await fetch('/auto_categories.json');
              if (res.ok) {
                const data = await res.json();
                for (const [k, v] of Object.entries(data)) categories[k] = v;
                localStorage.setItem('t-yt-categories', JSON.stringify(categories));
                loadChannels();
              }
            }}
              class="w-full text-[10px] py-1 rounded border transition text-center"
              style="color:var(--text-muted);border-color:var(--border)">
              ↻ Reset to Auto
            </button>
          </div>
        {/if}
        {#if editingCategories}
          <div class="overflow-y-auto max-h-48 p-2 space-y-1" style="border-bottom:1px solid var(--border);background:var(--bg-tertiary)">
            <div class="text-[10px] pb-1" style="color:var(--text-muted)">Type category name next to each channel:</div>
            <input class="w-full text-[11px] p-1 rounded border mb-1" style="background:var(--bg-primary);color:var(--text-primary);border-color:var(--border)"
              placeholder="New category name..." id="new-cat-input"
              onkeydown={e => { if (e.key === 'Enter') { const v = e.target.value.trim(); if (v) { categories[v] = []; localStorage.setItem('t-yt-categories', JSON.stringify(categories)); e.target.value = ''; } } }}>
            {#each allChannels as ch}
              <div class="flex items-center space-x-2 text-[11px]">
                <span class="flex-1 truncate" style="color:var(--text-primary)">{ch.channel_name}</span>
                <input class="w-20 text-[10px] p-0.5 rounded border text-center"
                  style="background:var(--bg-primary);color:var(--text-primary);border-color:var(--border)"
                  placeholder="cat"
                  value={categories[ch.channel_name] || ''}
                  oninput={e => {
                    categories[ch.channel_name] = e.target.value;
                    localStorage.setItem('t-yt-categories', JSON.stringify(categories));
                  }}>
              </div>
            {/each}
          </div>
        {/if}
        <!-- Video list -->
        <div onscroll={handleSidebarScroll} class="flex-1 overflow-y-auto divide-y" style="border-color:var(--border)">
          {#each filteredVideos as video (video.video_id)}
            {@const i = filteredVideos.indexOf(video)}
            <button onclick={() => { selectedVideo = video; }}
              data-video-index={i}
              class="w-full text-left p-2.5 transition flex items-start space-x-2 outline-none focus:outline-none"
              style="border-left:2px solid {selectedVideo?.video_id === video.video_id ? 'var(--accent)' : 'transparent'};
                     background:{selectedVideo?.video_id === video.video_id ? 'var(--bg-tertiary)' : 'transparent'}">
              <!-- Batch select checkbox -->
              <input type="checkbox" 
                     checked={selectedVideoIds.has(video.video_id)}
                     onclick={(e) => { e.stopPropagation(); toggleSelect(video.video_id); }}
                     class="w-3 h-3 mt-1 rounded flex-shrink-0"
                     style="accent-color:var(--accent)">
              <img src={video.thumbnail_url} alt="" class="w-16 h-10 rounded object-cover flex-shrink-0" style="background:var(--bg-tertiary)" />
              <div class="min-w-0 flex-1">
                <span class="block text-[10px] font-semibold truncate" style="color:var(--accent)">{video.channel_name}</span>
                <h3 class="text-[11px] font-bold line-clamp-2 leading-snug" style="color:var(--text-primary)">{video.title}</h3>
                <div class="flex items-center gap-2 mt-0.5">
                  <span class="text-[9px]" style="color:var(--text-muted)">{video.publish_date || ''}</span>
                  {#if video.duration}
                    <span class="text-[9px]" style="color:var(--text-muted)">{formatDuration(video.duration)}</span>
                  {/if}
                  {#if video.view_count && video.view_count > 0}
                    <span class="text-[9px]" style="color:var(--text-muted)">{formatViews(video.view_count)}</span>
                  {/if}
                  {#if noteIds.has(video.video_id)}
                    <span class="text-[9px]">📝</span>
                  {/if}
                </div>
              </div>
            </button>
          {/each}
                  {#if isMoreLoading}
            <div class="p-3 text-center text-[10px]" style="color:var(--text-muted)">Loading more...</div>
          {/if}
</div>
      {/if}
    </aside>

    <!-- Batch action bar -->
    {#if showBatchBar}
      <div class="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-40 px-4 py-2 rounded-full shadow-lg flex items-center space-x-3"
           style="background:var(--bg-secondary);border:1px solid var(--border)">
        <span class="text-xs font-bold" style="color:var(--accent)">{selectedVideoIds.size} selected</span>
        <button onclick={() => batchUpdate('read')} class="text-[10px] px-3 py-1 text-white font-bold rounded" style="background:var(--accent)">Read All</button>
        <button onclick={() => batchUpdate('skipped')} class="text-[10px] px-3 py-1 font-bold rounded border" style="color:var(--text-secondary);border-color:var(--border)">Skip All</button>
        <button onclick={clearSelection} class="text-[10px] px-3 py-1 font-bold rounded border" style="color:var(--text-muted);border-color:var(--border)">Clear</button>
      </div>
    {/if}

    <!-- Right: summary panel -->
    <main bind:this={summaryPanel} onscroll={handleSummaryScroll} id="summary-panel" class="flex-1 overflow-y-auto" style="background:var(--bg-primary);min-height:0">
      {#if activeTab === '🤖 Agent'}
        <div class="p-6 max-w-3xl mx-auto">
          <h2 class="text-sm font-black tracking-wider mb-4" style="color:var(--accent)">🤖 Agent Queue</h2>
          {#if agentItems.length === 0}
            <p class="text-xs opacity-50 italic">Queue empty. Watch a video, press <kbd class="px-1 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">n</kbd> to write a note, check "Send to Agent", and save.</p>
          {:else}
            <div class="space-y-3">
              {#each agentItems as item}
                <div class="p-4 rounded-xl border" style="background:var(--card-bg);border-color:var(--border)">
                  <div class="flex items-start space-x-3">
                    <div class="min-w-0 flex-1">
                      <h3 class="text-sm font-bold" style="color:var(--text-primary)">{item.title || 'Unknown'}</h3>
                      <p class="text-[10px] mt-0.5" style="color:var(--accent)">{item.channel_name}</p>
                      <div class="mt-3 p-2 rounded text-xs leading-relaxed" style="background:var(--bg-tertiary)">
                        <span class="font-bold text-[10px]" style="color:var(--text-muted)">PROMPT:</span>
                        <p class="mt-1 whitespace-pre-wrap" style="color:var(--text-primary)">{item.user_prompt}</p>
                      </div>
                      <div class="flex items-center space-x-2 mt-2">
                        <span class="text-[9px] px-1.5 py-0.5 rounded font-bold uppercase"
                          style="background:{item.status === 'done' ? '#10b981' : item.status === 'processing' ? '#f59e0b' : item.status === 'failed' ? '#ef4444' : 'var(--bg-tertiary)'};color:white">
                          {item.status}
                        </span>
                        <span class="text-[9px]" style="color:var(--text-muted)">{formatDate(item.created_at)}</span>
                        <a href={item.url} target="_blank" rel="noopener noreferrer"
                          class="text-[9px] px-2 py-0.5 rounded border" style="color:var(--accent);border-color:var(--accent)">▶ Watch</a>
                        {#if item.status === 'pending'}
                          <button onclick={() => removeAgentItem(item.id)}
                            class="text-[9px] px-2 py-0.5 rounded border" style="color:var(--text-muted);border-color:var(--border)">Remove</button>
                        {/if}
                      </div>
                    </div>
                  </div>
                </div>
              {/each}
            </div>
          {/if}
        </div>
      {:else if selectedVideo}
        <div class="p-6 max-w-3xl mx-auto space-y-4">
          <!-- Video header -->
          <div class="flex items-start space-x-4">
            <img src={selectedVideo.thumbnail_url} alt="" class="w-40 h-24 rounded-lg object-cover flex-shrink-0" style="background:var(--bg-tertiary)" />
            <div class="min-w-0 space-y-2">
              <span class="text-[10px] px-2 py-0.5 font-bold rounded-full cursor-pointer hover:opacity-80 transition" 
                style="color:var(--accent);background:var(--bg-tertiary)"
                onclick={() => { channelFilter = selectedVideo.channel_name; }}
                title="Filter by this channel">{selectedVideo.channel_name}</span>
              <h1 class="text-lg font-bold leading-snug" style="color:var(--text-primary)">{selectedVideo.title}</h1>
              <div class="text-[10px]" style="color:var(--text-muted)">{selectedVideo.publish_date || ''} 
                {#if selectedVideo.duration}
                  • {formatDuration(selectedVideo.duration)}
                {/if}
                {#if selectedVideo.view_count}
                  • {formatViews(selectedVideo.view_count)} views
                {/if}
              </div>
              <div class="flex space-x-2">
                <button onclick={() => updateStatus(selectedVideo.video_id, 'read')} class="text-[10px] px-2.5 py-1 text-white font-bold rounded transition" style="background:var(--accent)">Read (r)</button>
                <button onclick={() => updateStatus(selectedVideo.video_id, 'skipped')} class="text-[10px] px-2.5 py-1 font-bold rounded border transition" style="color:var(--text-secondary);border-color:var(--border)">Skip (s)</button>
                <button onclick={() => activeNote = activeNote === selectedVideo.video_id ? null : selectedVideo.video_id}
                  class="text-[10px] px-2.5 py-1 font-bold rounded border transition"
                  style="color:var(--text-muted);border-color:var(--border)">✎ Note (n)</button>
                <a href={selectedVideo.url} target="_blank" rel="noopener noreferrer" class="text-[10px] px-2.5 py-1 text-white font-bold rounded transition" style="background:var(--accent)">▶ Watch</a>
              </div>
            </div>
          </div>

          <!-- Note editor/viewer -->
          {#if activeNote === selectedVideo.video_id}
            <div class="space-y-1.5">
              <textarea id="note-textarea" rows="3" class="w-full text-xs p-2 rounded border resize-none"
                style="background:var(--bg-tertiary);color:var(--text-primary);border-color:var(--border)"
                placeholder="Write instructions for the agent here...
e.g. 'Add this gyroid lattice workflow as a Hermes skill'"
                value={notes[selectedVideo.video_id] || ''}
                oninput={e => notes[selectedVideo.video_id] = e.target.value}
                use:autoFocus
                onkeydown={e => { if (e.key === 'Escape') { saveNote(); } else if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); saveNote(); } }}
              ></textarea>
              <div class="flex items-center space-x-3">
                <label class="flex items-center space-x-1.5 text-[11px] cursor-pointer" style="color:var(--text-muted)">
                  <input type="checkbox" bind:checked={queueToAgent}
                    class="w-3 h-3 rounded" style="accent-color:var(--accent)">
                  <span>Send to Agent</span>
                </label>
                <div class="flex-1"></div>
                <button onclick={saveNote}
                  class="text-[10px] px-3 py-1 text-white font-bold rounded transition" style="background:var(--accent)">
                  {queueToAgent ? 'Save + 🤖 Queue' : 'Save'}
                </button>
              </div>
            </div>
          {:else if notes[selectedVideo.video_id]}
            <div class="p-2 rounded border text-xs leading-relaxed cursor-pointer"
              style="background:var(--bg-tertiary);color:var(--text-primary);border-color:var(--border)"
              onclick={() => activeNote = selectedVideo.video_id}
              title="Click to edit">{notes[selectedVideo.video_id]}</div>
          {/if}

          <!-- AI Digest -->
          <div class="rounded-xl border p-5 space-y-3 shadow-lg" style="background:var(--card-bg);border-color:var(--accent);border-left-width:3px">
            <div class="flex items-center justify-between pb-2" style="border-bottom:1px solid var(--border)">
              <h2 class="text-xs font-black tracking-widest uppercase" style="color:var(--accent)">AI Digest</h2>
              {#if !selectedVideo.summary || selectedVideo.summary.includes("Error")}
                <button onclick={() => generateSummary(selectedVideo.video_id)}
                  class="text-[11px] px-3 py-1 font-semibold rounded border transition" style="color:var(--accent);border-color:var(--accent)">Generate</button>
              {/if}
            </div>
            <div class="prose max-w-none text-sm leading-relaxed" style="color:var(--text-primary)">
              {@html formatMarkdown(selectedVideo.summary)}
            </div>
          </div>

          <!-- Transcript Accordion -->
          {#if selectedVideo.transcript && selectedVideo.transcript !== "No transcript available"}
            <div class="rounded-xl border p-5 space-y-3 shadow-lg" style="background:var(--card-bg);border-color:var(--border)">
              <div class="flex items-center justify-between cursor-pointer select-none" onclick={() => showTranscript = !showTranscript}>
                <h2 class="text-xs font-black tracking-widest uppercase" style="color:var(--text-muted)">Video Transcript</h2>
                <span class="text-xs transition-transform duration-200" style="transform: rotate({showTranscript ? '90deg' : '0deg'})">▶</span>
              </div>
              {#if showTranscript}
                <div class="prose max-w-none text-xs leading-relaxed overflow-y-auto max-h-96 whitespace-pre-wrap pt-2 border-t" style="color:var(--text-secondary);border-color:var(--border)">
                  {selectedVideo.transcript}
                </div>
              {/if}
            </div>
          {/if}
        </div>
      {:else}
        <div class="flex-1 flex items-center justify-center" style="color:var(--text-muted)">
          <p>Select a video to read its summary</p>
        </div>
      {/if}
    </main>
  </div>

  {#if showHelp}
    <div class="fixed inset-0 z-50 flex items-center justify-center" style="background:rgba(0,0,0,0.6)" onclick={() => showHelp = false}>
      <div class="rounded-xl p-6 max-w-xs w-full space-y-3 shadow-2xl" style="background:var(--card-bg);border:1px solid var(--border)" onclick={e => e.stopPropagation()}>
        <h3 class="text-sm font-bold" style="color:var(--accent)">Keyboard Shortcuts</h3>
        <div class="space-y-1.5 text-xs" style="color:var(--text-secondary)">
          <div class="flex justify-between"><span>Navigate</span><span><kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">j</kbd> <kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">↓</kbd></span></div>
          <div class="flex justify-between"><span>Navigate up</span><span><kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">k</kbd> <kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">↑</kbd></span></div>
          <div class="flex justify-between"><span>Mark Read</span><span><kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">r</kbd> / <kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">Enter</kbd></span></div>
          <div class="flex justify-between"><span>Skip</span><span><kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">s</kbd></span></div>
          <div class="flex justify-between"><span>Note</span><span><kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">n</kbd></span></div>
          <div class="flex justify-between"><span>Save note</span><span><kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">Enter</kbd> / <kbd class="px-1.5 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">Esc</kbd></span></div>
        </div>
        <p class="text-[10px] text-center pt-1" style="color:var(--text-muted)">Press <kbd class="px-1 py-0.5 rounded text-[10px]" style="background:var(--bg-tertiary)">?</kbd> to toggle</p>
      </div>
    </div>
  {/if}
</div>
