import { ConnectionManager } from "./services/ConnectionManager.js";
import { NativePortManager } from "./services/NativePortManager.js";
import {
  DEFAULT_AUTO_CONNECT,
  DEFAULT_AUTO_PAIR,
  DEFAULT_DISCOVERY_PORT,
  DEFAULT_NATIVE_ENABLED,
  DEFAULT_PAIRING_ENABLED,
  DEFAULT_RELAY_PORT
} from "./relay-settings.js";
import { logError } from "./logging.js";
import { OpsRuntime } from "./ops/ops-runtime.js";
import { CanvasRuntime } from "./canvas/canvas-runtime.js";
import {
  formatDispatchSourceLabel,
  stripAnnotationPayloadScreenshots
} from "./annotation-payload.js";
import { getRestrictionMessage } from "./services/url-restrictions.js";
import type {
  AgentInboxReceipt,
  AnnotationCommand,
  AnnotationDispatchSource,
  AnnotationErrorCode,
  AnnotationPayload,
  AnnotationResponse,
  AnnotationScreenshotMode,
  BackgroundMessage,
  ConnectionStatus,
  NativeTransportHealth,
  PopupAnnotationGetPayloadResponse,
  PopupAnnotationLastMetaResponse,
  PopupAnnotationMeta,
  PopupAnnotationSendPayloadResponse,
  PopupAnnotationStartResponse,
  PopupMessage,
  RelayAnnotationCommand,
  RelayAnnotationEvent,
  RelayAnnotationResponse,
  RelayHealthStatus
} from "./types.js";

const connection = new ConnectionManager();
let canvasRuntime: CanvasRuntime;
const opsRuntime = new OpsRuntime({
  send: (message) => connection.sendOpsMessage(message),
  cdp: connection.getCdpRouter(),
  getCanvasPageState: (targetId) => canvasRuntime.getPageStateByTargetId(targetId),
  performCanvasPageAction: (targetId, action, selector) => canvasRuntime.performPageAction(targetId, action, selector)
});
canvasRuntime = new CanvasRuntime({
  send: (message) => connection.sendCanvasMessage(message),
  registerOpsCanvasTarget: (browserSessionId, targetId) => opsRuntime.registerCanvasTargetForSession(browserSessionId, targetId),
  unregisterOpsCanvasTarget: (browserSessionId, targetId) => opsRuntime.unregisterCanvasTargetForSession(browserSessionId, targetId)
});
const nativePort = new NativePortManager({
  onMessage: (payload) => {
    handleNativePortMessage(payload).catch((error) => {
      logError("native_port.message", error, { code: "native_message_failed" });
    });
  },
  onDisconnect: () => {
    updateBadge(getEffectiveStatus());
    void refreshBadgeFromBackgroundStatus();
  }
});
let autoConnectInFlight = false;
let statusNoteOverride: string | null = null;
let retryScheduled = false;
let retryDelayMs = 5000;
let autoConnectEnabled = DEFAULT_AUTO_CONNECT;
let nativeEnabled = DEFAULT_NATIVE_ENABLED;
let lastRelayCleanupStatus: ConnectionStatus | null = null;

const RETRY_ALARM_NAME = "opendevbrowser-auto-connect";
const WATCHDOG_ALARM_NAME = "opendevbrowser-auto-connect-watchdog";
const RETRY_MAX_MS = 60_000;
const WATCHDOG_PERIOD_MINUTES = 1;
const ANNOTATION_CONTENT_SCRIPT = "dist/annotate-content.js";
const ANNOTATION_CONTENT_STYLE = "dist/annotate-content.css";
const ANNOTATION_MAX_PAYLOAD_BYTES = 10 * 1024 * 1024;
const ANNOTATION_REQUEST_TIMEOUT_MS = 120_000;
const ANNOTATION_RECEIVER_UNAVAILABLE_MESSAGE = "Annotation UI did not load in the page. Reload the tab and retry.";
const LAST_ANNOTATION_META_KEY = "annotationLastMeta";
const LAST_ANNOTATION_PAYLOAD_KEY = "annotationLastPayloadSansScreenshots";
const LAST_AGENT_ANNOTATION_META_KEY = "annotationAgentMeta";
const LAST_AGENT_ANNOTATION_PAYLOAD_KEY = "annotationAgentPayloadSansScreenshots";
const LAST_ANNOTATABLE_TAB_ID_KEY = "annotationLastTabId";
const POPUP_ANNOTATION_TARGET_TTL_MS = 10_000;
const BADGE_CONNECTED_DOT_COLOR = "#16a34a";
const BADGE_WARNING_DOT_COLOR = "#f59e0b";
const BADGE_DISCONNECTED_DOT_COLOR = "#dc2626";

type AnnotationTransport = "relay" | "native" | "popup";

type AnnotationSession = {
  requestId: string;
  tabId: number;
  options?: AnnotationCommand["options"];
  createdAt: number;
  timeoutId: number;
  transport: AnnotationTransport;
};

type AnnotationBridgeStatus = {
  ok: true;
  bootId: string;
  active: boolean;
};

type PopupAnnotationTargetState = {
  tabId: number;
  bootId: string | null;
  resolvedAt: number;
};

const annotationSessions = new Map<string, AnnotationSession>();
let lastAnnotationFull: { meta: PopupAnnotationMeta; payload: AnnotationPayload } | null = null;
let lastAgentAnnotationFull: { meta: PopupAnnotationMeta; payload: AnnotationPayload } | null = null;
let lastAnnotatableTabId: number | null = null;
let lastPopupAnnotationTarget: PopupAnnotationTargetState | null = null;

connection.onAnnotationCommand((command) => {
  handleRelayAnnotationCommand(command).catch((error) => {
    logError("annotation.relay_command", error, { code: "annotation_command_failed" });
  });
});

connection.onOpsMessage((message) => {
  opsRuntime.handleMessage(message);
});

connection.onCanvasMessage((message) => {
  canvasRuntime.handleMessage(message);
});

type RelayConfig = {
  relayPort: number;
  pairingRequired: boolean;
  instanceId: string | null;
  epoch: number | null;
};

type ContentScriptMessage =
  | {
    type: "annotation:capture";
    requestId: string;
    mode: AnnotationScreenshotMode;
  }
  | {
    type: "annotation:complete";
    requestId: string;
    payload: AnnotationPayload;
  }
  | {
    type: "annotation:cancelled";
    requestId: string;
  }
  | {
    type: "annotation:error";
    requestId: string;
    error: { code: AnnotationErrorCode; message: string };
  };

type BadgeTone = ConnectionStatus | "warning";

const updateBadge = (tone: BadgeTone): void => {
  const dotColor = tone === "connected"
    ? BADGE_CONNECTED_DOT_COLOR
    : tone === "warning"
      ? BADGE_WARNING_DOT_COLOR
      : BADGE_DISCONNECTED_DOT_COLOR;

  chrome.action.setBadgeText({ text: "●" });

  if (typeof chrome.action.setBadgeTextColor === "function") {
    chrome.action.setBadgeTextColor({ color: dotColor });
    chrome.action.setBadgeBackgroundColor({ color: [0, 0, 0, 0] });
    return;
  }

  chrome.action.setBadgeBackgroundColor({ color: dotColor });
};

const getEffectiveStatus = (): ConnectionStatus => {
  if (connection.getStatus() === "connected") {
    return "connected";
  }
  if (nativeEnabled && nativePort.isConnected()) {
    return "connected";
  }
  return "disconnected";
};

const hasReadyRelayHandshake = (health: RelayHealthStatus | null): boolean => {
  return health?.ok !== false && health?.extensionConnected === true && health.extensionHandshakeComplete === true;
};

const deriveBackgroundStatus = (
  relayStatus: ConnectionStatus,
  nativeHealth: NativeTransportHealth | null,
  relayHealth: RelayHealthStatus | null,
  reconnectSuppressed: boolean
): ConnectionStatus => {
  if (relayStatus === "connected") {
    if (relayHealth?.ok === false) {
      return "disconnected";
    }
    return "connected";
  }
  if (reconnectSuppressed) {
    return "disconnected";
  }
  if (nativeHealth?.status === "connected") {
    return "connected";
  }
  return hasReadyRelayHandshake(relayHealth) ? "connected" : "disconnected";
};

const deriveBadgeTone = (
  status: ConnectionStatus,
  relayHealth: RelayHealthStatus | null
): BadgeTone => {
  if (relayHealth?.ok === false) {
    return status === "connected" ? "warning" : "disconnected";
  }
  if (status === "connected") {
    return "connected";
  }
  return hasReadyRelayHandshake(relayHealth) ? "warning" : "disconnected";
};

