/**
 * Tiny structured logger. One JSON line per log record on stdout (errors on
 * stderr), with an additional append-only file sink at
 * `logs/workspace-sessions.log` so every spawn / resume / transcript event is
 * grep-able in isolation without scrolling past every other backend log line.
 *
 * Hand-rolled — keep the dep surface zero. The file sink failure must never
 * take down session lifecycle, so all fs errors are swallowed.
 */

import { createWriteStream, mkdirSync, type WriteStream } from 'node:fs';
import { dirname, resolve } from 'node:path';

type Level = 'debug' | 'info' | 'warn' | 'error';

const LEVELS: Record<Level, number> = { debug: 10, info: 20, warn: 30, error: 40 };

const envLevel = (process.env['WEB_TERMINAL_LOG_LEVEL'] ?? 'info').toLowerCase();
const minLevel: number = LEVELS[envLevel as Level] ?? LEVELS.info;

const FILE_PATH = resolve(process.cwd(), 'logs', 'workspace-sessions.log');
const fileStream = openFileSink(FILE_PATH);

function openFileSink(path: string): WriteStream | null {
  try {
    mkdirSync(dirname(path), { recursive: true });
    const s = createWriteStream(path, { flags: 'a' });
    s.on('error', () => { /* swallow — never take down spawn lifecycle */ });
    return s;
  } catch {
    return null;
  }
}

function emit(level: Level, msg: string, fields: Record<string, unknown>): void {
  if (LEVELS[level] < minLevel) return;
  const record: Record<string, unknown> = {
    ts: new Date().toISOString(),
    level,
    msg,
    ...fields,
  };
  const line = JSON.stringify(record, replacer) + '\n';
  if (level === 'error' || level === 'warn') {
    process.stderr.write(line);
  } else {
    process.stdout.write(line);
  }
  if (fileStream) {
    try { fileStream.write(line); } catch { /* swallow */ }
  }
}

function replacer(_key: string, value: unknown): unknown {
  if (value instanceof Error) {
    return { name: value.name, message: value.message, stack: value.stack };
  }
  return value;
}

export interface Logger {
  debug(msg: string, fields?: Record<string, unknown>): void;
  info(msg: string, fields?: Record<string, unknown>): void;
  warn(msg: string, fields?: Record<string, unknown>): void;
  error(msg: string, fields?: Record<string, unknown>): void;
  child(bindings: Record<string, unknown>): Logger;
}

function make(bindings: Record<string, unknown>): Logger {
  const merge = (fields?: Record<string, unknown>): Record<string, unknown> =>
    fields === undefined ? bindings : { ...bindings, ...fields };
  return {
    debug: (msg, fields) => emit('debug', msg, merge(fields)),
    info: (msg, fields) => emit('info', msg, merge(fields)),
    warn: (msg, fields) => emit('warn', msg, merge(fields)),
    error: (msg, fields) => emit('error', msg, merge(fields)),
    child: (extra) => make({ ...bindings, ...extra }),
  };
}

export const logger: Logger = make({});
