import { toJsonObject, toJsonValue } from './internal/json-safe.js'

export type LogLevel = 'debug' | 'info' | 'warn' | 'error'
export type LogContext = Readonly<Record<string, unknown>>

export interface LogRecord extends Record<string, unknown> {
  timestamp: string
  level: LogLevel
  message: string
}

export interface Logger {
  debug(message: string, context?: LogContext): void
  info(message: string, context?: LogContext): void
  warn(message: string, context?: LogContext): void
  error(message: string, error?: unknown, context?: LogContext): void
  child(context: LogContext): Logger
}

export interface LoggerOptions {
  level?: LogLevel
  context?: LogContext
  write?: (record: LogRecord) => void
  now?: () => Date
}

const LEVEL_ORDER: Record<LogLevel, number> = {
  debug: 10,
  info: 20,
  warn: 30,
  error: 40,
}

function defaultWrite(record: LogRecord): void {
  const line = JSON.stringify(record)
  switch (record.level) {
    case 'debug':
      console.debug(line)
      break
    case 'info':
      console.info(line)
      break
    case 'warn':
      console.warn(line)
      break
    case 'error':
      console.error(line)
      break
  }
}

export function createLogger(options: LoggerOptions = {}): Logger {
  const threshold = LEVEL_ORDER[options.level ?? 'info']
  const write = options.write ?? defaultWrite
  const now = options.now ?? (() => new Date())
  const baseContext = toJsonObject(options.context)

  const emit = (
    level: LogLevel,
    message: string,
    context?: LogContext,
    error?: unknown,
  ) => {
    if (LEVEL_ORDER[level] < threshold) return
    const record: LogRecord = {
      ...baseContext,
      ...toJsonObject(context),
      ...(error === undefined ? {} : { error: toJsonValue(error) }),
      timestamp: now().toISOString(),
      level,
      message,
    }
    write(record)
  }

  const logger: Logger = {
    debug: (message, context) => emit('debug', message, context),
    info: (message, context) => emit('info', message, context),
    warn: (message, context) => emit('warn', message, context),
    error: (message, error, context) => emit('error', message, context, error),
    child(context) {
      return createLogger({
        level: options.level,
        context: { ...baseContext, ...toJsonObject(context) },
        write,
        now,
      })
    },
  }

  return logger
}