const buildStatusMessage = async (): Promise<BackgroundMessage> => {
  const error = connection.getLastError();
  const relayStatus = connection.getStatus();
  const reconnectSuppressed = connection.isReconnectSuppressed();
  let note = error?.message;
  let relayHealth: RelayHealthStatus | null = null;
  const isNativeEnabled = nativeEnabled;
  let nativeHealth: NativeTransportHealth | null = isNativeEnabled ? nativePort.getHealth() : null;

  if (isNativeEnabled && nativePort.isConnected()) {
    try {
      await nativePort.ping(1000);
    } catch (error) {
      logError("native_port.ping", error, { code: "native_ping_failed" });
    }
    nativeHealth = nativePort.getHealth();
  }

  if (!error) {
    if (relayStatus === "connected") {
      const identity = connection.getRelayIdentity();
      if (identity.relayPort && identity.instanceId) {
        note = `Connected to 127.0.0.1:${identity.relayPort} (relay ${identity.instanceId.slice(0, 8)})`;
      } else if (identity.relayPort) {
        note = `Connected to 127.0.0.1:${identity.relayPort}`;
      }
      relayHealth = await connection.relayHealthCheck();
      if (relayHealth?.ok === false) {
        note = statusNoteOverride ?? buildRelayHealthNote(relayHealth);
      }
    } else {
      const stored = await new Promise<Record<string, unknown>>((resolve) => {
        chrome.storage.local.get(["relayPort"], (items) => resolve(items));
      });
      const port = parsePort(stored.relayPort) ?? DEFAULT_RELAY_PORT;
      relayHealth = await fetchRelayHealth(port);
      if (reconnectSuppressed) {
        note = statusNoteOverride
          ?? "Another extension client took over the relay connection. This client will stay disconnected until you reconnect it explicitly.";
      } else if (hasReadyRelayHandshake(relayHealth)) {
        note = `Connected to 127.0.0.1:${port}`;
      } else if (nativeHealth?.status === "connected") {
        note = "Connected via native host.";
      } else {
        note = statusNoteOverride ?? buildRelayHealthNote(relayHealth);
      }
      if (!hasReadyRelayHandshake(relayHealth) && !statusNoteOverride && isNativeEnabled && nativeHealth?.status === "error") {
        note = buildNativeHealthNote(nativeHealth);
      }
    }
  }

  const status = deriveBackgroundStatus(relayStatus, nativeHealth, relayHealth, reconnectSuppressed);

  if (!error) {
    const relayNotice = connection.getRelayNotice();
    if (relayNotice) {
      note = relayNotice;
    }
  }

  return {
    type: "status",
    status,
    note,
    relayHealth,
    nativeHealth,
    nativeEnabled: isNativeEnabled
  };
};

let badgeRefreshVersion = 0;

const refreshBadgeFromBackgroundStatus = async (): Promise<void> => {
  const refreshVersion = ++badgeRefreshVersion;
  let tone: BadgeTone = getEffectiveStatus();
  try {
    const statusMessage = await buildStatusMessage();
    tone = deriveBadgeTone(statusMessage.status, statusMessage.relayHealth ?? null);
  } catch {
    // Keep the synchronous local status when relay/native health cannot be fetched.
  }
  if (refreshVersion === badgeRefreshVersion) {
    updateBadge(tone);
  }
};

const setStorage = (items: Record<string, unknown>): Promise<void> => {
  return new Promise((resolve) => {
    chrome.storage.local.set(items, () => resolve());
  });
};

const setStatusNoteOverride = (note: string | null): void => {
  statusNoteOverride = note;
};

const loadStoredAnnotatableTabId = async (): Promise<number | null> => {
  const data = await new Promise<Record<string, unknown>>((resolve) => {
    chrome.storage.local.get([LAST_ANNOTATABLE_TAB_ID_KEY], (items) => resolve(items));
  });
  const value = data[LAST_ANNOTATABLE_TAB_ID_KEY];
  return typeof value === "number" && Number.isInteger(value) ? value : null;
};

const persistAnnotatableTabId = async (tabId: number | null): Promise<void> => {
  await setStorage({ [LAST_ANNOTATABLE_TAB_ID_KEY]: tabId });
};

const buildRelayHealthNote = (health: RelayHealthStatus | null): string => {
  if (!health) {
    return "Relay unreachable. Start the daemon and retry.";
  }
  switch (health.reason) {
    case "pairing_invalid":
      return "Pairing token mismatch. Update the token and reconnect.";
    case "pairing_required":
      return "Pairing required. Enable auto-pair or set the token.";
    case "handshake_incomplete":
      return "Extension websocket is up but the daemon-extension handshake is incomplete. Open the popup and click Connect again to re-establish a clean handshake, then confirm `status --daemon` shows ext=on and handshake=on.";
    case "extension_disconnected":
      return "Extension not connected to relay. Click Connect.";
    case "annotation_disconnected":
      return "Annotation channel disconnected. Keep the extension open and retry.";
    case "ops_disconnected":
      return "Ops channel disconnected. Start a new session and retry.";
    case "canvas_disconnected":
      return "Canvas channel disconnected. Reopen the design canvas command and retry.";
    case "cdp_disconnected":
      return "No CDP clients connected. Start a session and retry.";
    case "relay_dirty":
      return "Relay has active CDP, annotation, canvas clients, or ops-owned targets. Disconnect the current run and retry.";
    case "relay_down":
      return "Relay down. Start the daemon and retry.";
    default:
      return "Local relay only. Tokens stay on-device.";
  }
};

const buildNativeHealthNote = (health: NativeTransportHealth): string => {
  if (health.status === "connected") {
    return "Native host connected.";
  }
  switch (health.error) {
    case "host_not_installed":
      return "Native host not installed. Run `opendevbrowser native install <extension-id>`.";
    case "host_forbidden":
      return "Native host forbidden. Verify the extension ID matches the manifest.";
    case "host_disconnect":
      return "Native host disconnected. Restart the host.";
    case "host_timeout":
      return "Native host ping timed out.";
    case "host_message_too_large":
      return "Native host rejected message size.";
    default:
      return "Native host unavailable.";
  }
};

const clearRetry = (): void => {
  retryScheduled = false;
  retryDelayMs = 5000;
  if (chrome.alarms?.clear) {
    chrome.alarms.clear(RETRY_ALARM_NAME);
  }
};

const scheduleWatchdog = (): void => {
  if (chrome.alarms?.create) {
    chrome.alarms.create(WATCHDOG_ALARM_NAME, { periodInMinutes: WATCHDOG_PERIOD_MINUTES });
  }
};

const clearWatchdog = (): void => {
  if (chrome.alarms?.clear) {
    chrome.alarms.clear(WATCHDOG_ALARM_NAME);
  }
};

const scheduleRetry = (): void => {
  if (retryScheduled) {
    return;
  }
  retryScheduled = true;
  const delayMs = retryDelayMs;
  retryDelayMs = Math.min(retryDelayMs * 2, RETRY_MAX_MS);

  if (chrome.alarms?.create) {
    chrome.alarms.create(RETRY_ALARM_NAME, { when: Date.now() + delayMs });
    return;
  }

  setTimeout(() => {
    retryScheduled = false;
    autoConnect().catch((error) => {
      logError("auto_connect.retry", error, { code: "auto_connect_failed" });
    });
  }, delayMs);
};

const attemptNativeConnect = async (): Promise<boolean> => {
  if (!nativeEnabled) {
    return false;
  }
  const connected = await nativePort.connect();
  if (!connected) {
    return false;
  }
  try {
    await nativePort.ping(1500);
  } catch (error) {
    logError("native_port.ping", error, { code: "native_ping_failed" });
  }
  updateBadge(getEffectiveStatus());
  await refreshBadgeFromBackgroundStatus();
  return nativePort.isConnected();
};

const parsePort = (value: unknown): number | null => {
  if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535) {
    return value;
  }
  if (typeof value === "string" && value.trim()) {
    const parsed = Number(value);
    if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
      return parsed;
    }
  }
  return null;
};

const parseEpoch = (value: unknown): number | null => {
  if (typeof value === "number" && Number.isFinite(value)) {
    return value;
  }
  return null;
};

const getAnnotationRestrictionMessage = (url: URL): string | null => {
  return getRestrictionMessage(url, {
    restrictedSchemeMessage: "Active tab uses a restricted URL scheme. Open a normal http(s) page and retry.",
    webStoreMessage: "Chrome Web Store tabs cannot be annotated. Open a normal tab and retry."
  });
};

const fetchRelayConfig = async (port: number): Promise<RelayConfig | null> => {
  try {
    const response = await fetch(`http://127.0.0.1:${port}/config`, {
      method: "GET",
      headers: { "Accept": "application/json" }
    });
    if (!response.ok) {
      return null;
    }
    const data = await response.json();
    const relayPort = parsePort(data.relayPort);
    if (!relayPort) {
      return null;
    }
    const pairingRequired = typeof data.pairingRequired === "boolean" ? data.pairingRequired : true;
    const instanceId = typeof data.instanceId === "string" ? data.instanceId : null;
    const epoch = parseEpoch(data.epoch);
    return { relayPort, pairingRequired, instanceId, epoch };
  } catch (error) {
    logError("relay.config_fetch", error, { code: "relay_config_fetch_failed", extra: { port } });
    return null;
  }
};

