/**
 * RE::SPARKS Extension - Background Service Worker v3.0
 * ======================================================
 * Manifest V3 compliant service worker.
 * 
 * HYBRID LOCAL-CLOUD ARCHITECTURE:
 * - Local AI filtering (Xenova/paraphrase-multilingual-MiniLM-L12-v2)
 * - Only sends to Cloud if local filter finds matches
 * - ~90% reduction in API costs
 * 
 * PRODUCTION HARDENING:
 * - Debounce pattern (3s delay before analysis)
 * - Per-tab analysis tracking (no duplicate requests)
 * - Token bridge from main app
 * - Proper error handling
 * 
 * Responsibilities:
 * - Sync sparks_projects from backend
 * - LOCAL: Run multilingual embedding model for pre-filtering
 * - CLOUD: Send only "potentially relevant" pages to Vertex + Mistral
 * - Update badge with match count
 * - Handle popup communication
 */

// =============================================================================
// CONFIGURATION
// =============================================================================

const CONFIG = {
  API_BASE: 'https://redact-api-vnyj3f4eaq-ew.a.run.app',

  APP_URL: 'https://sparks.redact-app.com',
  AUTH_URL: 'https://sparks.redact-app.com?auth=extension',

  // Storage keys
  STORAGE: {
    TOKEN: 'spark_auth_token',
    USER: 'spark_user_info',
    SPARKS: 'spark_projects_cache',
    LAST_SYNC: 'spark_last_sync',
    CATCHER_VERSION: 'catcher_version',  // v1 or v2
  },

  // Sync interval (5 minutes)
  SYNC_INTERVAL_MS: 5 * 60 * 1000,

  // RATE LIMITING: Debounce delay before analysis (3 seconds)
  ANALYSIS_DELAY_MS: 3000,

  // LOCAL AI SETTINGS
  LOCAL_AI: {
    ENABLED: true,  // ENABLED - Using bundled transformers.js with multilingual-e5-small
    THRESHOLD: 0.40,  // Minimum local score to send to cloud
    OFFSCREEN_URL: 'offscreen.html',
    MODEL_NAME: 'Xenova/multilingual-e5-small',  // 100 languages, ~129MB quantized
  },

  // Analysis thresholds (for cloud)
  RETRIEVAL_THRESHOLD: 0.5,
  ATTENTION_THRESHOLD: 0.7,

  // Max content length (chars) to prevent huge payloads
  MAX_CONTENT_CHARS: 15000,

  // CATCHER V2 SETTINGS (Two-phase LLM architecture)
  CATCHER_V2: {
    ENABLED: false,  // Set via config endpoint
    ENDPOINTS: {
      ANALYZE: '/api/sparks/v2/analyze',
      PHASE1: '/api/sparks/v2/phase1',
      PHASE2: '/api/sparks/v2/phase2',
      CONFIG: '/admin/api/sparks-config/catcher-version',
    },
  },
};

// =============================================================================
// STATE
// =============================================================================

let state = {
  isAuthenticated: false,
  token: null,
  user: null,
  sparks: [],  // Active sparks_projects for matching
  lastSync: null,
  offscreenReady: false,  // Offscreen document status
  catcherVersion: 'v1',  // 'v1' (embedding) or 'v2' (two-phase LLM)
  catcherConfig: null,   // Full config from server (for MODE display)
};

// Promise that resolves when state is loaded (prevents race conditions)
let stateLoadedResolve;
const stateLoaded = new Promise(resolve => { stateLoadedResolve = resolve; });

// RATE LIMITING: Track pending analysis per tab
const pendingAnalysis = new Map();  // tabId -> timeoutId
const analyzedTabs = new Set();     // Tabs already analyzed in this session

// =============================================================================
// OFFSCREEN DOCUMENT SETUP (Local AI Worker)
// =============================================================================

/**
 * Create offscreen document for running local AI model.
 * Service workers can't run heavy WASM/AI directly.
 */
async function setupOffscreenDocument() {
  if (!CONFIG.LOCAL_AI.ENABLED) return;
  
  const offscreenUrl = chrome.runtime.getURL(CONFIG.LOCAL_AI.OFFSCREEN_URL);
  
  try {
    // Check if already exists
    const existingContexts = await chrome.runtime.getContexts({
      contextTypes: ['OFFSCREEN_DOCUMENT'],
      documentUrls: [offscreenUrl],
    });
    
    if (existingContexts.length > 0) {
      console.log('[SPARKS BG] Offscreen document already exists');
      state.offscreenReady = true;
      return;
    }
    
    // Create new offscreen document
    await chrome.offscreen.createDocument({
      url: CONFIG.LOCAL_AI.OFFSCREEN_URL,
      reasons: ['WORKERS'],  // For running ML model
      justification: 'Running local multilingual AI model for cost-efficient relevance filtering',
    });
    
    state.offscreenReady = true;
    console.log('[SPARKS BG] ✅ Offscreen document created for local AI');
    
  } catch (error) {
    console.error('[SPARKS BG] Failed to create offscreen document:', error);
    state.offscreenReady = false;
  }
}

/**
 * Send message to offscreen document.
 */
async function sendToOffscreen(action, data = {}) {
  if (!state.offscreenReady) {
    await setupOffscreenDocument();
  }
  
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage({ action, ...data }, (response) => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
      } else {
        resolve(response);
      }
    });
  });
}

// =============================================================================
// INITIALIZATION
// =============================================================================

// Initialize on install/update
chrome.runtime.onInstalled.addListener(async () => {
  console.log('[SPARKS BG] Extension installed/updated');
  await loadStateFromStorage();
  stateLoadedResolve(); // Signal state ready
  await setupOffscreenDocument();
  createContextMenu();
  if (state.isAuthenticated) {
    await syncSparks();
  }
});

// Initialize on startup
chrome.runtime.onStartup.addListener(async () => {
  console.log('[SPARKS BG] Browser started');
  await loadStateFromStorage();
  stateLoadedResolve(); // Signal state ready
  await setupOffscreenDocument();
  createContextMenu();
  if (state.isAuthenticated) {
    await syncSparks();
  }
});

// =============================================================================
// CONTEXT MENU
// =============================================================================

/**
 * Create context menu for quick Spark saving.
 */
