/**
 * @fileoverview Offscreen Keepalive
 * @description Keeps the MV3 service worker alive using an Offscreen Document + Port heartbeat.
 *
 * Architecture:
 * - Offscreen connects to Background (Service Worker) via a named Port.
 * - Offscreen sends periodic `keepalive.ping` messages while keepalive is enabled.
 * - Background replies with `keepalive.pong` to confirm the channel is alive.
 *
 * Contract:
 * - After `stop`, keepalive must fully stop: no ping loop, no Port, and no reconnection attempts.
 * - After `start`, keepalive must (re)connect if needed and resume the ping loop.
 */

import {
  RR_V3_KEEPALIVE_PORT_NAME,
  DEFAULT_KEEPALIVE_PING_INTERVAL_MS,
  type KeepaliveMessage,
} from '@/common/rr-v3-keepalive-protocol';

// ==================== Runtime Control Protocol ====================

const KEEPALIVE_CONTROL_MESSAGE_TYPE = 'rr_v3_keepalive.control' as const;

type KeepaliveControlCommand = 'start' | 'stop';

interface KeepaliveControlMessage {
  type: typeof KEEPALIVE_CONTROL_MESSAGE_TYPE;
  command: KeepaliveControlCommand;
}

function isKeepaliveControlMessage(value: unknown): value is KeepaliveControlMessage {
  if (!value || typeof value !== 'object') return false;
  const v = value as Record<string, unknown>;
  if (v.type !== KEEPALIVE_CONTROL_MESSAGE_TYPE) return false;
  return v.command === 'start' || v.command === 'stop';
}

// ==================== State ====================

let initialized = false;
let keepalivePort: chrome.runtime.Port | null = null;
let pingTimer: ReturnType<typeof setInterval> | null = null;
/** Whether keepalive is desired (set by start/stop commands from Background) */
let keepaliveDesired = false;
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;

// ==================== Type Guards ====================

/**
 * Type guard for KeepaliveMessage.
 */
function isKeepaliveMessage(value: unknown): value is KeepaliveMessage {
  if (!value || typeof value !== 'object') return false;
  const v = value as Record<string, unknown>;

  const type = v.type;
  if (
    type !== 'keepalive.ping' &&
    type !== 'keepalive.pong' &&
    type !== 'keepalive.start' &&
    type !== 'keepalive.stop'
  ) {
    return false;
  }

  return typeof v.timestamp === 'number' && Number.isFinite(v.timestamp);
}

// ==================== Port Management ====================

/**
 * Schedule a reconnect attempt to maintain the Port connection.
 * Only reconnect while keepalive is desired.
 */
function scheduleReconnect(delayMs = 1000): void {
  if (!initialized) return;
  if (!keepaliveDesired) return;
  if (reconnectTimer) return;

  reconnectTimer = setTimeout(() => {
    reconnectTimer = null;
    if (!initialized) return;
    if (!keepaliveDesired) return;
    if (!keepalivePort) {
      console.log('[rr-keepalive] Attempting scheduled reconnect...');
      keepalivePort = connectToBackground();
    }
  }, delayMs);
}

/**
 * Create a Port connection to Background.
 */
function connectToBackground(): chrome.runtime.Port | null {
  if (typeof chrome === 'undefined' || !chrome.runtime?.connect) {
    console.warn('[rr-keepalive] chrome.runtime.connect not available');
    return null;
  }

  try {
    const port = chrome.runtime.connect({ name: RR_V3_KEEPALIVE_PORT_NAME });

    port.onMessage.addListener((msg: unknown) => {
      if (!isKeepaliveMessage(msg)) return;

      if (msg.type === 'keepalive.start') {
        console.log('[rr-keepalive] Received start command via Port');
        startPingLoop();
      } else if (msg.type === 'keepalive.stop') {
        console.log('[rr-keepalive] Received stop command via Port');
        stopPingLoop();
      } else if (msg.type === 'keepalive.pong') {
        // Background replied to our ping.
        console.debug('[rr-keepalive] Received pong');
      }
    });

    port.onDisconnect.addListener(() => {
      console.log('[rr-keepalive] Port disconnected');
      keepalivePort = null;
      // Only reconnect if keepalive is still desired.
      scheduleReconnect(1000);
    });

    console.log('[rr-keepalive] Connected to background');
    return port;
  } catch (e) {
    console.warn('[rr-keepalive] Failed to connect:', e);
    return null;
  }
}

