import type { LogLevel, LogContext, LogEntry, LoggerConfig, LogTransport } from './types';

const LEVEL_PRIORITY: Record<LogLevel, number> = {
  debug: 0,
  info: 1,
  warn: 2,
  error: 3,
};

/** Default transport: sends batched logs via sendBeacon or fetch. */
class HttpTransport implements LogTransport {
  constructor(private endpoint: string) {}

  send(entries: LogEntry[]): void {
    const payload = JSON.stringify(entries);

    // sendBeacon is fire-and-forget, works during page unload
    if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
      const blob = new Blob([payload], { type: 'application/json' });
      const sent = navigator.sendBeacon(this.endpoint, blob);
      if (sent) return;
    }

    // Fallback to fetch (non-blocking)
    fetch(this.endpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: payload,
      keepalive: true,
    }).catch(() => {
      // Silently drop - we don't want logging failures to break the app
    });
  }
}

/** Console transport for development. */
class ConsoleTransport implements LogTransport {
  send(entries: LogEntry[]): void {
    for (const entry of entries) {
      const method = entry.level === 'debug' ? 'log' : entry.level;
      const ctx = Object.keys(entry.context).length > 0 ? entry.context : undefined;
      if (entry.error) {
        console[method](`[${entry.level.toUpperCase()}] ${entry.message}`, entry.error, ctx);
      } else if (ctx) {
        console[method](`[${entry.level.toUpperCase()}] ${entry.message}`, ctx);
      } else {
        console[method](`[${entry.level.toUpperCase()}] ${entry.message}`);
      }
    }
  }
}

export class Logger {
  private buffer: LogEntry[] = [];
  private timer: ReturnType<typeof setTimeout> | null = null;
  private transport: LogTransport;
  private minLevel: number;
  private baseContext: LogContext;
  private batchSize: number;
  private flushInterval: number;

  constructor(config: LoggerConfig) {
    this.minLevel = LEVEL_PRIORITY[config.level];
    this.batchSize = config.batchSize ?? 20;
    this.flushInterval = config.flushInterval ?? 5000;
    this.baseContext = { service: config.service };

    if (config.endpoint) {
      this.transport = new HttpTransport(config.endpoint);
    } else {
      this.transport = new ConsoleTransport();
    }

    this.startFlushTimer();

    // Flush on page unload
    if (typeof window !== 'undefined') {
      window.addEventListener('visibilitychange', () => {
        if (document.visibilityState === 'hidden') {
          this.flush();
        }
      });
      window.addEventListener('pagehide', () => this.flush());
    }
  }

  /** Create a child logger with additional context fields. */
  withContext(ctx: LogContext): Logger {
    const child = Object.create(this) as Logger;
    child.baseContext = { ...this.baseContext, ...ctx };
    return child;
  }

  debug(message: string, ctx?: LogContext): void {
    this.log('debug', message, ctx);
  }

  info(message: string, ctx?: LogContext): void {
    this.log('info', message, ctx);
  }

  warn(message: string, ctx?: LogContext): void {
    this.log('warn', message, ctx);
  }

  error(message: string, error?: Error | unknown, ctx?: LogContext): void {
    const entry = this.createEntry('error', message, ctx);
    if (error instanceof Error) {
      entry.error = {
        name: error.name,
        message: error.message,
        stack: error.stack,
      };
    } else if (error !== undefined) {
      entry.error = {
        name: 'UnknownError',
        message: String(error),
      };
    }
    this.enqueue(entry);
  }

  /** Force-flush the buffer immediately. */
  flush(): void {
    if (this.buffer.length === 0) return;
    const entries = this.buffer.splice(0);
    this.transport.send(entries);
  }

  /** Stop the flush timer (call on teardown). */
  destroy(): void {
    this.flush();
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
  }

  private log(level: LogLevel, message: string, ctx?: LogContext): void {
    if (LEVEL_PRIORITY[level] < this.minLevel) return;
    this.enqueue(this.createEntry(level, message, ctx));
  }

  private createEntry(level: LogLevel, message: string, ctx?: LogContext): LogEntry {
    return {
      level,
      message,
      timestamp: new Date().toISOString(),
      context: { ...this.baseContext, ...ctx },
    };
  }

  private enqueue(entry: LogEntry): void {
    this.buffer.push(entry);
    if (this.buffer.length >= this.batchSize) {
      this.flush();
    }
  }

  private startFlushTimer(): void {
    if (this.flushInterval > 0 && typeof setInterval !== 'undefined') {
      this.timer = setInterval(() => this.flush(), this.flushInterval);
    }
  }
}

/** Create a logger instance. */
export function createLogger(config: LoggerConfig): Logger {
  return new Logger(config);
}