function createContextMenu() {
  // Remove existing to avoid duplicates
  chrome.contextMenus.removeAll(() => {
    // Menu for selected text
    chrome.contextMenus.create({
      id: 'add-spark-selection',
      title: '⚡ Add to Spark',
      contexts: ['selection'],
    });

    // Menu for page (when no text selected)
    chrome.contextMenus.create({
      id: 'add-spark-page',
      title: '⚡ Add Page to Spark',
      contexts: ['page'],
    });

    console.log('[SPARKS BG] ✅ Context menu created');
  });
}

/**
 * Handle context menu click.
 */
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  console.log('[SPARKS BG] Context menu clicked:', info.menuItemId);

  if (!state.isAuthenticated) {
    // Not logged in - open login page
    chrome.tabs.create({ url: `${CONFIG.AUTH_URL}&ext_id=${chrome.runtime.id}` });
    return;
  }

  if (info.menuItemId === 'add-spark-selection' || info.menuItemId === 'add-spark-page') {
    const isSelection = info.menuItemId === 'add-spark-selection';
    const content = isSelection ? info.selectionText : tab.title;

    // Store pending selection for popup or app
    await chrome.storage.local.set({
      pending_selection: {
        text: content,
        url: tab.url,
        title: tab.title,
        source_type: isSelection ? 'snippet' : 'url',
        timestamp: Date.now(),
      },
    });

    console.log('[SPARKS BG] Stored pending selection:', content?.substring(0, 50));

    // Open Sparks app with selection data
    const encodedText = encodeURIComponent((content || '').substring(0, 500));
    const encodedUrl = encodeURIComponent(tab.url || '');
    const encodedTitle = encodeURIComponent(tab.title || '');

    chrome.tabs.create({
      url: `${CONFIG.APP_URL}/sparks?action=add&text=${encodedText}&url=${encodedUrl}&title=${encodedTitle}`
    });
  }
});

// Load state from storage
async function loadStateFromStorage() {
  try {
    const data = await chrome.storage.local.get([
      CONFIG.STORAGE.TOKEN,
      CONFIG.STORAGE.USER,
      CONFIG.STORAGE.SPARKS,
      CONFIG.STORAGE.LAST_SYNC,
      CONFIG.STORAGE.CATCHER_VERSION,
      'spark_projects_cache',  // Also check popup.js cache
    ]);

    // Load catcher version from storage (will be updated on first API call)
    state.catcherVersion = data[CONFIG.STORAGE.CATCHER_VERSION] || 'v1';

    state.token = data[CONFIG.STORAGE.TOKEN] || null;
    state.user = data[CONFIG.STORAGE.USER] || null;
    state.lastSync = data[CONFIG.STORAGE.LAST_SYNC] || null;
    state.isAuthenticated = !!state.token;

    // Use whichever cache has data (prefer spark_projects_cache as it's used by popup)
    const bgCache = data[CONFIG.STORAGE.SPARKS] || [];
    const popupCache = data['spark_projects_cache'] || [];

    // Use popup cache if it has data (more likely to be fresh from Drive)
    if (popupCache.length > 0) {
      state.sparks = popupCache;
      console.log(`[SPARKS BG] Loaded state from popup cache: auth=${state.isAuthenticated}, sparks=${state.sparks.length}`);
    } else if (bgCache.length > 0) {
      state.sparks = bgCache;
      console.log(`[SPARKS BG] Loaded state from bg cache: auth=${state.isAuthenticated}, sparks=${state.sparks.length}`);
    } else {
      state.sparks = [];
      console.log(`[SPARKS BG] Loaded state (no cached sparks): auth=${state.isAuthenticated}`);
    }

  } catch (error) {
    console.error('[SPARKS BG] Failed to load state:', error);
  }
}

// =============================================================================
// TAB LISTENERS (DEBOUNCE PATTERN)
// =============================================================================

/**
 * Listen for tab updates to trigger analysis with debounce.
 * This prevents flooding the API when user rapidly navigates.
 */
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  // Only trigger on complete load, for real URLs
  if (changeInfo.status !== 'complete') return;
  if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://')) return;
  // Allow analysis if we have sparks (from Drive cache) even without API auth
  if (!state.isAuthenticated && state.sparks.length === 0) return;
  
  // Cancel any pending analysis for this tab
  if (pendingAnalysis.has(tabId)) {
    clearTimeout(pendingAnalysis.get(tabId));
    pendingAnalysis.delete(tabId);
  }
  
  // Clear "already analyzed" flag for this tab (new page load)
  analyzedTabs.delete(tabId);
  
  // Schedule new analysis with debounce delay
  const timeoutId = setTimeout(async () => {
    pendingAnalysis.delete(tabId);
    
    // Double-check tab is still active
    try {
      const currentTab = await chrome.tabs.get(tabId);
      if (currentTab && currentTab.active) {
        // Signal content script to scan
        chrome.tabs.sendMessage(tabId, { action: 'triggerAnalysis' }, (response) => {
          if (chrome.runtime.lastError) {
            // Content script not loaded (e.g., chrome:// page)
            console.debug('[SPARKS BG] Content script not available:', chrome.runtime.lastError.message);
          }
        });
      }
    } catch (e) {
      // Tab was closed
      console.debug('[SPARKS BG] Tab no longer exists:', tabId);
    }
  }, CONFIG.ANALYSIS_DELAY_MS);
  
  pendingAnalysis.set(tabId, timeoutId);
});

// Clean up when tab is closed
chrome.tabs.onRemoved.addListener((tabId) => {
  if (pendingAnalysis.has(tabId)) {
    clearTimeout(pendingAnalysis.get(tabId));
    pendingAnalysis.delete(tabId);
  }
  analyzedTabs.delete(tabId);
});

// =============================================================================
// API CALLS
// =============================================================================

/**
 * Make authenticated API call to backend.
 */
async function apiCall(endpoint, options = {}) {
  const url = `${CONFIG.API_BASE}${endpoint}`;

  const headers = {
    'Content-Type': 'application/json',
    ...options.headers,
  };

  if (state.token) {
    headers['Authorization'] = `Bearer ${state.token}`;
  }

  const response = await fetch(url, {
    ...options,
    headers,
  });

  if (!response.ok) {
    // Handle 401 - token expired
    if (response.status === 401) {
      state.isAuthenticated = false;
      state.token = null;
      state.user = null;
      // Clear all auth-related storage
      await chrome.storage.local.remove([
        CONFIG.STORAGE.TOKEN,
        CONFIG.STORAGE.USER,
        'sparks_drive_token',
        'sparks_drive_token_time'
      ]);
      throw new Error('Session expired - please login again');
    }

    // Try to get error detail from response
    let errorDetail = `API error: ${response.status}`;
    try {
      const errorBody = await response.json();
      if (errorBody.detail) {
        errorDetail = errorBody.detail;
      } else if (errorBody.message) {
        errorDetail = errorBody.message;
      }
    } catch (e) {
      // Couldn't parse JSON, use default error
    }

    throw new Error(errorDetail);
  }

  return response.json();
}

