///|
/// A record describing a single request/response round-trip, passed to an
/// observer for logging, metrics, or debugging.
pub(all) struct RequestLog {
  /// The endpoint path (e.g. `/chat/completions`).
  path : String
  /// The request body as a JSON string.
  request_body : String
  /// The HTTP status code, or -1 if the request never got a response.
  status : Int
  /// The response body text (may be truncated by the caller).
  response_body : String
  /// Whether the call ultimately succeeded (2xx).
  ok : Bool
}

///|
/// A log severity level.
pub(all) enum LogLevel {
  Debug
  Info
  Warn
  Error
} derive(Eq, Debug)

///|
/// The numeric rank of a level, for threshold comparisons.
pub fn LogLevel::rank(self : LogLevel) -> Int {
  match self {
    Debug => 0
    Info => 1
    Warn => 2
    Error => 3
  }
}

///|
/// A short label for a level.
pub fn LogLevel::label(self : LogLevel) -> String {
  match self {
    Debug => "DEBUG"
    Info => "INFO"
    Warn => "WARN"
    Error => "ERROR"
  }
}

///|
/// A simple collecting logger that buffers formatted lines in memory — useful
/// for tests and for surfacing a request trace in a UI. A production app would
/// swap in its own observer that writes to a real logging backend.
pub struct MemoryLogger {
  min_level : LogLevel
  lines : Array[String]
}

///|
/// Create a memory logger that keeps entries at or above `min_level`.
pub fn MemoryLogger::new(min_level? : LogLevel = Info) -> MemoryLogger {
  { min_level, lines: [] }
}

///|
/// Log a message at a level (dropped if below the threshold).
pub fn MemoryLogger::log(
  self : MemoryLogger,
  level : LogLevel,
  message : String,
) -> Unit {
  if level.rank() >= self.min_level.rank() {
    self.lines.push("[\{level.label()}] \{message}")
  }
}

///|
/// Log a completed request round-trip at an appropriate level.
pub fn MemoryLogger::log_request(self : MemoryLogger, log : RequestLog) -> Unit {
  let level = if log.ok { Info } else { Error }
  self.log(
    level,
    "\{log.path} -> \{log.status} (\{log.request_body.length()} req bytes, \{log.response_body.length()} resp bytes)",
  )
}

///|
/// The number of buffered log lines.
pub fn MemoryLogger::count(self : MemoryLogger) -> Int {
  self.lines.length()
}

///|
/// All buffered lines joined by newlines.
pub fn MemoryLogger::dump(self : MemoryLogger) -> String {
  let out = StringBuilder::new()
  for i, line in self.lines {
    if i > 0 {
      out.write_string("\n")
    }
    out.write_string(line)
  }
  out.to_string()
}

///|
/// Whether any line at `level` or above was recorded.
pub fn MemoryLogger::has_at_least(
  self : MemoryLogger,
  level : LogLevel,
) -> Bool {
  for line in self.lines {
    if line.has_prefix("[\{level.label()}]") {
      return true
    }
  }
  false
}

///|
/// Clear all buffered lines.
pub fn MemoryLogger::clear(self : MemoryLogger) -> Unit {
  self.lines.clear()
}