export type LogLevel = "info" | "warn" | "error";

export interface LogEntry {
  timestamp: number;
  level: LogLevel;
  message: string;
}

type LogListener = () => void;

const MAX_ENTRIES = 500;
const STORAGE_KEY = "simpl-resultat-logs";
const logs: LogEntry[] = [];
const listeners = new Set<LogListener>();

// Immutable view of `logs`, rebuilt on every mutation. `useSyncExternalStore`
// compares snapshots by identity (Object.is), so `getLogs` MUST return a NEW
// reference whenever the logs change — and a STABLE one between changes (never
// re-slice per call, which would loop the renderer). The old behaviour mutated
// `logs` in place and returned that same array, so the log console never
// updated live: a log produced while the panel was open was never re-rendered.
let logsSnapshot: readonly LogEntry[] = [];

function refreshSnapshot() {
  logsSnapshot = logs.slice();
}

let initialized = false;

function loadFromStorage() {
  try {
    const stored = sessionStorage.getItem(STORAGE_KEY);
    if (stored) {
      const parsed: LogEntry[] = JSON.parse(stored);
      logs.push(...parsed);
      refreshSnapshot();
    }
  } catch {
    // ignore corrupted storage
  }
}

function saveToStorage() {
  try {
    sessionStorage.setItem(STORAGE_KEY, JSON.stringify(logs));
  } catch {
    // ignore quota errors
  }
}

function addEntry(level: LogLevel, args: unknown[]) {
  const message = args
    .map((a) => {
      if (typeof a === "string") return a;
      try {
        return JSON.stringify(a);
      } catch {
        return String(a);
      }
    })
    .join(" ");

  logs.push({ timestamp: Date.now(), level, message });
  if (logs.length > MAX_ENTRIES) {
    logs.splice(0, logs.length - MAX_ENTRIES);
  }
  refreshSnapshot();

  saveToStorage();
  listeners.forEach((fn) => fn());
}

export function initLogCapture() {
  if (initialized) return;
  initialized = true;

  loadFromStorage();

  const origLog = console.log.bind(console);
  const origWarn = console.warn.bind(console);
  const origError = console.error.bind(console);

  console.log = (...args: unknown[]) => {
    addEntry("info", args);
    origLog(...args);
  };

  console.warn = (...args: unknown[]) => {
    addEntry("warn", args);
    origWarn(...args);
  };

  console.error = (...args: unknown[]) => {
    addEntry("error", args);
    origError(...args);
  };
}

export function getLogs(): readonly LogEntry[] {
  return logsSnapshot;
}

/// Extract the N most recent non-info entries formatted as a single string,
/// suitable for appending to a feedback body. Empty string if no qualifying
/// entries. Each line: `[ISO timestamp] LEVEL: message`.
export function getRecentErrorLogs(n: number): string {
  if (n <= 0) return "";
  const errors = logs.filter((l) => l.level !== "info");
  const tail = errors.slice(Math.max(0, errors.length - n));
  return tail
    .map((l) => {
      const iso = new Date(l.timestamp).toISOString();
      return `[${iso}] ${l.level.toUpperCase()}: ${l.message}`;
    })
    .join("\n");
}

export function clearLogs() {
  logs.length = 0;
  refreshSnapshot();
  saveToStorage();
  listeners.forEach((fn) => fn());
}

// ---------------------------------------------------------------------------
// App logging API. Prefer these over raw `console.*` in application code so
// meaningful events land in the in-app log console (Settings → Systems →
// Journaux / Logs). They forward to the captured console methods, so entries
// also show up in devtools. `console.log` is captured as the `info` level.
// ---------------------------------------------------------------------------

export function logInfo(...args: unknown[]): void {
  console.log(...args);
}

export function logWarn(...args: unknown[]): void {
  console.warn(...args);
}

export function logError(...args: unknown[]): void {
  console.error(...args);
}

export function subscribe(listener: LogListener): () => void {
  listeners.add(listener);
  return () => listeners.delete(listener);
}