// =============================================================================
// SYNC SPARKS
// =============================================================================

/**
 * Sync active sparks_projects from backend or Drive.
 * Called on startup and periodically.
 *
 * PRIORITY:
 * 1. If Drive token exists → Load from Google Drive
 * 2. Fallback to API if Drive fails or no Drive token
 */
async function syncSparks() {
  if (!state.isAuthenticated) {
    console.log('[SPARKS BG] Not authenticated, skipping sync');
    return;
  }

  // Check if Drive token exists
  const { sparks_drive_token: driveToken } = await chrome.storage.local.get('sparks_drive_token');

  if (driveToken) {
    // === DRIVE MODE: Try to load from Drive first ===
    console.log('[SPARKS BG] Drive mode: Loading sparks from Drive...');
    try {
      const sparksFromDrive = await loadSparksFromDrive(driveToken);
      if (sparksFromDrive && sparksFromDrive.length > 0) {
        state.sparks = sparksFromDrive;
        state.lastSync = Date.now();
        // Save to BOTH caches for consistency with popup.js
        await chrome.storage.local.set({
          [CONFIG.STORAGE.SPARKS]: state.sparks,
          [CONFIG.STORAGE.LAST_SYNC]: state.lastSync,
          'spark_projects_cache': state.sparks,  // Also update popup cache
        });
        console.log(`[SPARKS BG] ✅ Synced ${state.sparks.length} sparks from Drive`);
        return;
      } else {
        console.log('[SPARKS BG] No sparks in Drive, falling back to API...');
      }
    } catch (error) {
      console.error('[SPARKS BG] Drive sync failed, falling back to API:', error);
    }
  }

  // === API MODE: Fallback or no Drive token ===
  try {
    console.log('[SPARKS BG] Syncing sparks from API...');

    // Get active projects (sparks_projects)
    const response = await apiCall('/api/sparks/projects');
    const projects = response.projects || response || [];

    // Only update cache if we got valid data
    if (Array.isArray(projects)) {
      state.sparks = projects;
      state.lastSync = Date.now();

      // Save to BOTH caches for consistency with popup.js
      await chrome.storage.local.set({
        [CONFIG.STORAGE.SPARKS]: state.sparks,
        [CONFIG.STORAGE.LAST_SYNC]: state.lastSync,
        'spark_projects_cache': state.sparks,  // Also update popup cache
      });

      console.log(`[SPARKS BG] ✅ Synced ${state.sparks.length} sparks from API`);
    }

  } catch (error) {
    console.error('[SPARKS BG] API sync failed:', error);
  }
}

/**
 * Load sparks_projects from Google Drive.
 * Uses same data structure as Sparks App and popup.js:
 * - RE_DACT folder
 * - sparks_index.json (index of all projects)
 * - sparks_project_{projectId}.json (full project data)
 */
async function loadSparksFromDrive(driveToken) {
  // 1. Find RE_DACT folder
  const folderResponse = await fetch(
    `https://www.googleapis.com/drive/v3/files?q=name='RE_DACT' and mimeType='application/vnd.google-apps.folder' and trashed=false&fields=files(id,name)`,
    { headers: { 'Authorization': `Bearer ${driveToken}` } }
  );

  if (!folderResponse.ok) {
    throw new Error(`Drive API error: ${folderResponse.status}`);
  }

  const folderData = await folderResponse.json();
  const folderId = folderData.files?.[0]?.id;

  if (!folderId) {
    console.log('[SPARKS BG] RE_DACT folder not found on Drive');
    return [];
  }

  console.log(`[SPARKS BG] Found RE_DACT folder: ${folderId}`);

  // 2. Find sparks_index.json
  const indexResponse = await fetch(
    `https://www.googleapis.com/drive/v3/files?q=name='sparks_index.json' and '${folderId}' in parents and trashed=false&fields=files(id,name)`,
    { headers: { 'Authorization': `Bearer ${driveToken}` } }
  );

  if (!indexResponse.ok) {
    throw new Error(`Drive index search error: ${indexResponse.status}`);
  }

  const indexData = await indexResponse.json();
  const indexFileId = indexData.files?.[0]?.id;

  if (!indexFileId) {
    console.log('[SPARKS BG] sparks_index.json not found - Drive is empty');
    return [];
  }

  // 3. Download sparks_index.json
  const contentResponse = await fetch(
    `https://www.googleapis.com/drive/v3/files/${indexFileId}?alt=media`,
    { headers: { 'Authorization': `Bearer ${driveToken}` } }
  );

  if (!contentResponse.ok) {
    throw new Error(`Drive download error: ${contentResponse.status}`);
  }

  const indexContent = await contentResponse.json();
  // Index may have 'topics' (old) or 'projects' (new) key
  const projectsList = indexContent.projects || indexContent.topics || [];
  console.log(`[SPARKS BG] Index loaded with ${projectsList.length} projects`);

  if (!projectsList || projectsList.length === 0) {
    return [];
  }

  // 4. Load full project data from sparks_project_*.json files
  const projects = [];

  for (const projectMeta of projectsList) {
    try {
      const projectId = projectMeta.projectId || projectMeta.topicId;
      const projectFileName = `sparks_project_${projectId}.json`;

      // Find project file
      const projectResponse = await fetch(
        `https://www.googleapis.com/drive/v3/files?q=name='${projectFileName}' and '${folderId}' in parents and trashed=false&fields=files(id,name)`,
        { headers: { 'Authorization': `Bearer ${driveToken}` } }
      );

      if (!projectResponse.ok) continue;

      const projectData = await projectResponse.json();
      const projectFileId = projectData.files?.[0]?.id;

      if (!projectFileId) continue;

      // Download project file
      const projectContentResponse = await fetch(
        `https://www.googleapis.com/drive/v3/files/${projectFileId}?alt=media`,
        { headers: { 'Authorization': `Bearer ${driveToken}` } }
      );

      if (!projectContentResponse.ok) continue;

      const projectContent = await projectContentResponse.json();

      // Handle both old 'topic' key and new 'project' key
      const projectInfo = projectContent.project || projectContent.topic;
      if (projectInfo) {
        const pid = projectInfo.project_id || projectInfo.topic_id || projectId;
        projects.push({
          project_id: pid,
          title: projectInfo.title || projectMeta.title,
          description: projectInfo.description || '',
          color: projectInfo.color || '#FF9A00',
          icon: projectInfo.icon || '⚡',
          is_active: true,
        });
      }
    } catch (e) {
      console.warn(`[SPARKS BG] Failed to load project ${projectId}:`, e);
    }
  }

  return projects;
}

