import browser from 'webextension-polyfill';
import type { ConversationState, ExecutorCommand } from '@platform-modules/chatgpt-orchestrator-protocol';
import type { BackendContinuityView } from './ui-model.js';

export interface ExtensionSettings {
  endpoint: string;
  token: string;
  executorId: string;
  maxConcurrentLaunches: number;
  maxManagedWorkers: number;
  overlayVisible: boolean;
}

export interface ManagedBinding {
  workerId: string;
  tabId: number;
  conversationId: string | null;
  url: string;
  state: ConversationState;
  updatedAt: string;
}

export type JournalPhase = 'starting' | 'tab-created' | 'submission-started' | 'submitted' | 'completed' | 'failed';

export interface JournalEntry {
  command: ExecutorCommand;
  phase: JournalPhase;
  result: Record<string, unknown> | null;
  error: string | null;
  updatedAt: string;
}

export interface LaunchThrottleState {
  lastLaunchStartedAt: number | null;
  rateLimitUntil: number | null;
  rateLimitStrike: number;
}

export interface RuntimeStatus {
  state: 'config-required' | 'connecting' | 'connected' | 'disconnected' | 'error';
  detail: string;
  updatedAt: string;
}

const SETTINGS_KEY = 'settings';
const BINDINGS_KEY = 'managedBindings';
const JOURNAL_KEY = 'commandJournal';
const STATUS_KEY = 'runtimeStatus';
const CONTINUITY_VIEWS_KEY = 'continuityViews';
const LAUNCH_THROTTLE_KEY = 'launchThrottle';

let mutationTail: Promise<void> = Promise.resolve();

async function serializeMutation<T>(operation: () => Promise<T>): Promise<T> {
  let release!: () => void;
  const previous = mutationTail;
  mutationTail = new Promise<void>((resolve) => { release = resolve; });
  await previous;
  try {
    return await operation();
  } finally {
    release();
  }
}

async function awaitMutations(): Promise<void> {
  await mutationTail;
}


function now(): string { return new Date().toISOString(); }
function defaultEndpoint(value: string | undefined): string { return value?.trim() || 'ws://127.0.0.1:8765'; }
function defaultToken(value: string | undefined): string { return value?.trim() || __DEFAULT_EXECUTOR_TOKEN__; }
function normalizeMaxManagedWorkers(value: number | undefined): number {
  if (value === undefined || value === 4) return 64;
  if (!Number.isInteger(value)) return 64;
  return Math.max(1, Math.min(256, value));
}

export async function loadSettings(): Promise<ExtensionSettings> {
  const raw = await browser.storage.local.get(SETTINGS_KEY);
  const existing = raw[SETTINGS_KEY] as Partial<ExtensionSettings> | undefined;
  let executorId = existing?.executorId;
  if (!executorId) {
    executorId = `browser-${crypto.randomUUID()}`;
    await serializeMutation(() => browser.storage.local.set({ [SETTINGS_KEY]: { endpoint: defaultEndpoint(existing?.endpoint), token: defaultToken(existing?.token), executorId, maxConcurrentLaunches: 1, maxManagedWorkers: normalizeMaxManagedWorkers(existing?.maxManagedWorkers), overlayVisible: existing?.overlayVisible ?? true } }));
  }
  return {
    endpoint: defaultEndpoint(existing?.endpoint),
    token: defaultToken(existing?.token),
    executorId,
    maxConcurrentLaunches: 1,
    maxManagedWorkers: normalizeMaxManagedWorkers(existing?.maxManagedWorkers),
    overlayVisible: existing?.overlayVisible ?? true,
  };
}

export async function saveSettings(settings: Pick<ExtensionSettings, 'endpoint' | 'token' | 'maxConcurrentLaunches' | 'maxManagedWorkers' | 'overlayVisible'>): Promise<ExtensionSettings> {
  return serializeMutation(async () => {
    const raw = await browser.storage.local.get(SETTINGS_KEY);
    const existing = raw[SETTINGS_KEY] as Partial<ExtensionSettings> | undefined;
    const current: ExtensionSettings = {
      endpoint: defaultEndpoint(existing?.endpoint),
      token: defaultToken(existing?.token),
      executorId: existing?.executorId ?? `browser-${crypto.randomUUID()}`,
      maxConcurrentLaunches: 1,
      maxManagedWorkers: normalizeMaxManagedWorkers(existing?.maxManagedWorkers),
      overlayVisible: existing?.overlayVisible ?? true,
    };
    const next: ExtensionSettings = { ...current, ...settings, maxConcurrentLaunches: 1 };
    await browser.storage.local.set({ [SETTINGS_KEY]: next });
    return next;
  });
}

export async function loadBindings(): Promise<Record<string, ManagedBinding>> {
  await awaitMutations();
  const raw = await browser.storage.local.get(BINDINGS_KEY);
  return (raw[BINDINGS_KEY] as Record<string, ManagedBinding> | undefined) ?? {};
}