const fetchRelayHealth = async (port: number): Promise<RelayHealthStatus | null> => {
  try {
    const response = await fetch(`http://127.0.0.1:${port}/status`, {
      method: "GET",
      headers: { "Accept": "application/json" }
    });
    if (!response.ok) {
      return null;
    }
    const data = await response.json() as Record<string, unknown>;
    if (data.health && typeof data.health === "object") {
      const health = data.health as Record<string, unknown>;
      return typeof health.opsOwnedTargetCount === "number" && Number.isFinite(health.opsOwnedTargetCount)
        ? health as RelayHealthStatus
        : null;
    }
    const extensionConnected = data.extensionConnected === true;
    const handshake = data.extensionHandshakeComplete === true;
    const cdpConnected = data.cdpConnected === true;
    const annotationConnected = data.annotationConnected === true;
    const opsConnected = data.opsConnected === true;
    const opsOwnedTargetCount = typeof data.opsOwnedTargetCount === "number" && Number.isFinite(data.opsOwnedTargetCount)
      ? data.opsOwnedTargetCount
      : null;
    if (opsOwnedTargetCount === null || opsOwnedTargetCount < 0) {
      return null;
    }
    const canvasConnected = data.canvasConnected === true;
    const pairingRequired = data.pairingRequired === true;
    const dirty = cdpConnected || annotationConnected || canvasConnected || opsOwnedTargetCount > 0;
    const ok = extensionConnected && handshake && !dirty;
    return {
      ok,
      reason: ok
        ? "ok"
        : (dirty ? "relay_dirty" : (extensionConnected ? "handshake_incomplete" : "extension_disconnected")),
      extensionConnected,
      extensionHandshakeComplete: handshake,
      cdpConnected,
      annotationConnected,
      opsConnected,
      opsOwnedTargetCount,
      canvasConnected,
      pairingRequired
    };
  } catch (error) {
    logError("relay.health_fetch", error, { code: "relay_health_fetch_failed", extra: { port } });
    return null;
  }
};

const relayHasActiveExtensionClient = async (storedRelayPort: number): Promise<boolean> => {
  const storedHealth = await fetchRelayHealth(storedRelayPort);
  return hasReadyRelayHandshake(storedHealth);
};

const sendAnnotationResponse = (payload: AnnotationResponse, transport: AnnotationTransport = "relay"): void => {
  if (transport === "popup") {
    return;
  }
  const response: RelayAnnotationResponse = { type: "annotationResponse", payload };
  if (transport === "native") {
    nativePort.send(response);
    return;
  }
  connection.sendAnnotationResponse(response);
};

const sendAnnotationEvent = (payload: RelayAnnotationEvent["payload"], transport: AnnotationTransport = "relay"): void => {
  if (transport === "popup") {
    return;
  }
  const event: RelayAnnotationEvent = { type: "annotationEvent", payload };
  if (transport === "native") {
    nativePort.send(event);
    return;
  }
  connection.sendAnnotationEvent(event);
};

const startAnnotationTimeout = (requestId: string, transport: AnnotationTransport): number => {
  return setTimeout(() => {
    const session = annotationSessions.get(requestId);
    if (!session) return;
    annotationSessions.delete(requestId);
    const response: AnnotationResponse = {
      version: 1,
      requestId,
      status: "error",
      error: { code: "timeout", message: "Annotation request timed out." }
    };
    const meta = buildLastAnnotationMeta(requestId, response, false);
    lastAnnotationFull = null;
    persistLastAnnotation(meta, null).catch((error) => {
      logError("annotation.persist_timeout_meta", error, { code: "annotation_persist_failed" });
    });
    sendAnnotationResponse(response, transport);
  }, ANNOTATION_REQUEST_TIMEOUT_MS);
};

const getTab = async (tabId: number): Promise<chrome.tabs.Tab | null> => {
  try {
    return await chrome.tabs.get(tabId);
  } catch (error) {
    logError("tabs.get", error, { code: "tab_lookup_failed", extra: { tabId } });
    return null;
  }
};

const createTab = async (url?: string): Promise<chrome.tabs.Tab> => {
  return await new Promise((resolve, reject) => {
    chrome.tabs.create({ url, active: true }, (tab) => {
      const lastError = chrome.runtime.lastError;
      if (lastError) {
        reject(new Error(lastError.message));
        return;
      }
      if (!tab) {
        reject(new Error("Tab creation failed"));
        return;
      }
      resolve(tab);
    });
  });
};

const updateTabUrl = async (tabId: number, url: string): Promise<chrome.tabs.Tab> => {
  return await new Promise((resolve, reject) => {
    chrome.tabs.update(tabId, { url, active: true }, (tab) => {
      const lastError = chrome.runtime.lastError;
      if (lastError) {
        reject(new Error(lastError.message));
        return;
      }
      if (!tab) {
        reject(new Error("Tab update failed"));
        return;
      }
      resolve(tab);
    });
  });
};

const waitForTabComplete = async (tabId: number, timeoutMs = 10000): Promise<void> => {
  const tab = await getTab(tabId);
  if (tab?.status === "complete") return;
  await new Promise<void>((resolve, reject) => {
    let settled = false;
    const timeout = setTimeout(() => {
      if (settled) return;
      settled = true;
      chrome.tabs.onUpdated.removeListener(listener);
      reject(new Error("Tab load timeout"));
    }, timeoutMs);

    const listener = (updatedId: number, changeInfo: chrome.tabs.OnUpdatedInfo) => {
      if (updatedId !== tabId) return;
      if (changeInfo.status === "complete") {
        if (settled) return;
        settled = true;
        clearTimeout(timeout);
        chrome.tabs.onUpdated.removeListener(listener);
        resolve();
      }
    };
    chrome.tabs.onUpdated.addListener(listener);
  });
};

const waitForAnnotationTabReady = async (tabId: number): Promise<void> => {
  const tab = await getTab(tabId);
  if (!tab || tab.status === "complete") {
    return;
  }
  try {
    await waitForTabComplete(tabId);
  } catch (error) {
    if (error instanceof Error && error.message === "Tab load timeout") {
      throw new Error("Active tab is still loading. Wait for it to finish and retry.");
    }
    throw error;
  }
};

const getActiveTab = async (): Promise<chrome.tabs.Tab | null> => {
  const tabs = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
  return tabs[0] ?? null;
};

const getTabRecency = (tab: chrome.tabs.Tab): number => {
  return typeof tab.lastAccessed === "number" ? tab.lastAccessed : -1;
};

const rememberAnnotatableTab = (tab: chrome.tabs.Tab | null): void => {
  if (!tab || typeof tab.id !== "number") {
    return;
  }
  if (isRestrictedTab(tab) !== null) {
    if (lastAnnotatableTabId === tab.id) {
      lastAnnotatableTabId = null;
      void persistAnnotatableTabId(null).catch((error) => {
        logError("annotation.persist_tab", error, { code: "annotation_tab_persist_failed", extra: { tabId: null } });
      });
    }
    return;
  }
  lastAnnotatableTabId = tab.id;
  void persistAnnotatableTabId(tab.id).catch((error) => {
    logError("annotation.persist_tab", error, { code: "annotation_tab_persist_failed", extra: { tabId: tab.id } });
  });
};

const getRememberedAnnotatableTab = async (excludeTabId?: number): Promise<chrome.tabs.Tab | null> => {
  if (typeof lastAnnotatableTabId !== "number") {
    lastAnnotatableTabId = await loadStoredAnnotatableTabId();
  }
  if (typeof lastAnnotatableTabId !== "number") {
    return null;
  }
  if (typeof excludeTabId === "number" && lastAnnotatableTabId === excludeTabId) {
    return null;
  }
  const tabId = lastAnnotatableTabId;
  const tab = await getTab(tabId);
  if (!tab || isRestrictedTab(tab) !== null) {
    if (lastAnnotatableTabId === tabId) {
      lastAnnotatableTabId = null;
      void persistAnnotatableTabId(null).catch((error) => {
        logError("annotation.persist_tab", error, { code: "annotation_tab_persist_failed", extra: { tabId: null } });
      });
    }
    return null;
  }
  return tab;
};

const clearPopupAnnotationTarget = (tabId?: number): void => {
  if (!lastPopupAnnotationTarget) {
    return;
  }
  if (typeof tabId === "number" && lastPopupAnnotationTarget.tabId !== tabId) {
    return;
  }
  lastPopupAnnotationTarget = null;
};

const rememberPopupAnnotationTarget = (tabId: number, bootId?: string | null): void => {
  const previousBootId = lastPopupAnnotationTarget?.tabId === tabId
    ? lastPopupAnnotationTarget.bootId
    : null;
  lastPopupAnnotationTarget = {
    tabId,
    bootId: typeof bootId === "string" ? bootId : previousBootId,
    resolvedAt: Date.now()
  };
};

const getCachedPopupAnnotationTab = async (): Promise<chrome.tabs.Tab | null> => {
  const cached = lastPopupAnnotationTarget;
  if (!cached) {
    return null;
  }
  if (Date.now() - cached.resolvedAt > POPUP_ANNOTATION_TARGET_TTL_MS) {
    clearPopupAnnotationTarget(cached.tabId);
    return null;
  }
  const tab = await getTab(cached.tabId);
  if (!tab || isRestrictedTab(tab) !== null) {
    clearPopupAnnotationTarget(cached.tabId);
    return null;
  }
  return tab;
};

