// A record of what the server did: one span per request, with how long it took
// and whether it ended in an error, and the counters an operator watches.

///|
/// One served request, as the telemetry layer saw it.
#derive(Debug)
pub(all) struct Span {
  operation : Operation
  /// The tool or prompt name, the resource URI, or the empty string for a
  /// listing.
  name : String
  /// How long the request took, in microseconds.
  duration_us : Double
  /// Whether the request ended in an error rather than an answer.
  failed : Bool
}

///|
/// Timing and counting for every request that passes through (← FastMCP's
/// timing and logging middleware, kept as one record a caller can read or
/// forward).
///
/// The last `capacity` spans are kept in memory and every one is handed to
/// `sink`, so a program can print them, write them to its logger, or forward
/// them to a tracing system of its own. A span is recorded whether the request
/// was answered or raised, so a failure is counted where it happened rather
/// than where the client noticed.
pub struct Telemetry {
  priv capacity : Int32
  priv sink : ((Span) -> Unit)?
  priv mut spans : Array[Span]
  priv counts : Map[String, Int32]
  priv mut failures : Int32
  priv mut total : Int32
}

///|
/// A recorder of the last 128 spans, keeping nothing else.
pub fn Telemetry::new(
  capacity~ : Int32 = 128,
  sink? : (Span) -> Unit,
) -> Telemetry {
  { capacity, sink, spans: [], counts: Map([]), failures: 0, total: 0, }
}

///|
/// The layer this recorder installs on a server.
pub fn Telemetry::layer(self : Telemetry) -> Layer {
  Layer::new(name="telemetry", run=async fn(
    call,
    next,
  ) -> Json raise @exceptions.FastMCPError {
    let started = @bench.monotonic_clock_start()
    let answer = {
      errdefer self.record(
        call,
        @bench.monotonic_clock_end(started),
        failed=true,
      )
      next.then(call)
    }
    self.record(call, @bench.monotonic_clock_end(started), failed=false)
    answer
  })
}

///|
/// Keep one span and count it.
fn Telemetry::record(
  self : Telemetry,
  call : Call,
  duration_us : Double,
  failed~ : Bool,
) -> Unit {
  let span = {
    operation: call.operation,
    name: call.name,
    duration_us,
    failed,
  }
  self.total = self.total + 1
  if failed {
    self.failures = self.failures + 1
  }
  let op = call.operation.code()
  self.counts[op] = self.counts.get(op).unwrap_or(0) + 1
  while self.spans.length() >= self.capacity {
    self.spans = self.spans[1:].to_owned()
  }
  self.spans.push(span)
  match self.sink {
    Some(sink) => sink(span)
    None => ()
  }
}

///|
/// The spans kept so far, oldest first.
pub fn Telemetry::spans(self : Telemetry) -> Array[Span] {
  self.spans.copy()
}

///|
/// How many requests of `operation` were served.
pub fn Telemetry::count(self : Telemetry, operation : Operation) -> Int32 {
  self.counts.get(operation.code()).unwrap_or(0)
}

///|
/// How many requests were served, all operations together.
pub fn Telemetry::total(self : Telemetry) -> Int32 {
  self.total
}

///|
/// How many of them ended in an error.
pub fn Telemetry::failures(self : Telemetry) -> Int32 {
  self.failures
}

///|
/// Forget every span and counter recorded so far.
pub fn Telemetry::clear(self : Telemetry) -> Unit {
  self.spans.clear()
  self.counts.clear()
  self.failures = 0
  self.total = 0
}

///|
pub extend Span with Debug::{to_repr}