/**
 * Supported structured logger levels.
 */
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";

/**
 * App-facing structured logger port.
 *
 * Application code logs through this interface so production can use Pino,
 * Datadog, or another adapter while tests can use no-op or memory loggers.
 */
export interface LoggerPort {
  /**
   * Log very detailed diagnostic information.
   */
  trace(message: string, meta?: Record<string, unknown>): void;
  /**
   * Log debug-level diagnostic information.
   */
  debug(message: string, meta?: Record<string, unknown>): void;
  /**
   * Log normal application progress.
   */
  info(message: string, meta?: Record<string, unknown>): void;
  /**
   * Log recoverable problems or unusual conditions.
   */
  warn(message: string, meta?: Record<string, unknown>): void;
  /**
   * Log failed operations.
   */
  error(message: string, meta?: Record<string, unknown>): void;
  /**
   * Log unrecoverable failures.
   */
  fatal(message: string, meta?: Record<string, unknown>): void;
  /**
   * Return a logger with additional structured bindings.
   */
  child(bindings: Record<string, unknown>): LoggerPort;
}

/**
 * Captured entry from `createMemoryLogger(...)`.
 */
export interface MemoryLogEntry {
  level: LogLevel;
  message: string;
  meta?: Record<string, unknown>;
  bindings: Record<string, unknown>;
}

/**
 * In-memory logger port used by tests and local assertions.
 */
export interface MemoryLoggerPort extends LoggerPort {
  /**
   * Captured log entries in call order.
   */
  entries: MemoryLogEntry[];
}

/**
 * Create a logger that discards every log call.
 *
 * Use this in tests where logging is irrelevant.
 *
 * @returns A logger port whose methods are no-ops.
 */
export function createNoopLogger(): LoggerPort {
  const logger: LoggerPort = {
    trace: () => {},
    debug: () => {},
    info: () => {},
    warn: () => {},
    error: () => {},
    fatal: () => {},
    child: () => logger,
  };

  return logger;
}

/**
 * Create a logger that captures entries in memory.
 *
 * Child loggers inherit existing bindings and append new bindings to each
 * captured entry.
 *
 * @param bindings - Structured bindings attached to every captured entry.
 * @param entries - Optional shared entry array.
 * @returns A logger port with an inspectable `entries` array.
 */
export function createMemoryLogger(
  bindings: Record<string, unknown> = {},
  entries: MemoryLogEntry[] = [],
): MemoryLoggerPort {
  const capturedBindings = { ...bindings };
  const record = (
    level: LogLevel,
    message: string,
    meta?: Record<string, unknown>,
  ) => {
    entries.push({
      level,
      message,
      meta: meta ? { ...meta } : undefined,
      bindings: { ...capturedBindings },
    });
  };

  return {
    entries,
    trace: (message, meta) => record("trace", message, meta),
    debug: (message, meta) => record("debug", message, meta),
    info: (message, meta) => record("info", message, meta),
    warn: (message, meta) => record("warn", message, meta),
    error: (message, meta) => record("error", message, meta),
    fatal: (message, meta) => record("fatal", message, meta),
    child: (childBindings) =>
      createMemoryLogger({ ...capturedBindings, ...childBindings }, entries),
  };
}