const getPopupAnnotationTab = async (tabIdHint?: number): Promise<chrome.tabs.Tab | null> => {
  const cached = await getCachedPopupAnnotationTab();
  if (cached) {
    rememberAnnotatableTab(cached);
    return cached;
  }
  if (typeof tabIdHint === "number") {
    const hinted = await getTab(tabIdHint);
    if (hinted && isRestrictedTab(hinted) === null) {
      rememberAnnotatableTab(hinted);
      if (typeof hinted.id === "number") {
        rememberPopupAnnotationTarget(hinted.id);
      }
      return hinted;
    }
  }
  const preferred = await getPreferredAnnotationTab();
  if (preferred && typeof preferred.id === "number") {
    rememberPopupAnnotationTarget(preferred.id);
  }
  return preferred;
};

const getMostRecentAnnotatableTab = async (excludeTabId?: number): Promise<chrome.tabs.Tab | null> => {
  const tabs = await chrome.tabs.query({});
  const candidates = tabs
    .filter((tab) => {
      if (typeof tab.id !== "number") {
        return false;
      }
      if (typeof excludeTabId === "number" && tab.id === excludeTabId) {
        return false;
      }
      return isRestrictedTab(tab) === null;
    })
    .sort((left, right) => {
      const recencyDelta = getTabRecency(right) - getTabRecency(left);
      if (recencyDelta !== 0) {
        return recencyDelta;
      }
      const activeDelta = Number(Boolean(right.active)) - Number(Boolean(left.active));
      if (activeDelta !== 0) {
        return activeDelta;
      }
      return (right.id ?? 0) - (left.id ?? 0);
    });
  return candidates[0] ?? null;
};

const getPreferredAnnotationTab = async (): Promise<chrome.tabs.Tab | null> => {
  const active = await getActiveTab();
  if (active && isRestrictedTab(active) === null) {
    rememberAnnotatableTab(active);
    return active;
  }
  const excludeTabId = active && typeof active.id === "number" ? active.id : undefined;
  const remembered = await getRememberedAnnotatableTab(excludeTabId);
  if (remembered) {
    return remembered;
  }
  const fallback = await getMostRecentAnnotatableTab(excludeTabId);
  rememberAnnotatableTab(fallback);
  return fallback ?? active;
};

const resolveAnnotationTab = async (command: AnnotationCommand): Promise<chrome.tabs.Tab> => {
  if (typeof command.tabId === "number") {
    if (command.url) {
      const updated = await updateTabUrl(command.tabId, command.url);
      await waitForTabComplete(command.tabId);
      return updated;
    }
    const existing = await getTab(command.tabId);
    if (!existing) {
      throw new Error("Target tab unavailable");
    }
    return existing;
  }

  if (command.url) {
    const created = await createTab(command.url);
    if (typeof created.id === "number") {
      await waitForTabComplete(created.id);
    }
    return created;
  }

  const active = await getPreferredAnnotationTab();
  if (!active) {
    throw new Error("No active tab available");
  }
  return active;
};

const isRestrictedTab = (tab: chrome.tabs.Tab): string | null => {
  const rawUrl = tab.url ?? tab.pendingUrl ?? "";
  if (!rawUrl) return "Active tab URL unavailable.";
  let parsed: URL | null = null;
  try {
    parsed = new URL(rawUrl);
  } catch (error) {
    logError("annotation.parse_tab_url", error, { code: "tab_url_parse_failed" });
    return "Active tab URL is invalid.";
  }
  return getAnnotationRestrictionMessage(parsed);
};

const injectAnnotationAssets = async (tabId: number): Promise<void> => {
  await new Promise<void>((resolve, reject) => {
    chrome.scripting.insertCSS({ target: { tabId }, files: [ANNOTATION_CONTENT_STYLE] }, () => {
      const lastError = chrome.runtime.lastError;
      if (lastError) {
        reject(new Error(lastError.message));
        return;
      }
      resolve();
    });
  });

  await new Promise<void>((resolve, reject) => {
    chrome.scripting.executeScript({ target: { tabId }, files: [ANNOTATION_CONTENT_SCRIPT] }, () => {
      const lastError = chrome.runtime.lastError;
      if (lastError) {
        reject(new Error(lastError.message));
        return;
      }
      resolve();
    });
  });
};

const sendMessageToTab = async (tabId: number, message: Record<string, unknown>): Promise<unknown> => {
  return await new Promise((resolve, reject) => {
    chrome.tabs.sendMessage(tabId, message, (response) => {
      const lastError = chrome.runtime.lastError;
      if (lastError) {
        reject(new Error(lastError.message));
        return;
      }
      resolve(response);
    });
  });
};

const isMissingAnnotationReceiverError = (error: unknown): boolean => {
  if (!(error instanceof Error)) {
    return false;
  }
  return error.message.includes("Receiving end does not exist");
};

const readAnnotationBridgeStatus = (response: unknown): AnnotationBridgeStatus | null => {
  if (typeof response !== "object" || response === null) {
    return null;
  }
  const candidate = response as { ok?: unknown; bootId?: unknown; active?: unknown };
  if (candidate.ok !== true || typeof candidate.bootId !== "string" || typeof candidate.active !== "boolean") {
    return null;
  }
  return {
    ok: true,
    bootId: candidate.bootId,
    active: candidate.active
  };
};

const sendAnnotationMessageToTab = async (tabId: number, message: Record<string, unknown>): Promise<unknown> => {
  try {
    return await sendMessageToTab(tabId, message);
  } catch (error) {
    if (!isMissingAnnotationReceiverError(error)) {
      throw error;
    }
    await ensureAnnotationInjected(tabId);
    try {
      return await sendMessageToTab(tabId, message);
    } catch (retryError) {
      if (isMissingAnnotationReceiverError(retryError)) {
        throw new Error(ANNOTATION_RECEIVER_UNAVAILABLE_MESSAGE);
      }
      throw retryError;
    }
  }
};

const sleep = async (ms: number): Promise<void> => {
  await new Promise((resolve) => setTimeout(resolve, ms));
};

const pingAnnotation = async (tabId: number): Promise<AnnotationBridgeStatus> => {
  const response = await sendMessageToTab(tabId, { type: "annotation:ping" });
  const ready = readAnnotationBridgeStatus(response);
  if (!ready) {
    throw new Error("Annotation ping failed");
  }
  rememberPopupAnnotationTarget(tabId, ready.bootId);
  return ready;
};

const ensureAnnotationInjected = async (tabId: number): Promise<AnnotationBridgeStatus> => {
  await waitForAnnotationTabReady(tabId);
  try {
    return await pingAnnotation(tabId);
  } catch (error) {
    // Initial ping can fail before content script injection; avoid noisy logs for missing receivers.
    if (!isMissingAnnotationReceiverError(error)) {
      logError("annotation.ping", error, { code: "annotation_ping_failed", extra: { tabId } });
    }
  }

  const backoff = [150, 400];
  let lastError: unknown = null;
  for (let attempt = 0; attempt <= backoff.length; attempt += 1) {
    try {
      await waitForAnnotationTabReady(tabId);
      await injectAnnotationAssets(tabId);
      return await pingAnnotation(tabId);
    } catch (error) {
      lastError = error;
      logError("annotation.inject", error, { code: "annotation_injection_failed", extra: { tabId, attempt } });
      if (attempt < backoff.length) {
        const delay = backoff[attempt] ?? 0;
        await sleep(delay);
      }
    }
  }

  if (isMissingAnnotationReceiverError(lastError)) {
    throw new Error(ANNOTATION_RECEIVER_UNAVAILABLE_MESSAGE);
  }
  if (lastError instanceof Error) {
    throw lastError;
  }
  throw new Error("Annotation injection failed");
};

const refreshAnnotationInjected = async (tabId: number): Promise<AnnotationBridgeStatus> => {
  await waitForAnnotationTabReady(tabId);
  await injectAnnotationAssets(tabId);
  return await pingAnnotation(tabId);
};

const startAnnotationUi = async (
  tabId: number,
  requestId: string,
  options: AnnotationCommand["options"] | undefined,
  url?: string
): Promise<AnnotationBridgeStatus> => {
  let lastError: unknown = null;
  for (let attempt = 0; attempt < 2; attempt += 1) {
    try {
      const response = await sendAnnotationMessageToTab(tabId, {
        type: "annotation:start",
        requestId,
        options: options ?? {},
        url
      });
      const ready = readAnnotationBridgeStatus(response);
      if (ready?.active) {
        rememberPopupAnnotationTarget(tabId, ready.bootId);
        return ready;
      }
      throw new Error("Annotation start acknowledgement missing active bridge.");
    } catch (error) {
      lastError = error;
      if (attempt === 0) {
        await refreshAnnotationInjected(tabId);
        continue;
      }
    }
  }

  if (
    isMissingAnnotationReceiverError(lastError)
    || (lastError instanceof Error && lastError.message.includes("active bridge"))
  ) {
    throw new Error(ANNOTATION_RECEIVER_UNAVAILABLE_MESSAGE);
  }
  if (lastError instanceof Error) {
    throw lastError;
  }
  throw new Error("Annotation start failed");
};

