// Logger Middleware
//
// Logs HTTP requests in various formats.
// Default format: dev (METHOD /path STATUS time)

// ============================================================================
// Logger Configuration
// ============================================================================

///|
/// Log output format
pub enum LogFormat {
  /// Development format: GET /path 200 12ms
  Dev
  /// Combined log format (Apache-style)
  Combined
  /// JSON format for structured logging
  Json
}

///|
/// Logger configuration
pub struct LoggerConfig {
  /// Output format
  format : LogFormat
  /// Skip logging for certain paths
  skip_paths : Array[String]
  /// Log to custom function (default: console.log)
  log_fn : ((String) -> Unit)?
}

///|
/// Default logger configuration
pub fn LoggerConfig::default() -> LoggerConfig {
  { format: LogFormat::Dev, skip_paths: [], log_fn: None }
}

///|
/// Create dev format config
pub fn LoggerConfig::dev() -> LoggerConfig {
  { format: LogFormat::Dev, skip_paths: [], log_fn: None }
}

///|
/// Create combined format config
pub fn LoggerConfig::combined() -> LoggerConfig {
  { format: LogFormat::Combined, skip_paths: [], log_fn: None }
}

///|
/// Create JSON format config
pub fn LoggerConfig::json() -> LoggerConfig {
  { format: LogFormat::Json, skip_paths: [], log_fn: None }
}

///|
/// Set skip paths
pub fn LoggerConfig::with_skip_paths(
  self : LoggerConfig,
  paths : Array[String],
) -> LoggerConfig {
  { ..self, skip_paths: paths }
}

///|
/// Set custom log function
pub fn LoggerConfig::with_log_fn(
  self : LoggerConfig,
  log_fn : (String) -> Unit,
) -> LoggerConfig {
  { ..self, log_fn: Some(log_fn) }
}

// ============================================================================
// Logger Middleware
// ============================================================================

///|
/// Create logger middleware with default config (dev format)
pub fn logger() -> Middleware {
  logger_with_config(LoggerConfig::default())
}

///|
/// Create logger middleware with custom config
pub fn logger_with_config(config : LoggerConfig) -> Middleware {
  match delegate_logger_handler(config) {
    Some(handler) => return Middleware(handler)
    None => ()
  }
  Middleware(
    @mars.Handler(async fn(ctx) {
      let path = ctx.path()

      // Check skip condition
      for skip_path in config.skip_paths {
        if path.has_prefix(skip_path) {
          return // Continue without logging
        }
      }

      // Format log message
      let message = format_log_message(ctx, config.format)

      // Output log
      match config.log_fn {
        Some(log_fn) => log_fn(message)
        None => console_log(message)
      }
      // Continue to next handler
    }),
  )
}

///|
fn delegate_logger_handler(config : LoggerConfig) -> @mars.Handler? {
  match config.log_fn {
    Some(_) => None
    None =>
      match config.format {
        LogFormat::Dev => {
          let base_handler = @mars_mw.logger_simple()
          if config.skip_paths.is_empty() {
            Some(base_handler)
          } else {
            Some(
              @mars.Handler(async fn(ctx) {
                let path = ctx.path()
                for skip_path in config.skip_paths {
                  if path.has_prefix(skip_path) {
                    return
                  }
                }
                let @mars.Handler(h) = base_handler
                h(ctx)
              }),
            )
          }
        }
        LogFormat::Combined | LogFormat::Json => None
      }
  }
}

// ============================================================================
// Log Formatting
// ============================================================================

///|
fn format_log_message(ctx : Ctx, format : LogFormat) -> String {
  let http_method = ctx.meth().to_string()
  let path = ctx.path()
  match format {
    Dev => format_dev_pure(http_method, path)
    Combined => {
      let user_agent = ctx.header("User-Agent").unwrap_or("-")
      let referer = ctx.header("Referer").unwrap_or("-")
      let timestamp = get_timestamp()
      format_combined_pure(http_method, path, user_agent, referer, timestamp)
    }
    Json => {
      let timestamp = get_timestamp()
      format_json_pure(http_method, path, timestamp)
    }
  }
}

///|
/// Dev format: GET /path (pure, no IO)
fn format_dev_pure(http_method : String, path : String) -> String {
  "\{http_method} \{path}"
}

///|
/// Combined format (pure, no IO)
fn format_combined_pure(
  http_method : String,
  path : String,
  user_agent : String,
  referer : String,
  timestamp : String,
) -> String {
  "- - - [\{timestamp}] \"\{http_method} \{path}\" - - \"\{referer}\" \"\{user_agent}\""
}

///|
/// JSON format (pure, no IO)
fn format_json_pure(
  http_method : String,
  path : String,
  timestamp : String,
) -> String {
  let json : Json = {
    "method": http_method.to_json(),
    "path": path.to_json(),
    "timestamp": timestamp.to_json(),
  }
  json.stringify()
}

// ============================================================================
// FFI Helpers
// ============================================================================

///|
extern "js" fn get_timestamp() -> String =
  #| () => new Date().toISOString()

///|
extern "js" fn console_log(message : String) -> Unit =
  #| (msg) => console.log(msg)