// Schedule periodic sync
setInterval(async () => {
  if (state.isAuthenticated) {
    await syncSparks();
  }
}, CONFIG.SYNC_INTERVAL_MS);

// =============================================================================
// CONTENT TRUNCATION (Prevent huge payloads)
// =============================================================================

/**
 * Truncate chunks to safe size.
 * Business logic: Sparks needs context, not entire books.
 */
function truncateChunks(chunks) {
  if (!chunks || !chunks.length) return [];
  
  // Calculate total chars
  let totalChars = 0;
  const truncatedChunks = [];
  
  for (const chunk of chunks) {
    if (totalChars + chunk.length > CONFIG.MAX_CONTENT_CHARS) {
      // Add partial if there's room
      const remaining = CONFIG.MAX_CONTENT_CHARS - totalChars;
      if (remaining > 100) {
        truncatedChunks.push(chunk.substring(0, remaining));
      }
      break;
    }
    truncatedChunks.push(chunk);
    totalChars += chunk.length;
  }
  
  return truncatedChunks;
}

// =============================================================================
// LOCAL AI FILTERING (Pre-Cloud Sieve)
// =============================================================================

/**
 * Run local multilingual model to pre-filter page content.
 * Returns matches ONLY if local AI thinks page is relevant.
 * 
 * This saves ~90% of Cloud API costs by filtering irrelevant pages locally.
 * 
 * @param {string} pageText - Page content
 * @returns {Array} - Local matches (empty = skip cloud)
 */
async function runLocalFilter(pageText) {
  if (!CONFIG.LOCAL_AI.ENABLED) {
    console.log('[SPARKS BG] Local AI disabled, skipping local filter');
    return null;  // null = skip local, proceed to cloud
  }
  
  if (!state.sparks || state.sparks.length === 0) {
    return [];
  }
  
  try {
    console.log('[SPARKS BG] 🧠 Running local AI filter...');
    
    const response = await sendToOffscreen('ANALYZE_TEXT', {
      text: pageText,
      sparks: state.sparks,
    });
    
    if (!response || !response.success) {
      console.warn('[SPARKS BG] Local AI returned error:', response?.error);
      return null;  // On error, fallback to cloud
    }
    
    const matches = response.result || [];
    
    if (matches.length > 0) {
      console.log(`[SPARKS BG] 🔥 Local AI found ${matches.length} potential matches:`, 
        matches.map(m => `${m.title} (${m.score})`).join(', '));
    } else {
      console.log('[SPARKS BG] ❄️ Local AI: Page is irrelevant. Skipping cloud API.');
    }
    
    return matches;
    
  } catch (error) {
    console.error('[SPARKS BG] Local AI error:', error);
    return null;  // On error, fallback to cloud
  }
}

// =============================================================================
// PAGE ANALYSIS (Local Filter → Phase 1 → Phase 2)
// =============================================================================

/**
 * Phase 1: Quick retrieval using Vertex AI embeddings.
 * 
 * HYBRID FLOW:
 * 1. Run LOCAL AI filter first (free, ~40ms)
 * 2. If local says "irrelevant" → return empty, save cloud costs
 * 3. If local says "maybe relevant" → send to cloud for Vertex AI
 * 
 * Returns candidates that MAY be relevant.
 */
async function runPhase1Analysis(pageData) {
  // Skip if no sparks loaded (from API or Drive cache)
  if (!state.sparks.length) {
    console.log('[SPARKS BG] Phase 1 skipped: no sparks');
    return { candidates: [], count: 0, reason: 'no_sparks' };
  }
  
  try {
    console.log('[SPARKS BG] Phase 1: Analyzing', pageData.url);
    
    // Truncate chunks to prevent huge payloads
    const safeChunks = truncateChunks(pageData.chunks);
    const fullText = safeChunks.join('\n');
    
    // =========================================================
    // STEP 1: LOCAL AI FILTER (FREE - runs in browser)
    // =========================================================
    const localMatches = await runLocalFilter(fullText);
    
    // If local filter returns empty array, skip cloud entirely
    if (localMatches !== null && localMatches.length === 0) {
      console.log('[SPARKS BG] ❄️ Local AI: No matches. Skipping cloud API (saved $$$)');
      return { candidates: [], count: 0, localFiltered: true };
    }
    
    // If local filter found matches, log which projects
    if (localMatches && localMatches.length > 0) {
      console.log(`[SPARKS BG] 🔥 Local AI found ${localMatches.length} potential matches. Proceeding to cloud...`);
    }

    // =========================================================
    // STEP 2: CLOUD API (Only if local filter passed)
    // =========================================================
    // Send projects directly from cache to bypass Firestore lookup (faster, more reliable)
    const projectsToSend = localMatches && localMatches.length > 0
      ? state.sparks.filter(s => localMatches.some(m => m.id === s.project_id))
      : state.sparks;

    console.log(`[SPARKS BG] Sending ${projectsToSend.length} projects to Phase 1 API`);

    const response = await apiCall('/api/sparks/phase1', {
      method: 'POST',
      body: JSON.stringify({
        url: pageData.url,
        title: pageData.title,
        chunks: safeChunks,
        // Send full projects data (bypass Firestore lookup)
        projects: projectsToSend.map(s => ({
          project_id: s.project_id,
          title: s.title,
          description: s.description || '',
        })),
      }),
    });
    
    const candidates = response.candidates || [];
    console.log(`[SPARKS BG] Phase 1 complete: ${candidates.length} candidates from cloud`);
    
    return { candidates, count: candidates.length };
    
  } catch (error) {
    console.error('[SPARKS BG] Phase 1 failed:', error);
    return { candidates: [], count: 0 };
  }
}

/**
 * Phase 2: LLM verification of candidates (uses provider from Flow admin config).
 * Called when user clicks the spark icon.
 */