const probeAnnotationInjected = async (tabIdHint?: number): Promise<{ injected: boolean; detail?: string }> => {
  const active = await getPopupAnnotationTab(tabIdHint);
  if (!active || typeof active.id !== "number") {
    return { injected: false, detail: "No active tab available." };
  }
  const restricted = isRestrictedTab(active);
  if (restricted) {
    return { injected: false, detail: restricted };
  }
  if (active.status && active.status !== "complete") {
    return { injected: false, detail: "Active tab is still loading." };
  }
  try {
    const ready = await pingAnnotation(active.id);
    rememberPopupAnnotationTarget(active.id, ready.bootId);
    return { injected: true };
  } catch (error) {
    const message = error instanceof Error ? error.message : "Annotation not injected.";
    return { injected: false, detail: message };
  }
};

const toggleAnnotationUi = async (): Promise<void> => {
  const tab = await getPreferredAnnotationTab();
  if (!tab || typeof tab.id !== "number") {
    return;
  }
  const restricted = isRestrictedTab(tab);
  if (restricted) {
    return;
  }
  await ensureAnnotationInjected(tab.id);
  await sendAnnotationMessageToTab(tab.id, { type: "annotation:toggle" });
};

const startAnnotationSession = async (command: AnnotationCommand, transport: AnnotationTransport): Promise<void> => {
  const requestId = command.requestId;
  if (annotationSessions.has(requestId)) {
    sendAnnotationResponse({
      version: 1,
      requestId,
      status: "error",
      error: { code: "invalid_request", message: "Duplicate annotation requestId." }
    }, transport);
    return;
  }

  const tab = await resolveAnnotationTab(command);
  const restricted = isRestrictedTab(tab);
  if (restricted) {
    sendAnnotationResponse({
      version: 1,
      requestId,
      status: "error",
      error: { code: "restricted_url", message: restricted }
    }, transport);
    return;
  }

  if (typeof tab.id !== "number") {
    sendAnnotationResponse({
      version: 1,
      requestId,
      status: "error",
      error: { code: "invalid_request", message: "Target tab missing id." }
    }, transport);
    return;
  }
  rememberAnnotatableTab(tab);

  let timeoutId: number | null = null;
  try {
    const initialBridge = await ensureAnnotationInjected(tab.id);
    rememberPopupAnnotationTarget(tab.id, initialBridge.bootId);
    timeoutId = startAnnotationTimeout(requestId, transport);
    annotationSessions.set(requestId, {
      requestId,
      tabId: tab.id,
      options: command.options,
      createdAt: Date.now(),
      timeoutId,
      transport
    });
    const startedBridge = await startAnnotationUi(tab.id, requestId, command.options, command.url);
    rememberPopupAnnotationTarget(tab.id, startedBridge.bootId);
  } catch (error) {
    if (timeoutId !== null) {
      clearTimeout(timeoutId);
    }
    annotationSessions.delete(requestId);
    clearPopupAnnotationTarget(tab.id);
    throw error instanceof Error ? error : new Error("Annotation injection failed");
  }
};

const cancelAnnotationSession = async (requestId: string, transport: AnnotationTransport): Promise<void> => {
  const session = annotationSessions.get(requestId);
  if (!session) {
    sendAnnotationResponse({
      version: 1,
      requestId,
      status: "cancelled",
      error: { code: "cancelled", message: "Annotation session not active." }
    }, transport);
    return;
  }
  clearTimeout(session.timeoutId);
  annotationSessions.delete(requestId);
  await sendAnnotationMessageToTab(session.tabId, { type: "annotation:cancel", requestId });
  sendAnnotationResponse({
    version: 1,
    requestId,
    status: "cancelled",
    error: { code: "cancelled", message: "Annotation cancelled." }
  }, session.transport);
};

const validatePayloadSize = (payload: AnnotationPayload): boolean => {
  const size = new TextEncoder().encode(JSON.stringify(payload)).length;
  return size <= ANNOTATION_MAX_PAYLOAD_BYTES;
};

const generateAnnotationRequestId = (): string => {
  return crypto.randomUUID();
};

const buildLastAnnotationMeta = (
  requestId: string,
  response: AnnotationResponse,
  hasFullPayloadInMemory: boolean,
  extras: Partial<Pick<PopupAnnotationMeta, "source" | "label" | "receipt">> = {}
): PopupAnnotationMeta => {
  const payload = response.payload;
  const annotationCount = payload ? payload.annotations.length : undefined;
  const screenshotCount = payload?.screenshots?.length ?? 0;
  return {
    requestId,
    status: response.status,
    error: response.error,
    receipt: extras.receipt ?? response.receipt,
    source: extras.source,
    label: extras.label,
    url: payload?.url,
    title: payload?.title,
    timestamp: payload?.timestamp,
    annotationCount,
    screenshotCount: payload ? screenshotCount : undefined,
    screenshotMode: payload?.screenshotMode,
    storedAt: Date.now(),
    hasScreenshots: screenshotCount > 0,
    hasFullPayloadInMemory
  };
};

const persistLastAnnotation = async (meta: PopupAnnotationMeta, payload: AnnotationPayload | null): Promise<void> => {
  await setStorage({
    [LAST_ANNOTATION_META_KEY]: meta,
    [LAST_ANNOTATION_PAYLOAD_KEY]: payload
  });
};

const persistAgentAnnotation = async (meta: PopupAnnotationMeta, payload: AnnotationPayload | null): Promise<void> => {
  await setStorage({
    [LAST_AGENT_ANNOTATION_META_KEY]: meta,
    [LAST_AGENT_ANNOTATION_PAYLOAD_KEY]: payload
  });
};

const loadPersistedLastAnnotation = async (): Promise<{
  meta: PopupAnnotationMeta | null;
  payload: AnnotationPayload | null;
}> => {
  const data = await new Promise<Record<string, unknown>>((resolve) => {
    chrome.storage.local.get([LAST_ANNOTATION_META_KEY, LAST_ANNOTATION_PAYLOAD_KEY], (items) => resolve(items));
  });
  const metaRecord = data[LAST_ANNOTATION_META_KEY];
  const payloadRecord = data[LAST_ANNOTATION_PAYLOAD_KEY];
  const meta = metaRecord && typeof metaRecord === "object" ? metaRecord as PopupAnnotationMeta : null;
  const payload = payloadRecord && typeof payloadRecord === "object" ? payloadRecord as AnnotationPayload : null;
  return { meta, payload };
};

const loadPersistedAgentAnnotation = async (): Promise<{
  meta: PopupAnnotationMeta | null;
  payload: AnnotationPayload | null;
}> => {
  const data = await new Promise<Record<string, unknown>>((resolve) => {
    chrome.storage.local.get([LAST_AGENT_ANNOTATION_META_KEY, LAST_AGENT_ANNOTATION_PAYLOAD_KEY], (items) => resolve(items));
  });
  const metaRecord = data[LAST_AGENT_ANNOTATION_META_KEY];
  const payloadRecord = data[LAST_AGENT_ANNOTATION_PAYLOAD_KEY];
  const meta = metaRecord && typeof metaRecord === "object" ? metaRecord as PopupAnnotationMeta : null;
  const payload = payloadRecord && typeof payloadRecord === "object" ? payloadRecord as AnnotationPayload : null;
  return { meta, payload };
};

const isAnnotationPayload = (value: unknown): value is AnnotationPayload => {
  if (!value || typeof value !== "object") {
    return false;
  }
  const payload = value as Record<string, unknown>;
  return typeof payload.url === "string"
    && typeof payload.timestamp === "string"
    && typeof payload.screenshotMode === "string"
    && Array.isArray(payload.annotations);
};

const storeAgentAnnotationPayload = async (
  payload: AnnotationPayload,
  source: AnnotationDispatchSource,
  label?: string,
  receipt?: AgentInboxReceipt
): Promise<PopupAnnotationMeta> => {
  if (!validatePayloadSize(payload)) {
    throw new Error("Annotation payload exceeded size limits.");
  }
  const response: AnnotationResponse = {
    version: 1,
    requestId: receipt?.receiptId ?? crypto.randomUUID(),
    status: "ok",
    payload,
    receipt
  };
  const meta = buildLastAnnotationMeta(response.requestId, response, true, {
    source,
    label: label?.trim().length ? label.trim() : formatDispatchSourceLabel(source),
    receipt
  });
  lastAgentAnnotationFull = { meta, payload };
  await persistAgentAnnotation({ ...meta, hasFullPayloadInMemory: false }, stripAnnotationPayloadScreenshots(payload));
  return meta;
};

const buildStoredOnlyReceipt = (
  payload: AnnotationPayload,
  source: AnnotationDispatchSource,
  label: string | undefined,
  reason: string
): AgentInboxReceipt => {
  const sanitizedPayload = stripAnnotationPayloadScreenshots(payload);
  return {
    receiptId: crypto.randomUUID(),
    deliveryState: "stored_only",
    storedFallback: true,
    reason,
    chatScopeKey: null,
    createdAt: new Date().toISOString(),
    itemCount: sanitizedPayload.annotations.length,
    byteLength: new TextEncoder().encode(JSON.stringify(sanitizedPayload)).length,
    source,
    label: label?.trim().length ? label.trim() : formatDispatchSourceLabel(source)
  };
};

