import cluster from 'cluster';
import process from 'process';
import type { LogLevel } from './LogLevel';

export interface LogMetadata {
  /** Is the current process the Primary process */
  isPrimary: boolean;
  /** The process id of the current process */
  pid: number;
}

/**
 * Logs messages on a specific level.
 *
 * @see getLoggerFor on how to instantiate loggers.
 */
export interface SimpleLogger {
  /**
   * Log the given message at the given level.
   * If the internal level is higher than the given level, the message may be voided.
   * @param level - The level to log at.
   * @param message - The message to log.
   * @param meta - Optional metadata to include in the log message.
   */
  log: (level: LogLevel, message: string, meta?: LogMetadata) => SimpleLogger;
}

/**
 * Logs messages, with convenience methods to log on a specific level.
 *
 * @see getLoggerFor on how to instantiate loggers.
 */
export interface Logger extends SimpleLogger {
  /**
   * Log the given message at the given level.
   * If the internal level is higher than the given level, the message may be voided.
   * @param level - The level to log at.
   * @param message - The message to log.
   * @param meta - Optional metadata to include in the log message.
   */
  log: (level: LogLevel, message: string, meta?: LogMetadata) => Logger;

  /**
   * Log a message at the 'error' level.
   * @param message - The message to log.
   * @param meta - Optional metadata to include in the log message.
   */
  error: (message: string) => Logger;

  /**
   * Log a message at the 'warn' level.
   * @param message - The message to log.
   * @param meta - Optional metadata to include in the log message.
   */
  warn: (message: string) => Logger;

  /**
   * Log a message at the 'info' level.
   * @param message - The message to log.
   * @param meta - Optional metadata to include in the log message.
   */
  info: (message: string) => Logger;

  /**
   * Log a message at the 'verbose' level.
   * @param message - The message to log.
   * @param meta - Optional metadata to include in the log message.
   */
  verbose: (message: string) => Logger;

  /**
   * Log a message at the 'debug' level.
   * @param message - The message to log.
   * @param meta - Optional metadata to include in the log message.
   */
  debug: (message: string) => Logger;

  /**
   * Log a message at the 'silly' level.
   * @param message - The message to log.
   * @param meta - Optional metadata to include in the log message.
   */
  silly: (message: string) => Logger;
}

/**
 * Base class that implements all additional {@link BaseLogger} methods,
 * leaving only the implementation of {@link SimpleLogger}.
 */
export abstract class BaseLogger implements Logger {
  public abstract log(level: LogLevel, message: string, meta?: LogMetadata): Logger;

  private readonly getMeta = (): LogMetadata => ({
    pid: process.pid,
    isPrimary: cluster.isMaster,
  });

  public error(message: string): Logger {
    return this.log('error', message, this.getMeta());
  }

  public warn(message: string): Logger {
    return this.log('warn', message, this.getMeta());
  }

  public info(message: string): Logger {
    return this.log('info', message, this.getMeta());
  }

  public verbose(message: string): Logger {
    return this.log('verbose', message, this.getMeta());
  }

  public debug(message: string): Logger {
    return this.log('debug', message, this.getMeta());
  }

  public silly(message: string): Logger {
    return this.log('silly', message, this.getMeta());
  }
}

/**
 * Implements {@link BaseLogger} around a {@link SimpleLogger},
 * which can be swapped out a runtime.
 */
export class WrappingLogger extends BaseLogger {
  public logger: SimpleLogger;

  public constructor(logger: SimpleLogger) {
    super();
    this.logger = logger;
  }

  public log(level: LogLevel, message: string, meta?: LogMetadata): this {
    this.logger.log(level, message, meta);
    return this;
  }
}