async function runPhase2Analysis(candidates, pageInfo) {
  // Skip only if no candidates - auth not required (sparks from Drive cache work)
  if (!candidates.length) {
    return { matches: [], count: 0 };
  }

  try {
    // Get provider info from cached config
    const p2 = state.catcherConfig?.v1_config?.phase2 || {};
    const providerName = p2.provider === 'vertex' ? 'Gemini' : 'Mistral';
    const modelName = p2.model || 'unknown';
    console.log(`[SPARKS BG] Phase 2: ${providerName} (${modelName}) verification for`, candidates.length, 'candidates');
    
    // Call backend - Phase 2 (Mistral verification)
    const response = await apiCall('/api/sparks/phase2', {
      method: 'POST',
      body: JSON.stringify({
        candidates,
        url: pageInfo.url,
        title: pageInfo.title,
        user_id: state.user?.user_id || state.user?.email || null,
      }),
    });
    
    const matches = response.matches || [];
    console.log(`[SPARKS BG] Phase 2 complete: ${matches.length} verified matches`);
    
    return { matches, count: matches.length };
    
  } catch (error) {
    console.error('[SPARKS BG] Phase 2 failed:', error);
    return { matches: [], count: 0 };
  }
}

/**
 * Legacy: Full analysis (both phases) - for backward compatibility.
 */
async function analyzePage(pageData) {
  // Skip only if no sparks - auth not required (sparks from Drive cache work)
  if (!state.sparks.length) {
    return { matches: [], count: 0 };
  }
  
  try {
    console.log('[SPARKS BG] Analyzing page:', pageData.url);
    
    // Truncate chunks
    const safeChunks = truncateChunks(pageData.chunks);
    
    // Send to backend for analysis
    const response = await apiCall('/api/sparks/analyze', {
      method: 'POST',
      body: JSON.stringify({
        url: pageData.url,
        title: pageData.title,
        chunks: safeChunks,
        spark_ids: state.sparks.map(s => s.project_id),
      }),
    });
    
    const matches = response.matches || [];
    
    console.log(`[SPARKS BG] Found ${matches.length} matches`);
    
    return {
      matches,
      count: matches.length,
    };
    
  } catch (error) {
    console.error('[SPARKS BG] Analysis failed:', error);
    return { matches: [], count: 0 };
  }
}

// =============================================================================
// CATCHER V2: TWO-PHASE LLM ANALYSIS
// =============================================================================

/**
 * Check which catcher version is enabled on the server.
 * Also fetches full config for displaying in MODE log.
 */
async function checkCatcherVersion() {
  const url = `${CONFIG.API_BASE}${CONFIG.CATCHER_V2.ENDPOINTS.CONFIG}`;
  console.log('[SPARKS BG] Checking catcher version from:', url);

  try {
    const response = await fetch(url);
    console.log('[SPARKS BG] Catcher version response status:', response.status);

    if (response.ok) {
      const data = await response.json();
      console.log('[SPARKS BG] Catcher version response:', JSON.stringify(data));

      if (data.status === 'success' && data.data) {
        state.catcherVersion = data.data.version || 'v1';
        state.catcherConfig = data.data;  // Store full config
        CONFIG.CATCHER_V2.ENABLED = data.data.v2_enabled || false;
        await chrome.storage.local.set({ [CONFIG.STORAGE.CATCHER_VERSION]: state.catcherVersion });
        console.log(`[SPARKS BG] ✅ Catcher version set to: ${state.catcherVersion}`);
        return state.catcherVersion;
      }
    }
  } catch (e) {
    console.warn('[SPARKS BG] ❌ Could not check catcher version:', e);
  }

  console.log('[SPARKS BG] Using default catcher version: v1');
  return 'v1';
}

/**
 * Log current MODE with full config details.
 */
function logModeConfig(mode) {
  const cfg = state.catcherConfig || {};

  if (mode === 'v2') {
    const v2 = cfg.v2_config || {};
    console.log('%c╔══════════════════════════════════════════════════════════════════╗', 'color: #FFD700; font-weight: bold');
    console.log('%c║  🧠 MODE 2: REASONING LLM (Qwen3 local → Cloud API)              ║', 'color: #FFD700; font-weight: bold');
    console.log('%c╠══════════════════════════════════════════════════════════════════╣', 'color: #FFD700');
    console.log(`%c║  Phase 1: ${v2.phase1_model || 'Qwen2.5-0.5B'} (${v2.phase1_provider || 'transformers.js'})`, 'color: #FFD700');
    console.log(`%c║  Phase 2: ${v2.phase2_model || 'mistral-small'} (${v2.phase2_provider || 'mistral'})`, 'color: #FFD700');
    console.log(`%c║  Filter threshold: ${v2.filter_threshold || 0.6}`, 'color: #FFD700');
    console.log('%c╚══════════════════════════════════════════════════════════════════╝', 'color: #FFD700; font-weight: bold');
  } else {
    const v1 = cfg.v1_config || {};
    const p1 = v1.phase1 || {};
    const p2 = v1.phase2 || {};
    console.log('%c╔══════════════════════════════════════════════════════════════════╗', 'color: #4CAF50; font-weight: bold');
    console.log('%c║  ⚡ MODE 1: EMBEDDING (Xenova e5 local → Vertex cloud)           ║', 'color: #4CAF50; font-weight: bold');
    console.log('%c╠══════════════════════════════════════════════════════════════════╣', 'color: #4CAF50');
    console.log(`%c║  Phase 1: ${p1.model || 'text-embedding-004'} | chunks: ${p1.max_chunks || 20} (${p1.chunk_type || 'sentences'})`, 'color: #4CAF50');
    console.log(`%c║  Phase 2: ${p2.model || 'mistral-small'} (${p2.provider || 'mistral'}) | temp: ${p2.temperature || 0.1}`, 'color: #4CAF50');
    console.log(`%c║  Thresholds: similarity=${p1.similarity_threshold || 0.45}, attention=${p2.attention_threshold || 0.7}`, 'color: #4CAF50');
    console.log(`%c║  Reasoning: ${p2.reasoning_mode ? 'ON' : 'OFF'}`, 'color: #4CAF50');
    console.log('%c╚══════════════════════════════════════════════════════════════════╝', 'color: #4CAF50; font-weight: bold');
  }
}