const enqueueAgentPayload = async (
  payload: AnnotationPayload,
  source: AnnotationDispatchSource,
  label?: string
): Promise<AgentInboxReceipt> => {
  const response = await connection.sendAnnotationCommand({
    version: 1,
    requestId: crypto.randomUUID(),
    command: "store_agent_payload",
    payload,
    source,
    label
  });
  if (response.status !== "ok" || !response.receipt) {
    throw new Error(response.error?.code ?? response.error?.message ?? "relay_unavailable");
  }
  return response.receipt;
};

const loadAgentAnnotationPayload = async (includeScreenshots: boolean): Promise<AnnotationResponse> => {
  if (includeScreenshots && lastAgentAnnotationFull) {
    return {
      version: 1,
      requestId: crypto.randomUUID(),
      status: "ok",
      payload: lastAgentAnnotationFull.payload
    };
  }
  if (lastAgentAnnotationFull) {
    return {
      version: 1,
      requestId: crypto.randomUUID(),
      status: "ok",
      payload: stripAnnotationPayloadScreenshots(lastAgentAnnotationFull.payload)
    };
  }
  const stored = await loadPersistedAgentAnnotation();
  if (stored.payload) {
    return {
      version: 1,
      requestId: crypto.randomUUID(),
      status: "ok",
      payload: stored.payload
    };
  }
  return {
    version: 1,
    requestId: crypto.randomUUID(),
    status: "error",
    error: { code: "payload_unavailable", message: "No agent-dispatched annotation payload available." }
  };
};

async function handleNativePortMessage(payload: unknown): Promise<void> {
  if (!payload || typeof payload !== "object") {
    return;
  }
  const record = payload as Record<string, unknown>;
  if (record.type === "annotationCommand") {
    await handleNativeAnnotationCommand(record as RelayAnnotationCommand);
  }
}

const handleRelayAnnotationCommand = async (
  command: RelayAnnotationCommand,
  transport: AnnotationTransport = "relay"
): Promise<void> => {
  const payload = command.payload;
  if (!payload || payload.version !== 1 || typeof payload.requestId !== "string") {
    sendAnnotationResponse({
      version: 1,
      requestId: payload?.requestId ?? "unknown",
      status: "error",
      error: { code: "invalid_request", message: "Invalid annotation command." }
    }, transport);
    return;
  }

  if (payload.command === "cancel") {
    await cancelAnnotationSession(payload.requestId, transport);
    return;
  }

  if (payload.command === "fetch_stored") {
    const stored = await loadAgentAnnotationPayload(payload.options?.includeScreenshots === true);
    sendAnnotationResponse({
      ...stored,
      requestId: payload.requestId
    }, transport);
    return;
  }

  try {
    await startAnnotationSession(payload, transport);
    sendAnnotationEvent({
      version: 1,
      requestId: payload.requestId,
      event: "ready",
      message: "Annotation session started."
    }, transport);
  } catch (error) {
    const detail = error instanceof Error ? error.message : "Annotation start failed.";
    sendAnnotationResponse({
      version: 1,
      requestId: payload.requestId,
      status: "error",
      error: { code: "injection_failed", message: detail }
    }, transport);
  }
};

const handleNativeAnnotationCommand = async (command: RelayAnnotationCommand): Promise<void> => {
  await handleRelayAnnotationCommand(command, "native");
};

const finalizeAnnotationSession = (requestId: string): AnnotationSession | null => {
  const session = annotationSessions.get(requestId);
  if (!session) return null;
  clearTimeout(session.timeoutId);
  annotationSessions.delete(requestId);
  return session;
};

const handleAnnotationComplete = (requestId: string, payload: AnnotationPayload): void => {
  const session = finalizeAnnotationSession(requestId);
  if (!session) {
    return;
  }
  if (!validatePayloadSize(payload)) {
    const response: AnnotationResponse = {
      version: 1,
      requestId,
      status: "error",
      error: { code: "payload_too_large", message: "Annotation payload exceeded size limits." }
    };
    const meta = buildLastAnnotationMeta(requestId, response, false);
    lastAnnotationFull = null;
    persistLastAnnotation(meta, null).catch((error) => {
      logError("annotation.persist_payload_too_large", error, { code: "annotation_persist_failed" });
    });
    sendAnnotationResponse(response, session.transport);
    return;
  }
  const response: AnnotationResponse = {
    version: 1,
    requestId,
    status: "ok",
    payload
  };
  const meta = buildLastAnnotationMeta(requestId, response, true);
  lastAnnotationFull = { meta, payload };
  const storageMeta = { ...meta, hasFullPayloadInMemory: false };
  const sanitizedPayload = stripAnnotationPayloadScreenshots(payload);
  persistLastAnnotation(storageMeta, sanitizedPayload).catch((error) => {
    logError("annotation.persist_sanitized_payload", error, { code: "annotation_persist_failed" });
  });
  sendAnnotationResponse(response, session.transport);
};

const handleAnnotationError = (requestId: string, error: { code: AnnotationErrorCode; message: string }): void => {
  const session = finalizeAnnotationSession(requestId);
  if (!session) return;
  const response: AnnotationResponse = {
    version: 1,
    requestId,
    status: "error",
    error
  };
  const meta = buildLastAnnotationMeta(requestId, response, false);
  lastAnnotationFull = null;
  persistLastAnnotation(meta, null).catch((error) => {
    logError("annotation.persist_error_meta", error, { code: "annotation_persist_failed" });
  });
  sendAnnotationResponse(response, session.transport);
};

const handleAnnotationCancelled = (requestId: string): void => {
  const session = finalizeAnnotationSession(requestId);
  if (!session) return;
  const response: AnnotationResponse = {
    version: 1,
    requestId,
    status: "cancelled",
    error: { code: "cancelled", message: "Annotation cancelled." }
  };
  const meta = buildLastAnnotationMeta(requestId, response, false);
  lastAnnotationFull = null;
  persistLastAnnotation(meta, null).catch((error) => {
    logError("annotation.persist_cancel_meta", error, { code: "annotation_persist_failed" });
  });
  sendAnnotationResponse(response, session.transport);
};

const captureVisibleTab = async (tab: chrome.tabs.Tab): Promise<string> => {
  return await new Promise((resolve, reject) => {
    chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" }, (dataUrl) => {
      const lastError = chrome.runtime.lastError;
      if (lastError) {
        reject(new Error(lastError.message));
        return;
      }
      if (!dataUrl) {
        reject(new Error("Capture failed"));
        return;
      }
      resolve(dataUrl);
    });
  });
};

const fetchTokenFromPlugin = async (
  port: number
): Promise<{ token: string; instanceId: string | null; epoch: number | null } | null> => {
  try {
    const response = await fetch(`http://127.0.0.1:${port}/pair`, {
      method: "GET",
      headers: { "Accept": "application/json" }
    });
    if (!response.ok) {
      return null;
    }
    const data = await response.json();
    if (typeof data.token !== "string") {
      return null;
    }
    return {
      token: data.token,
      instanceId: typeof data.instanceId === "string" ? data.instanceId : null,
      epoch: parseEpoch(data.epoch)
    };
  } catch (error) {
    logError("relay.token_fetch", error, { code: "relay_pair_fetch_failed", extra: { port } });
    return null;
  }
};

const clearStoredRelayCredentials = async (): Promise<void> => {
  await setStorage({
    pairingToken: null,
    tokenEpoch: null
  });
};