export async function saveBinding(binding: ManagedBinding): Promise<void> {
  await serializeMutation(async () => {
    const raw = await browser.storage.local.get(BINDINGS_KEY);
    const bindings = (raw[BINDINGS_KEY] as Record<string, ManagedBinding> | undefined) ?? {};
    bindings[binding.workerId] = binding;
    await browser.storage.local.set({ [BINDINGS_KEY]: bindings });
  });
}

export async function removeBinding(workerId: string): Promise<void> {
  await serializeMutation(async () => {
    const raw = await browser.storage.local.get(BINDINGS_KEY);
    const bindings = (raw[BINDINGS_KEY] as Record<string, ManagedBinding> | undefined) ?? {};
    delete bindings[workerId];
    await browser.storage.local.set({ [BINDINGS_KEY]: bindings });
  });
}

export async function findBindingByTabId(tabId: number): Promise<ManagedBinding | null> {
  const bindings = await loadBindings();
  return Object.values(bindings).find((binding) => binding.tabId === tabId) ?? null;
}

export async function loadJournal(): Promise<Record<string, JournalEntry>> {
  await awaitMutations();
  const raw = await browser.storage.local.get(JOURNAL_KEY);
  return (raw[JOURNAL_KEY] as Record<string, JournalEntry> | undefined) ?? {};
}

export async function getJournal(commandId: string): Promise<JournalEntry | null> {
  const journal = await loadJournal();
  return journal[commandId] ?? null;
}

export async function saveJournal(commandId: string, entry: Omit<JournalEntry, 'updatedAt'>): Promise<JournalEntry> {
  return serializeMutation(async () => {
    const raw = await browser.storage.local.get(JOURNAL_KEY);
    const journal = (raw[JOURNAL_KEY] as Record<string, JournalEntry> | undefined) ?? {};
    const stored: JournalEntry = { ...entry, updatedAt: now() };
    journal[commandId] = stored;
    await browser.storage.local.set({ [JOURNAL_KEY]: journal });
    return stored;
  });
}

export async function setRuntimeStatus(state: RuntimeStatus['state'], detail: string): Promise<void> {
  const status: RuntimeStatus = { state, detail, updatedAt: now() };
  await serializeMutation(() => browser.storage.local.set({ [STATUS_KEY]: status }));
}

export async function getRuntimeStatus(): Promise<RuntimeStatus | null> {
  await awaitMutations();
  const raw = await browser.storage.local.get(STATUS_KEY);
  return (raw[STATUS_KEY] as RuntimeStatus | undefined) ?? null;
}


export async function loadLaunchThrottleState(): Promise<LaunchThrottleState> {
  await awaitMutations();
  const raw = await browser.storage.local.get(LAUNCH_THROTTLE_KEY);
  const existing = raw[LAUNCH_THROTTLE_KEY] as Partial<LaunchThrottleState> | undefined;
  return {
    lastLaunchStartedAt: typeof existing?.lastLaunchStartedAt === 'number' ? existing.lastLaunchStartedAt : null,
    rateLimitUntil: typeof existing?.rateLimitUntil === 'number' ? existing.rateLimitUntil : null,
    rateLimitStrike: Number.isInteger(existing?.rateLimitStrike) && (existing?.rateLimitStrike ?? 0) >= 0 ? existing!.rateLimitStrike! : 0,
  };
}

export async function saveLaunchThrottleState(state: LaunchThrottleState): Promise<void> {
  await serializeMutation(() => browser.storage.local.set({ [LAUNCH_THROTTLE_KEY]: state }));
}

export async function loadContinuityViews(): Promise<Record<string, BackendContinuityView>> {
  await awaitMutations();
  const raw = await browser.storage.local.get(CONTINUITY_VIEWS_KEY);
  return (raw[CONTINUITY_VIEWS_KEY] as Record<string, BackendContinuityView> | undefined) ?? {};
}

export async function saveContinuityView(view: BackendContinuityView): Promise<void> {
  await serializeMutation(async () => {
    const raw = await browser.storage.local.get(CONTINUITY_VIEWS_KEY);
    const views = (raw[CONTINUITY_VIEWS_KEY] as Record<string, BackendContinuityView> | undefined) ?? {};
    views[view.workerId] = view;
    await browser.storage.local.set({ [CONTINUITY_VIEWS_KEY]: views });
  });
}

export async function removeContinuityView(workerId: string): Promise<void> {
  await serializeMutation(async () => {
    const raw = await browser.storage.local.get(CONTINUITY_VIEWS_KEY);
    const views = (raw[CONTINUITY_VIEWS_KEY] as Record<string, BackendContinuityView> | undefined) ?? {};
    delete views[workerId];
    await browser.storage.local.set({ [CONTINUITY_VIEWS_KEY]: views });
  });
}