/**
 * Run V2 analysis (two-phase LLM architecture).
 *
 * Phase 1: LOCAL LLM in browser (auto-downloads via transformers.js) - FREE
 * Phase 2: Gemini Pro for deep analysis with precise quotes
 */
async function runV2Analysis(pageData, projectInfo) {
  try {
    console.log('[SPARKS BG] V2: Starting two-phase LLM analysis...');
    const startTime = Date.now();

    // ========================================
    // PHASE 1: LOCAL LLM FILTER (in browser)
    // Model auto-downloads on first use (~500MB)
    // ========================================
    console.log('[SPARKS BG] V2 Phase 1: Running local LLM filter...');

    const pageText = pageData.chunks.join('\n\n');
    let phase1Decision = 'UNCERTAIN';
    let phase1Reason = '';

    try {
      const phase1Response = await sendToOffscreen('V2_FILTER', {
        text: pageText.substring(0, 2000),  // Truncate for speed
        projectTitle: projectInfo.title,
        projectDescription: projectInfo.description || '',
        pageUrl: pageData.url,  // For URL-based caching
      });

      if (phase1Response?.success && phase1Response.result) {
        phase1Decision = phase1Response.result.decision;
        phase1Reason = phase1Response.result.reason;
        console.log(`[SPARKS BG] V2 Phase 1: ${phase1Decision} - ${phase1Reason}`);

        // If local LLM says NO → skip Phase 2 (save API costs)
        if (phase1Decision === 'NO') {
          return {
            phase1_decision: 'NOT_RELEVANT',
            phase1_reason: phase1Reason,
            phase1_time_ms: Date.now() - startTime,
            matches: [],
            count: 0,
            total_time_ms: Date.now() - startTime,
            v2: true,
          };
        }
      }
    } catch (phase1Error) {
      console.warn('[SPARKS BG] V2 Phase 1 error (continuing to Phase 2):', phase1Error);
      phase1Reason = 'Local filter error - proceeding to cloud';
    }

    // ========================================
    // PHASE 2: CLOUD API (Mistral/Gemini)
    // Only runs if Phase 1 says YES or UNCERTAIN
    // ========================================
    console.log('[SPARKS BG] V2 Phase 2: Calling cloud API...');

    // Call Phase 2 endpoint directly (skipping backend Phase 1 since we did it locally)
    const response = await apiCall(CONFIG.CATCHER_V2.ENDPOINTS.PHASE2, {
      method: 'POST',
      body: JSON.stringify({
        text: pageText,
        project_title: projectInfo.title,
        project_description: projectInfo.description || '',
        existing_sparks: [],
      }),
    });

    console.log('[SPARKS BG] V2 Phase 2 response:', response);

    // Transform v2 response to match v1 format for content script
    // Phase2 endpoint returns 'matches' directly, not 'phase2_matches'
    const matches = (response.matches || response.phase2_matches || []).map(m => ({
      spark_id: projectInfo.project_id,
      spark_title: projectInfo.title,
      quote: m.quote,
      start_char: m.start_char,
      end_char: m.end_char,
      relevance_score: m.relevance_score,
      reasoning: m.reasoning,
      category: m.category,
      verified: m.verified,
      chunk_index: findChunkIndex(pageData.chunks, m.quote),
    }));

    return {
      phase1_decision: phase1Decision,  // From local browser LLM
      phase1_reason: phase1Reason,       // From local browser LLM
      phase1_time_ms: Date.now() - startTime - (response.processing_time_ms || 0),
      phase2_assessment: response.overall_assessment || '',
      phase2_confidence: response.confidence || 0,
      phase2_time_ms: response.processing_time_ms || 0,
      matches,
      count: matches.length,
      total_time_ms: Date.now() - startTime,
      v2: true,
    };

  } catch (error) {
    console.error('[SPARKS BG] V2 analysis failed:', error);
    return { matches: [], count: 0, error: error.message, v2: true };
  }
}

/**
 * Find which chunk index contains the given quote.
 */
function findChunkIndex(chunks, quote) {
  if (!chunks || !quote) return 0;

  const normalizedQuote = quote.toLowerCase().replace(/\s+/g, ' ').trim();

  for (let i = 0; i < chunks.length; i++) {
    const normalizedChunk = chunks[i].toLowerCase().replace(/\s+/g, ' ');
    if (normalizedChunk.includes(normalizedQuote.substring(0, 50))) {
      return i;
    }
  }

  return 0;  // Default to first chunk
}

/**
 * Update extension badge with match count.
 */
async function updateBadge(tabId, count) {
  try {
    if (count > 0) {
      await chrome.action.setBadgeText({ tabId, text: count.toString() });
      await chrome.action.setBadgeBackgroundColor({ tabId, color: '#FF6D00' });
    } else {
      await chrome.action.setBadgeText({ tabId, text: '' });
    }
  } catch (e) {
    // Tab might be closed
    console.debug('[SPARKS BG] Could not update badge:', e.message);
  }
}