const attemptAutoConnect = async (): Promise<void> => {
  const data = await new Promise<Record<string, unknown>>((resolve) => {
    chrome.storage.local.get(
      [
        "autoConnect",
        "autoPair",
        "pairingEnabled",
        "pairingToken",
        "relayPort",
        "relayInstanceId",
        "relayEpoch",
        "tokenEpoch",
        "nativeEnabled"
      ],
      (items) => {
        resolve(items);
      }
    );
  });

  const autoConnect = typeof data.autoConnect === "boolean" ? data.autoConnect : DEFAULT_AUTO_CONNECT;
  autoConnectEnabled = autoConnect;
  if (!autoConnect || connection.getStatus() === "connected") {
    clearRetry();
    if (!autoConnect) {
      clearWatchdog();
    } else {
      scheduleWatchdog();
    }
    return;
  }
  scheduleWatchdog();

  const autoPair = typeof data.autoPair === "boolean" ? data.autoPair : DEFAULT_AUTO_PAIR;
  const pairingEnabled = typeof data.pairingEnabled === "boolean" ? data.pairingEnabled : DEFAULT_PAIRING_ENABLED;
  const storedRelayPort = parsePort(data.relayPort) ?? DEFAULT_RELAY_PORT;
  let storedPairingToken = typeof data.pairingToken === "string" ? data.pairingToken : null;
  nativeEnabled = typeof data.nativeEnabled === "boolean" ? data.nativeEnabled : DEFAULT_NATIVE_ENABLED;
  if (typeof data.nativeEnabled !== "boolean") {
    await setStorage({ nativeEnabled });
  }
  if (connection.isReconnectSuppressed()) {
    const activeExtensionClient = await relayHasActiveExtensionClient(storedRelayPort);
    if (activeExtensionClient) {
      setStatusNoteOverride("Another extension client is connected. Retrying when the relay is free.");
      scheduleRetry();
      return;
    }
    setStatusNoteOverride("Relay no longer has an active extension client. Reconnecting.");
  }

  if (autoPair && pairingEnabled) {
    let config = await fetchRelayConfig(DEFAULT_DISCOVERY_PORT);
    if (!config && storedRelayPort !== DEFAULT_DISCOVERY_PORT) {
      config = await fetchRelayConfig(storedRelayPort);
    }
    const storedRelayEpoch = parseEpoch(data.relayEpoch);
    const storedRelayInstanceId = typeof data.relayInstanceId === "string" ? data.relayInstanceId : null;
    const storedTokenEpoch = parseEpoch(data.tokenEpoch);

    if (!config) {
      setStatusNoteOverride("Relay config unreachable. Start the daemon and retry.");
      scheduleRetry();
      return;
    }

    const relayPort = config.relayPort ?? storedRelayPort;
    const configEpoch = config.epoch ?? null;
    const hasEpoch = config.epoch !== null;
    if (config.relayPort) {
      await setStorage({
        relayPort: config.relayPort,
        relayInstanceId: config.instanceId,
        relayEpoch: config.epoch
      });
    }

    if (hasEpoch && storedRelayEpoch !== null && storedRelayEpoch !== configEpoch) {
      await clearStoredRelayCredentials();
      storedPairingToken = null;
      setStatusNoteOverride("Relay restarted. Refresh the connection.");
    }
    if (config.instanceId && storedRelayInstanceId && config.instanceId !== storedRelayInstanceId) {
      await clearStoredRelayCredentials();
      storedPairingToken = null;
      setStatusNoteOverride("Relay instance mismatch. Open the popup and click Connect.");
    }
    if (hasEpoch && storedTokenEpoch !== null && storedTokenEpoch !== configEpoch) {
      await setStorage({ pairingToken: null, tokenEpoch: null });
      storedPairingToken = null;
    }
    if (hasEpoch && storedTokenEpoch === null && storedPairingToken) {
      await setStorage({ pairingToken: null, tokenEpoch: null });
      storedPairingToken = null;
    }

    const pairingRequired = config.pairingRequired ?? true;
    if (pairingRequired) {
      if (!storedPairingToken) {
        const fetched = await fetchTokenFromPlugin(relayPort);
        if (!fetched) {
          setStatusNoteOverride("Auto-pair failed. Start the daemon and retry.");
          scheduleRetry();
          return;
        }
        if (config.instanceId && fetched.instanceId && config.instanceId !== fetched.instanceId) {
          console.warn("[opendevbrowser] Relay instance mismatch during auto-pair. Retrying later.");
          setStatusNoteOverride("Relay instance mismatch. Open the popup and click Connect.");
          scheduleRetry();
          return;
        }
        const tokenEpoch = fetched.epoch ?? configEpoch;
        await setStorage({ pairingToken: fetched.token, tokenEpoch });
      }
    }
  }

  await connection.connect();
  if (connection.getStatus() !== "connected") {
    const nativeConnected = await attemptNativeConnect();
    if (nativeConnected) {
      setStatusNoteOverride("Connected via native host.");
      clearRetry();
      return;
    }
    if (nativeEnabled) {
      setStatusNoteOverride(buildNativeHealthNote(nativePort.getHealth()));
    }
    scheduleRetry();
    return;
  }
  nativePort.disconnect();
  setStatusNoteOverride(null);
  clearRetry();
};

const autoConnect = async () => {
  if (autoConnectInFlight) {
    return;
  }
  autoConnectInFlight = true;
  try {
    await attemptAutoConnect();
  } catch (error) {
    logError("auto_connect.attempt", error, { code: "auto_connect_failed" });
    connection.disconnect();
    nativePort.disconnect();
  } finally {
    autoConnectInFlight = false;
  }
};

connection.onStatus((status) => {
  const shouldCleanupRelayDisconnect = status === "disconnected" && lastRelayCleanupStatus !== "disconnected";
  lastRelayCleanupStatus = status;
  const effectiveStatus =
    status === "connected" ? "connected" : (nativeEnabled && nativePort.isConnected()) ? "connected" : "disconnected";
  updateBadge(effectiveStatus);
  void refreshBadgeFromBackgroundStatus();
  if (status === "connected") {
    nativePort.disconnect();
    setStatusNoteOverride(null);
    clearRetry();
  }
  if (status === "disconnected") {
    if (shouldCleanupRelayDisconnect) {
      for (const session of annotationSessions.values()) {
        clearTimeout(session.timeoutId);
      }
      annotationSessions.clear();
      opsRuntime.handleRelayDisconnected();
    }
    if (nativePort.isConnected()) {
      return;
    }
    if (connection.isReconnectSuppressed() && autoConnectEnabled) {
      scheduleRetry();
      return;
    }
    if (connection.isReconnectSuppressed()) {
      clearRetry();
      return;
    }
    if (autoConnectEnabled) {
      scheduleRetry();
    }
  }
});
updateBadge(getEffectiveStatus());

chrome.runtime.onStartup.addListener(() => {
  autoConnect().catch((error) => {
    logError("auto_connect.startup", error, { code: "auto_connect_failed" });
  });
});

chrome.runtime.onInstalled.addListener(() => {
  autoConnect().catch((error) => {
    logError("auto_connect.installed", error, { code: "auto_connect_failed" });
  });
});

if (chrome.alarms?.onAlarm) {
  chrome.alarms.onAlarm.addListener((alarm) => {
    if (alarm.name === RETRY_ALARM_NAME) {
      retryScheduled = false;
      autoConnect().catch((error) => {
        logError("auto_connect.alarm", error, { code: "auto_connect_failed" });
      });
    }
    if (alarm.name === WATCHDOG_ALARM_NAME) {
      autoConnect().catch((error) => {
        logError("auto_connect.watchdog", error, { code: "auto_connect_failed" });
      });
    }
  });
}

if (chrome.commands?.onCommand) {
  chrome.commands.onCommand.addListener((command) => {
    if (command === "toggle-annotation") {
      toggleAnnotationUi().catch((error) => {
        logError("annotation.toggle", error, { code: "annotation_toggle_failed" });
      });
    }
  });
}

autoConnect().catch((error) => {
  logError("auto_connect.startup", error, { code: "auto_connect_failed" });
});

void getActiveTab()
  .then((tab) => {
    rememberAnnotatableTab(tab);
  })
  .catch((error) => {
    logError("annotation.initial_tab", error, { code: "annotation_tab_resolution_failed" });
  });

chrome.tabs.onActivated.addListener((activeInfo) => {
  void getTab(activeInfo.tabId)
    .then((tab) => {
      rememberAnnotatableTab(tab);
    })
    .catch((error) => {
      logError("annotation.tab_activated", error, { code: "annotation_tab_resolution_failed", extra: { tabId: activeInfo.tabId } });
    });
});

chrome.tabs.onUpdated.addListener((tabId, _changeInfo, tab) => {
  if (!tab.active && tabId !== lastAnnotatableTabId) {
    return;
  }
  rememberAnnotatableTab(tab);
});

chrome.tabs.onRemoved.addListener((tabId) => {
  if (lastAnnotatableTabId === tabId) {
    lastAnnotatableTabId = null;
    void persistAnnotatableTabId(null).catch((error) => {
      logError("annotation.persist_tab", error, { code: "annotation_tab_persist_failed", extra: { tabId: null } });
    });
  }
  clearPopupAnnotationTarget(tabId);
});

chrome.storage.onChanged.addListener((changes, area) => {
  if (area !== "local") {
    return;
  }
  if (changes.nativeEnabled) {
    nativeEnabled = changes.nativeEnabled.newValue === true;
    if (!nativeEnabled) {
      nativePort.disconnect();
    }
    updateBadge(getEffectiveStatus());
    void refreshBadgeFromBackgroundStatus();
  }
  if (changes.autoConnect) {
    autoConnectEnabled =
      typeof changes.autoConnect.newValue === "boolean" ? changes.autoConnect.newValue : DEFAULT_AUTO_CONNECT;
  }
  if (changes.autoConnect?.newValue === true) {
    scheduleWatchdog();
    autoConnect().catch((error) => {
      logError("auto_connect.setting", error, { code: "auto_connect_failed" });
    });
  } else if (changes.autoConnect?.newValue === false) {
    clearRetry();
    clearWatchdog();
  }
  if (changes.pairingToken) {
    autoConnect().catch((error) => {
      logError("auto_connect.pairing_token", error, { code: "auto_connect_failed" });
    });
  }
});

