// OrbitCosmo background service worker — the thin entry: the orbit:* message
// router and toolbar/side-panel wiring. Everything else lives in the module
// it belongs to (manifest "type": "module"):
//
//   store.js       — IndexedDB persistence, blob migration, replay wake path,
//                    wire shaping (imports db.js/migrate.js/projections.js
//                    and the pure layers)
//   pipeline.js    — tab lineage, the visit/embed write cycles under the
//                    withGraph lock, offscreen embeddings, recall search
//   appwindow.js   — the app-style popup window (open/focus/bounds)
//   projections.js — pure folds: fold one visit event == replay of length 1
//   wiki.js        — the LLM-maintained wiki (ingest/ask/file/export)
//
// Capture, graph, search, and embeddings all run on-device. The one
// exception is the opt-in wiki: claude.js is the single module that talks
// to the network, and the manifest CSP allowlists exactly its two hosts.

import { loadGraph, shapeForWire } from "./store.js";
import { recordPage, handleSearch, handleSitting, handleQueue, embedText } from "./pipeline.js";
import { openAppWindow } from "./appwindow.js";
import {
  ingestSession, answerQuestion, fileAnswer, revertPage,
  listPages, getPage, wikiStatus, exportWiki,
} from "./wiki.js";

// --- Messages from content script + side panel/app pages --------------------
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg?.type === "orbit:page") {
    recordPage(msg.payload, sender);
    return false;
  }
  if (msg?.type === "orbit:getGraph") {
    loadGraph().then((g) => sendResponse(shapeForWire(g)));
    return true; // async
  }
  if (msg?.type === "orbit:search") {
    handleSearch(msg.query).then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:sitting") {
    handleSitting(msg.url, msg.other).then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:queue") {
    // The reading queue: ghosts ranked by who references them (the wire
    // graph ships live ghosts too — see shapeForWire; this is the ranked
    // rail view).
    handleQueue().then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:warmup") {
    // Fired on search-box focus so the offscreen MiniLM cold start is
    // absorbed before the first keystroke's query lands.
    embedText("orbit").then(() => sendResponse({ ok: true }));
    return true; // async
  }
  if (msg?.type === "orbit:openApp") {
    openAppWindow(sendResponse);
    return true; // async
  }
  // --- wiki surface (wiki.js) — every handler resolves a typed {ok, ...} ----
  if (msg?.type === "orbit:wikiIngest") {
    ingestSession(msg.sessionId).then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:wikiAsk") {
    answerQuestion({ urls: msg.urls, question: msg.question, facts: msg.facts }).then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:wikiFile") {
    fileAnswer({ question: msg.question, answer: msg.answer, citations: msg.citations }).then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:wikiRevert") {
    revertPage({ slug: msg.slug, version: msg.version }).then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:wikiList") {
    listPages().then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:wikiPage") {
    getPage(msg.slug).then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:wikiStatus") {
    wikiStatus().then(sendResponse);
    return true; // async
  }
  if (msg?.type === "orbit:wikiExport") {
    exportWiki().then(sendResponse);
    return true; // async
  }
  return false;
});

// --- Toolbar: a click opens the side panel ----------------------------------
function enableSidePanelOnClick() {
  chrome.sidePanel
    ?.setPanelBehavior?.({ openPanelOnActionClick: true })
    .catch(() => {});
}
enableSidePanelOnClick();
chrome.runtime.onInstalled.addListener(enableSidePanelOnClick);

// Fallback: if Chrome doesn't honor openPanelOnActionClick, the action click
// still fires here — open the panel explicitly (a click is a valid gesture).
chrome.action?.onClicked?.addListener((tab) => {
  if (tab?.windowId != null) {
    chrome.sidePanel?.open?.({ windowId: tab.windowId }).catch(() => {});
  }
});