// =============================================================================
// MESSAGE HANDLERS
// =============================================================================

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  // Handle synchronous responses FIRST (before async IIFE)
  if (message.action === 'checkAuth') {
    console.log('[SPARKS BG] checkAuth (sync): isAuth:', state.isAuthenticated, 'sparks:', state.sparks?.length);
    sendResponse({
      isAuthenticated: state.isAuthenticated,
      user: state.user,
    });
    return false; // Synchronous response, no need to keep channel open
  }

  if (message.action === 'getSparks') {
    console.log('[SPARKS BG] getSparks (sync): sparks:', state.sparks?.length);
    sendResponse({ sparks: state.sparks });
    return false;
  }

  // syncSparks - needs async but handled outside IIFE for reliable sendResponse
  if (message.action === 'syncSparks') {
    (async () => {
      await stateLoaded;
      console.log('[SPARKS BG] syncSparks (sync-wrapper): stateLoaded, isAuth:', state.isAuthenticated, 'sparks:', state.sparks?.length);
      await syncSparks();
      console.log('[SPARKS BG] syncSparks (sync-wrapper): after sync, sparks:', state.sparks?.length);
      sendResponse({ sparks: state.sparks, count: state.sparks.length });
    })();
    return true; // Keep channel open for async response
  }

  // Handle async responses
  (async () => {
    try {
      switch (message.action) {

        // === AUTH ===
        case 'setToken':
          state.token = message.token;
          state.user = message.user || {};
          state.isAuthenticated = true;
          
          await chrome.storage.local.set({
            [CONFIG.STORAGE.TOKEN]: message.token,
            [CONFIG.STORAGE.USER]: message.user,
          });
          
          // Sync after login
          await syncSparks();
          
          sendResponse({ success: true });
          break;
        
        case 'logout':
          state.isAuthenticated = false;
          state.token = null;
          state.user = null;
          state.sparks = [];
          
          await chrome.storage.local.remove([
            CONFIG.STORAGE.TOKEN,
            CONFIG.STORAGE.USER,
            CONFIG.STORAGE.SPARKS,
          ]);
          
          sendResponse({ success: true });
          break;

        // NOTE: checkAuth is now handled BEFORE the async IIFE

        case 'login':
          // Open Sparks app with ext_id so Token Bridge sends token back
          chrome.tabs.create({ url: `${CONFIG.AUTH_URL}&ext_id=${chrome.runtime.id}` });
          sendResponse({ success: true, pending: true });
          break;

        case 'getAuthUrl':
          // Return URL for login page (include ext_id for token bridge)
          sendResponse({ url: `${CONFIG.AUTH_URL}&ext_id=${chrome.runtime.id}` });
          break;

        case 'logout':
          // Clear all auth state
          state.isAuthenticated = false;
          state.token = null;
          state.user = null;
          state.sparks = [];
          await chrome.storage.local.remove([
            CONFIG.STORAGE.TOKEN,
            CONFIG.STORAGE.USER,
            CONFIG.STORAGE.SPARKS,
            'sparks_drive_token',
            'sparks_drive_token_time',
            'cached_sparks'
          ]);
          console.log('[SPARKS] Logged out, all auth cleared');
          sendResponse({ success: true });
          break;

        // NOTE: getSparks and syncSparks are now handled BEFORE the async IIFE
        // for reliable sendResponse in MV3 service workers

        // === PHASE 1: QUICK RETRIEVAL (v1) or V2 TWO-PHASE LLM ===
        case 'phase1Analysis':
          // Check if already analyzed this tab
          if (sender.tab?.id && analyzedTabs.has(sender.tab.id)) {
            console.log('[SPARKS BG] Tab already analyzed, skipping');
            sendResponse({ candidates: [], count: 0, cached: true });
            break;
          }

          // Check catcher version
          await checkCatcherVersion();

          if (state.catcherVersion === 'v2' && state.sparks.length > 0) {
            // === V2 MODE: Two-phase LLM analysis ===
            logModeConfig('v2');
            console.log(`[SPARKS BG] V2: Analyzing ${state.sparks.length} projects...`);

            // Run v2 analysis for ALL active projects
            let allMatches = [];
            let anyRelevant = false;

            for (const project of state.sparks) {
              console.log(`[SPARKS BG] V2: Analyzing for project "${project.title}"...`);
              const v2Result = await runV2Analysis(message.pageData, project);

              if (v2Result.phase1_decision !== 'NOT_RELEVANT') {
                anyRelevant = true;
              }

              if (v2Result.matches && v2Result.matches.length > 0) {
                allMatches = allMatches.concat(v2Result.matches);
                console.log(`[SPARKS BG] V2: Found ${v2Result.matches.length} matches for "${project.title}"`);
              }
            }

            // Mark tab as analyzed
            if (sender.tab?.id) {
              analyzedTabs.add(sender.tab.id);
            }

            // Update badge
            if (sender.tab?.id && allMatches.length > 0) {
              await chrome.action.setBadgeText({
                tabId: sender.tab.id,
                text: allMatches.length.toString()
              });
              await chrome.action.setBadgeBackgroundColor({
                tabId: sender.tab.id,
                color: '#00C853'  // Green for v2 verified matches
              });
            }

            // Return in format compatible with content script
            sendResponse({
              candidates: allMatches,  // v2 returns verified matches directly
              count: allMatches.length,
              v2: true,
              phase1_decision: anyRelevant ? 'RELEVANT' : 'NOT_RELEVANT',
            });
            break;
          }

          // === V1 MODE: Embedding-based analysis ===
          logModeConfig('v1');
          console.log(`[SPARKS BG] V1: Analyzing ${state.sparks.length} sparks...`);

          const phase1Result = await runPhase1Analysis(message.pageData);

          // Mark tab as analyzed
          if (sender.tab?.id) {
            analyzedTabs.add(sender.tab.id);
          }

          // Update badge with candidate count
          if (sender.tab?.id && phase1Result.candidates.length > 0) {
            await chrome.action.setBadgeText({
              tabId: sender.tab.id,
              text: phase1Result.candidates.length.toString()
            });
            await chrome.action.setBadgeBackgroundColor({
              tabId: sender.tab.id,
              color: '#FFB800'
            });
          }

          sendResponse(phase1Result);
          break;
        
        // === PHASE 2: MISTRAL VERIFICATION ===
        case 'phase2Analysis':
          const phase2Result = await runPhase2Analysis(
            message.candidates,
            message.pageInfo
          );
          
          // Update badge to green if matches found
          if (sender.tab?.id) {
            if (phase2Result.matches.length > 0) {
              await chrome.action.setBadgeText({ 
                tabId: sender.tab.id, 
                text: phase2Result.matches.length.toString() 
              });
              await chrome.action.setBadgeBackgroundColor({ 
                tabId: sender.tab.id, 
                color: '#00C853' 
              });
            } else {
              await chrome.action.setBadgeText({ tabId: sender.tab.id, text: '' });
            }
          }
          
          sendResponse(phase2Result);
          break;
        
        // === LEGACY: FULL ANALYSIS (both phases) ===
        case 'analyzePage':
          const result = await analyzePage(message.pageData);
          
          if (sender.tab?.id) {
            await updateBadge(sender.tab.id, result.count);
          }
          
          sendResponse(result);
          break;
        
        // === SAVE SPARK ===
        case 'saveSpark':
          console.log('[SPARKS BG] Saving spark:', message.data);
          try {
            // Use /api/storage/sparks (same endpoint as Sparks App)
            const saveResult = await apiCall('/api/storage/sparks', {
              method: 'POST',
              body: JSON.stringify(message.data),
            });
            console.log('[SPARKS BG] Save result:', saveResult);
            sendResponse(saveResult);
          } catch (saveError) {
            console.error('[SPARKS BG] Save error:', saveError);
            sendResponse({ error: saveError.message });
          }
          break;
        
        // === OPEN SPARKS APP ===
        case 'openSparks':
          chrome.tabs.create({ url: `${CONFIG.APP_URL}/sparks` });
          sendResponse({ opened: true });
          break;
        
        // === OPEN LOGIN ===
        case 'openLogin':
          chrome.tabs.create({ url: `${CONFIG.AUTH_URL}&ext_id=${chrome.runtime.id}` });
          sendResponse({ opened: true });
          break;
        
        // === SELECTION ===
        case 'openWithSelection':
          await chrome.storage.local.set({
            pending_selection: {
              text: message.selection,
              url: message.url,
              title: message.title,
            },
          });
          sendResponse({ stored: true });
          break;

        // === GET CATCHER VERSION ===
        case 'getCatcherVersion':
          await checkCatcherVersion();
          sendResponse({
            version: state.catcherVersion,
            v2_enabled: CONFIG.CATCHER_V2.ENABLED,
          });
          break;

        // === LOCAL AI STATUS ===
        case 'GET_LOCAL_AI_STATUS':
          try {
            // Check offscreen document status
            if (!state.offscreenReady) {
              sendResponse({
                success: true,
                status: {
                  modelLoaded: false,
                  modelInitializing: false,
                  modelName: CONFIG.LOCAL_AI.MODEL_NAME,
                  message: 'Offscreen not ready'
                }
              });
              break;
            }

            // Ask offscreen for model status
            const aiStatus = await sendToOffscreen('GET_STATUS');
            sendResponse({
              success: true,
              status: aiStatus?.status || {
                modelLoaded: false,
                modelName: CONFIG.LOCAL_AI.MODEL_NAME
              }
            });
          } catch (aiError) {
            console.error('[SPARKS BG] AI status error:', aiError);
            sendResponse({
              success: false,
              error: aiError.message,
              status: { modelLoaded: false, modelName: CONFIG.LOCAL_AI.MODEL_NAME }
            });
          }
          break;

        default:
          sendResponse({ error: 'Unknown action' });
      }
    } catch (error) {
      console.error('[SPARKS BG] Message handler error:', error);
      sendResponse({ error: error.message });
    }
  })();
  
  return true; // Keep channel open for async response
});