chrome.runtime.onMessage.addListener((message: PopupMessage | ContentScriptMessage, sender, sendResponse) => {
  const respond = (status: BackgroundMessage) => {
    sendResponse(status);
  };

    if (message.type === "status") {
      (async () => {
        const statusMessage = await buildStatusMessage();
        updateBadge(deriveBadgeTone(statusMessage.status, statusMessage.relayHealth ?? null));
        respond(statusMessage);
      })().catch((error) => {
        logError("popup.status", error, { code: "status_failed" });
        respond({
        type: "status",
        status: connection.getStatus(),
        note: "Background unavailable"
      });
    });
    return true;
  }

    if (message.type === "connect") {
      (async () => {
        await connection.connect();
        if (connection.getStatus() !== "connected") {
          await attemptNativeConnect();
        }
        const statusMessage = await buildStatusMessage();
        updateBadge(deriveBadgeTone(statusMessage.status, statusMessage.relayHealth ?? null));
        respond(statusMessage);
      })().catch((error) => {
        logError("popup.connect", error, { code: "connect_failed" });
        connection.disconnect();
      nativePort.disconnect();
      respond({
        type: "status",
        status: "disconnected",
        note: "Connect failed"
      });
    });
    return true;
  }

    if (message.type === "disconnect") {
      (async () => {
        await connection.disconnect();
        nativePort.disconnect();
        connection.clearLastError();
        const statusMessage = await buildStatusMessage();
        updateBadge(deriveBadgeTone(statusMessage.status, statusMessage.relayHealth ?? null));
        respond(statusMessage);
      })().catch((error) => {
        logError("popup.disconnect", error, { code: "disconnect_failed" });
        connection.disconnect();
      nativePort.disconnect();
      connection.clearLastError();
      respond({
        type: "status",
        status: "disconnected",
        note: "Disconnect failed"
      });
    });
    return true;
  }

  if (message.type === "annotation:start") {
    (async () => {
      const requestId = generateAnnotationRequestId();
      const response: PopupAnnotationStartResponse = {
        type: "annotation:startResult",
        requestId,
        ok: false
      };

      try {
        const active = await getPopupAnnotationTab(message.tabId);
        if (!active) {
          response.error = { code: "invalid_request", message: "No active tab available." };
          sendResponse(response);
          return;
        }
        const restricted = isRestrictedTab(active);
        if (restricted) {
          response.error = { code: "restricted_url", message: restricted };
          sendResponse(response);
          return;
        }
        if (typeof active.id !== "number") {
          response.error = { code: "invalid_request", message: "Target tab missing id." };
          sendResponse(response);
          return;
        }
        await startAnnotationSession({
          version: 1,
          requestId,
          command: "start",
          tabId: active.id,
          options: message.options
        }, "popup");
        if (!annotationSessions.has(requestId)) {
          response.error = { code: "injection_failed", message: "Annotation session failed to start." };
          sendResponse(response);
          return;
        }
        response.ok = true;
        sendResponse(response);
      } catch (error) {
        const detail = error instanceof Error ? error.message : "Annotation start failed.";
        response.error = { code: "injection_failed", message: detail };
        sendResponse(response);
      }
    })();
    return true;
  }

  if (message.type === "annotation:lastMeta") {
    (async () => {
      if (lastAnnotationFull) {
        const meta = { ...lastAnnotationFull.meta, hasFullPayloadInMemory: true };
        const response: PopupAnnotationLastMetaResponse = { type: "annotation:lastMetaResult", meta };
        sendResponse(response);
        return;
      }
      const stored = await loadPersistedLastAnnotation();
      const meta = stored.meta ? { ...stored.meta, hasFullPayloadInMemory: false } : null;
      const response: PopupAnnotationLastMetaResponse = { type: "annotation:lastMetaResult", meta };
      sendResponse(response);
    })().catch(() => {
      const response: PopupAnnotationLastMetaResponse = { type: "annotation:lastMetaResult", meta: null };
      sendResponse(response);
    });
    return true;
  }

  if (message.type === "annotation:probe") {
    (async () => {
      const result = await probeAnnotationInjected(message.tabId);
      sendResponse({ type: "annotation:probeResult", ...result });
    })().catch((error) => {
      logError("annotation.probe", error, { code: "annotation_probe_failed" });
      sendResponse({ type: "annotation:probeResult", injected: false, detail: "Probe failed." });
    });
    return true;
  }

  if (message.type === "annotation:getPayload") {
    (async () => {
      if (message.includeScreenshots && lastAnnotationFull) {
        const response: PopupAnnotationGetPayloadResponse = {
          type: "annotation:payloadResult",
          payload: lastAnnotationFull.payload,
          meta: { ...lastAnnotationFull.meta, hasFullPayloadInMemory: true },
          source: "memory"
        };
        sendResponse(response);
        return;
      }

      const stored = await loadPersistedLastAnnotation();
      const storedMeta = stored.meta ? { ...stored.meta, hasFullPayloadInMemory: false } : null;

      if (message.includeScreenshots) {
        const response: PopupAnnotationGetPayloadResponse = {
          type: "annotation:payloadResult",
          payload: stored.payload,
          meta: storedMeta,
          source: stored.payload ? "storage" : "none",
          warning: "Full payload not available; screenshots may have been dropped."
        };
        sendResponse(response);
        return;
      }

      if (lastAnnotationFull) {
        const response: PopupAnnotationGetPayloadResponse = {
          type: "annotation:payloadResult",
          payload: stripAnnotationPayloadScreenshots(lastAnnotationFull.payload),
          meta: { ...lastAnnotationFull.meta, hasFullPayloadInMemory: true },
          source: "memory"
        };
        sendResponse(response);
        return;
      }

      if (stored.payload) {
        const response: PopupAnnotationGetPayloadResponse = {
          type: "annotation:payloadResult",
          payload: stored.payload,
          meta: storedMeta,
          source: "storage"
        };
        sendResponse(response);
        return;
      }

      const response: PopupAnnotationGetPayloadResponse = {
        type: "annotation:payloadResult",
        payload: null,
        meta: storedMeta,
        source: "none",
        warning: "No stored annotation payload."
      };
      sendResponse(response);
    })().catch(() => {
      const response: PopupAnnotationGetPayloadResponse = {
        type: "annotation:payloadResult",
        payload: null,
        meta: null,
        source: "none",
        warning: "Background unavailable."
      };
      sendResponse(response);
    });
    return true;
  }

  if (message.type === "annotation:sendPayload") {
    (async () => {
      if (!isAnnotationPayload(message.payload)) {
        const response: PopupAnnotationSendPayloadResponse = {
          type: "annotation:sendPayloadResult",
          ok: false,
          meta: null,
          receipt: null,
          error: { code: "invalid_request", message: "Invalid annotation payload." }
        };
        sendResponse(response);
        return;
      }
      const source = message.source ?? "popup_all";
      let receipt: AgentInboxReceipt;
      try {
        receipt = await enqueueAgentPayload(message.payload, source, message.label);
      } catch (error) {
        const reason = error instanceof Error && error.message.trim().length > 0
          ? error.message.trim()
          : "relay_unavailable";
        receipt = buildStoredOnlyReceipt(message.payload, source, message.label, reason);
      }
      let meta: PopupAnnotationMeta;
      try {
        meta = await storeAgentAnnotationPayload(
          message.payload,
          source,
          message.label,
          receipt
        );
      } catch (error) {
        if (receipt.deliveryState !== "delivered") {
          throw error;
        }
        const response: AnnotationResponse = {
          version: 1,
          requestId: receipt.receiptId,
          status: "ok",
          payload: message.payload,
          receipt
        };
        meta = buildLastAnnotationMeta(receipt.receiptId, response, true, {
          source,
          label: message.label?.trim().length ? message.label.trim() : formatDispatchSourceLabel(source),
          receipt
        });
        logError("annotation.store_agent_payload_optional", error, { code: "annotation_persist_failed" });
      }
      const response: PopupAnnotationSendPayloadResponse = {
        type: "annotation:sendPayloadResult",
        ok: true,
        meta: { ...meta, hasFullPayloadInMemory: true },
        receipt
      };
      sendResponse(response);
    })().catch((error) => {
      const response: PopupAnnotationSendPayloadResponse = {
        type: "annotation:sendPayloadResult",
        ok: false,
        meta: null,
        receipt: null,
        error: {
          code: "payload_too_large",
          message: error instanceof Error ? error.message : "Annotation dispatch failed."
        }
      };
      sendResponse(response);
    });
    return true;
  }

  if (message.type === "annotation:capture") {
    (async () => {
      const tab = sender.tab;
      if (!tab) {
        sendResponse({ ok: false, error: "No tab for capture" });
        return;
      }
      try {
        const dataUrl = await captureVisibleTab(tab);
        sendResponse({ ok: true, dataUrl });
      } catch (error) {
        sendResponse({ ok: false, error: error instanceof Error ? error.message : "Capture failed" });
      }
    })();
    return true;
  }

  if (message.type === "annotation:complete") {
    handleAnnotationComplete(message.requestId, message.payload);
    sendResponse({ ok: true });
    return true;
  }

  if (message.type === "annotation:cancelled") {
    handleAnnotationCancelled(message.requestId);
    sendResponse({ ok: true });
    return true;
  }

  if (message.type === "annotation:error") {
    handleAnnotationError(message.requestId, message.error);
    sendResponse({ ok: true });
    return true;
  }

  return false;
});

chrome.runtime.onConnect.addListener((port) => {
  if (port.name === "canvas-page") {
    canvasRuntime.attachPort(port);
  }
});
