/**
 * Wiki generation progress tracking.
 *
 * Manages in-memory progress state and pub/sub listeners for wiki
 * generation jobs.
 */

import type { WikiProgressUpdate } from "./types";

export type WikiGenerationState = "idle" | "running" | "done" | "error";

export interface WikiGenerationStatus extends WikiProgressUpdate {
  repositoryId: string;
  state: WikiGenerationState;
  startedAt: string;
  updatedAt: string;
}

const wikiProgressStore = new Map<string, WikiGenerationStatus>();
const wikiProgressListeners = new Map<string, Set<(status: WikiGenerationStatus) => void>>();
const wikiAbortControllers = new Map<string, AbortController>();

/** Monotonically increasing generation ID per repository to detect stale updates. */
const wikiGenerationId = new Map<string, number>();

const defaultProgress: WikiProgressUpdate = {
  phase: "scanning",
  message: "Queued",
  pagesGenerated: 0,
  errors: 0,
  completedSteps: 0,
  totalSteps: 0,
  completedPageIds: [],
};


function emitWikiProgress(status: WikiGenerationStatus): void {
  const listeners = wikiProgressListeners.get(status.repositoryId);
  if (!listeners) {
    return;
  }
  for (const listener of listeners) {
    listener(status);
  }
}

export function createWikiAbortController(repositoryId: string): AbortController {
  // Abort any previous controller
  wikiAbortControllers.get(repositoryId)?.abort();
  const controller = new AbortController();
  wikiAbortControllers.set(repositoryId, controller);
  return controller;
}

export function cancelWikiGeneration(repositoryId: string): boolean {
  const controller = wikiAbortControllers.get(repositoryId);
  if (!controller) return false;
  const genId = wikiGenerationId.get(repositoryId);
  controller.abort();
  wikiAbortControllers.delete(repositoryId);
  finishWikiProgress(repositoryId, "error", "Generation cancelled by user", genId);
  // Record cancel time so ensureWikiFresh doesn't immediately re-trigger
  wikiCancelTimestamps.set(repositoryId, Date.now());
  return true;
}

/** Exposes cancel timestamps for the freshness cooldown guard. */
export const wikiCancelTimestamps = new Map<string, number>();

/**
 * Start a new generation run. Returns the generation ID that must be
 * passed to updateWikiProgress / finishWikiProgress so that stale
 * updates from a previous (cancelled) run are ignored.
 */
export function startWikiProgress(repositoryId: string): number {
  const genId = (wikiGenerationId.get(repositoryId) ?? 0) + 1;
  wikiGenerationId.set(repositoryId, genId);

  const now = new Date().toISOString();
  const status = {
    repositoryId,
    state: "running" as const,
    startedAt: now,
    updatedAt: now,
    ...defaultProgress,
  };
  wikiProgressStore.set(repositoryId, status);
  emitWikiProgress(status);
  return genId;
}

export function updateWikiProgress(repositoryId: string, progress: WikiProgressUpdate, genId?: number): void {
  // Reject stale updates from a previous generation run.
  if (genId !== undefined && genId !== wikiGenerationId.get(repositoryId)) {
    return;
  }

  const existing = wikiProgressStore.get(repositoryId);

  // Ignore updates after the generation has been cancelled or finished.
  if (existing?.state === "error" || existing?.state === "done") {
    return;
  }

  const now = new Date().toISOString();
  const state: WikiGenerationState = progress.phase === "done"
    ? (progress.errors > 0 ? "error" : "done")
    : "running";
  const status: WikiGenerationStatus = {
    repositoryId,
    state,
    startedAt: existing?.startedAt ?? now,
    updatedAt: now,
    ...progress,
  };
  wikiProgressStore.set(repositoryId, status);
  emitWikiProgress(status);
}

export function finishWikiProgress(
  repositoryId: string,
  state: "done" | "error",
  message: string,
  genId?: number,
): void {
  // Reject stale finish from a previous generation run.
  if (genId !== undefined && genId !== wikiGenerationId.get(repositoryId)) {
    return;
  }

  // Don't overwrite a terminal state — the first finish wins.
  // This prevents cancelWikiGeneration's "cancelled by user" message from
  // being overwritten by the generation's catch handler re-throwing the abort.
  const existing = wikiProgressStore.get(repositoryId);
  if (existing?.state === "done" || existing?.state === "error") {
    return;
  }

  wikiAbortControllers.delete(repositoryId);
  const now = new Date().toISOString();
  const status = {
    repositoryId,
    state,
    startedAt: existing?.startedAt ?? now,
    updatedAt: now,
    ...(existing ?? defaultProgress),
    message,
    phase: "done" as const,
  };
  wikiProgressStore.set(repositoryId, status);
  emitWikiProgress(status);
}

export function getWikiProgress(repositoryId: string): WikiGenerationStatus | null {
  return wikiProgressStore.get(repositoryId) ?? null;
}


export function subscribeWikiProgress(
  repositoryId: string,
  listener: (status: WikiGenerationStatus) => void,
  options?: { immediate?: boolean },
): () => void {
  const listeners = wikiProgressListeners.get(repositoryId) ?? new Set();
  listeners.add(listener);
  wikiProgressListeners.set(repositoryId, listeners);

  const current = wikiProgressStore.get(repositoryId);
  if (current && options?.immediate !== false) {
    listener(current);
  }

  return () => {
    const currentListeners = wikiProgressListeners.get(repositoryId);
    if (!currentListeners) {
      return;
    }
    currentListeners.delete(listener);
    if (currentListeners.size === 0) {
      wikiProgressListeners.delete(repositoryId);
    }
  };
}