// =============================================================================
// EXTERNAL MESSAGING (Token Bridge from Main App)
// =============================================================================

/**
 * Receive token from main Redact app.
 * This enables "Zero-Config" login for users.
 * 
 * Main app calls: chrome.runtime.sendMessage(EXTENSION_ID, { type: 'SET_TOKEN', ... })
 */
chrome.runtime.onMessageExternal.addListener(async (message, sender, sendResponse) => {
  console.log('[SPARKS BG] External message from:', sender.origin);
  
  // Verify sender is our app
  const allowedOrigins = [
    'https://editor.redact-app.com',
    'https://sparks.redact-app.com',
    'https://redact-gcp.web.app',
    'https://redact-api-vnyj3f4eaq-ew.a.run.app',
    'http://localhost:8080',
    'http://localhost:3000',
    'http://127.0.0.1:8000',
  ];
  
  if (!allowedOrigins.some(o => sender.origin?.startsWith(o))) {
    console.warn('[SPARKS BG] Rejected external message from unknown origin:', sender.origin);
    sendResponse({ error: 'Unauthorized origin' });
    return;
  }
  
  try {
    switch (message.type) {
      case 'SET_TOKEN':
        // Token bridge - receive token from main app
        state.token = message.token;
        state.user = message.user || {};
        state.isAuthenticated = true;

        const storageData = {
          [CONFIG.STORAGE.TOKEN]: message.token,
          [CONFIG.STORAGE.USER]: message.user,
        };

        // Save Drive token if provided
        if (message.driveToken) {
          storageData['sparks_drive_token'] = message.driveToken;
          console.log('[SPARKS BG] Drive token saved');
        }

        await chrome.storage.local.set(storageData);

        // Sync sparks immediately
        await syncSparks();

        console.log('[SPARKS BG] ✅ Token received from main app');
        sendResponse({ success: true, sparks: state.sparks.length });
        break;

      case 'SET_DRIVE_TOKEN':
        // Receive Drive token from SparksDrive after user enables sync
        if (message.driveToken) {
          await chrome.storage.local.set({
            'sparks_drive_token': message.driveToken
          });
          console.log('[SPARKS BG] ✅ Drive token received and saved');

          // Also update auth token if provided
          if (message.token) {
            state.token = message.token;
            state.user = message.user || state.user;
            state.isAuthenticated = true;
            await chrome.storage.local.set({
              [CONFIG.STORAGE.TOKEN]: message.token,
              [CONFIG.STORAGE.USER]: message.user,
            });
          }

          sendResponse({ success: true });
        } else {
          sendResponse({ success: false, error: 'No Drive token provided' });
        }
        break;

      case 'LOGOUT':
        state.isAuthenticated = false;
        state.token = null;
        state.user = null;
        state.sparks = [];
        
        await chrome.storage.local.remove([
          CONFIG.STORAGE.TOKEN,
          CONFIG.STORAGE.USER,
          CONFIG.STORAGE.SPARKS,
        ]);
        
        console.log('[SPARKS BG] ✅ Logged out via main app');
        sendResponse({ success: true });
        break;
      
      case 'PING':
        // Health check
        sendResponse({ 
          alive: true, 
          authenticated: state.isAuthenticated,
          sparks: state.sparks.length,
          localAI: CONFIG.LOCAL_AI.ENABLED,
          offscreenReady: state.offscreenReady,
        });
        break;
      
      default:
        sendResponse({ error: 'Unknown message type' });
    }
  } catch (error) {
    console.error('[SPARKS BG] External message error:', error);
    sendResponse({ error: error.message });
  }
  
  return true;
});

// =============================================================================
// STARTUP COMPLETE
// =============================================================================

console.log('[SPARKS BG] Service worker loaded');
console.log('[SPARKS BG] Local AI:', CONFIG.LOCAL_AI.ENABLED ? 'ENABLED' : 'DISABLED');

// CRITICAL: Load state immediately when service worker starts
// This is needed because MV3 service workers can be terminated at any time
// and onInstalled/onStartup don't fire when the worker is "woken up"
(async () => {
  console.log('[SPARKS BG] Loading state on service worker wake...');
  await loadStateFromStorage();
  console.log('[SPARKS BG] State loaded, sparks:', state.sparks?.length || 0);
  stateLoadedResolve(); // Signal that state is ready
})();