// ==================== Ping Loop ====================

/**
 * Send a ping message to Background.
 */
function sendPing(): void {
  if (!keepalivePort) {
    keepalivePort = connectToBackground();
  }

  if (!keepalivePort) return;

  const msg: KeepaliveMessage = {
    type: 'keepalive.ping',
    timestamp: Date.now(),
  };

  try {
    keepalivePort.postMessage(msg);
    console.debug('[rr-keepalive] Sent ping');
  } catch (e) {
    console.warn('[rr-keepalive] Failed to send ping:', e);
    keepalivePort = null;
    scheduleReconnect(1000);
  }
}

/**
 * Start the ping loop.
 */
function startPingLoop(): void {
  if (pingTimer) return;

  keepaliveDesired = true;

  // Ensure we have a Port connection.
  if (!keepalivePort) {
    keepalivePort = connectToBackground();
  }

  // Send one ping immediately.
  sendPing();

  // Start the interval timer.
  pingTimer = setInterval(() => {
    sendPing();
  }, DEFAULT_KEEPALIVE_PING_INTERVAL_MS);

  console.log(
    `[rr-keepalive] Ping loop started (interval=${DEFAULT_KEEPALIVE_PING_INTERVAL_MS}ms)`,
  );
}

/**
 * Stop the ping loop.
 * This must fully stop keepalive: no timer, no Port, and no reconnection attempts.
 */
function stopPingLoop(): void {
  keepaliveDesired = false;

  if (pingTimer) {
    clearInterval(pingTimer);
    pingTimer = null;
  }

  if (reconnectTimer) {
    clearTimeout(reconnectTimer);
    reconnectTimer = null;
  }

  // Disconnect the Port to fully stop keepalive.
  if (keepalivePort) {
    try {
      keepalivePort.disconnect();
    } catch {
      // Ignore
    }
    keepalivePort = null;
  }

  console.log('[rr-keepalive] Ping loop stopped');
}

// ==================== Public API ====================

/**
 * Initialize keepalive control handlers.
 * @description Registers the runtime control listener and waits for start/stop commands.
 */
export function initKeepalive(): void {
  if (initialized) return;
  initialized = true;

  // Check Chrome API availability.
  if (typeof chrome === 'undefined' || !chrome.runtime?.onMessage) {
    console.warn('[rr-keepalive] chrome.runtime.onMessage not available');
    return;
  }

  // Listen for runtime control messages from Background.
  // This allows Background to send start/stop even when Port is not connected.
  chrome.runtime.onMessage.addListener((msg: unknown, _sender, sendResponse) => {
    if (!isKeepaliveControlMessage(msg)) return;

    if (msg.command === 'start') {
      console.log('[rr-keepalive] Received runtime start command');
      startPingLoop();
    } else {
      console.log('[rr-keepalive] Received runtime stop command');
      stopPingLoop();
    }

    try {
      sendResponse({ ok: true });
    } catch {
      // Ignore
    }
  });

  // Also establish initial Port connection for backwards compatibility.
  keepalivePort = connectToBackground();

  console.log('[rr-keepalive] Keepalive initialized');
}

/**
 * Check whether keepalive is active.
 */
export function isKeepaliveActive(): boolean {
  return keepaliveDesired && pingTimer !== null && keepalivePort !== null;
}

/**
 * Get the active port count (for debugging).
 * @deprecated Use isKeepaliveActive() instead
 */
export function getActivePortCount(): number {
  return keepalivePort ? 1 : 0;
}

// Re-export for backwards compatibility
export {
  RR_V3_KEEPALIVE_PORT_NAME,
  type KeepaliveMessage,
} from '@/common/rr-v3-keepalive-protocol';
