// ============================================================
// Lightweight, allocation-free-per-tick telemetry wrapper.
// Applications can expose these counters to a profiler or metrics backend.
// ============================================================

///|
/// Aggregate counters for one instrumented node.
pub struct NodeMetrics {
  total_ticks : Ref[Int]
  successes : Ref[Int]
  failures : Ref[Int]
  running : Ref[Int]
  consecutive_running : Ref[Int]
  last : Ref[Status?]
}

///|
/// Create empty metrics.
pub fn NodeMetrics::new() -> NodeMetrics {
  {
    total_ticks: Ref::new(0),
    successes: Ref::new(0),
    failures: Ref::new(0),
    running: Ref::new(0),
    consecutive_running: Ref::new(0),
    last: Ref::new(None),
  }
}

///|
/// Reset all counters.
pub fn NodeMetrics::reset(self : NodeMetrics) -> Unit {
  self.total_ticks.set(0)
  self.successes.set(0)
  self.failures.set(0)
  self.running.set(0)
  self.consecutive_running.set(0)
  self.last.set(None)
}

///|
/// Record one status observation.
pub fn NodeMetrics::record(self : NodeMetrics, status : Status) -> Unit {
  self.total_ticks.set(self.total_ticks.get() + 1)
  self.last.set(Some(status))
  match status {
    Status::BTSuccess => {
      self.successes.set(self.successes.get() + 1)
      self.consecutive_running.set(0)
    }
    Status::BTFailure => {
      self.failures.set(self.failures.get() + 1)
      self.consecutive_running.set(0)
    }
    Status::BTRunning => {
      self.running.set(self.running.get() + 1)
      self.consecutive_running.set(self.consecutive_running.get() + 1)
    }
  }
}

///|
/// Number of observed ticks.
pub fn NodeMetrics::total_ticks(self : NodeMetrics) -> Int {
  self.total_ticks.get()
}

///|
/// Number of successful observations.
pub fn NodeMetrics::successes(self : NodeMetrics) -> Int {
  self.successes.get()
}

///|
/// Number of failed observations.
pub fn NodeMetrics::failures(self : NodeMetrics) -> Int {
  self.failures.get()
}

///|
/// Number of running observations.
pub fn NodeMetrics::running(self : NodeMetrics) -> Int {
  self.running.get()
}

///|
/// Longest current running streak.
pub fn NodeMetrics::consecutive_running(self : NodeMetrics) -> Int {
  self.consecutive_running.get()
}

///|
/// Most recently observed status.
pub fn NodeMetrics::last_status(self : NodeMetrics) -> Status? {
  self.last.get()
}

///|
/// Wrap a node and record every tick without changing its behavior.
pub fn metrics_node(child : Node, metrics : NodeMetrics) -> Node {
  let tick = fn(bb) {
    let status = child.tick(bb)
    metrics.record(status)
    status
  }
  let reset = fn() { child.reset() }
  Node::new(tick, reset)
}

///|
/// A named telemetry sample suitable for exporting to text or logs.
pub struct MetricsSnapshot {
  name : String
  total : Int
  success : Int
  failure : Int
  running : Int
}

///|
/// Read a stable value snapshot from live metrics.
pub fn snapshot_metrics(
  name : String,
  metrics : NodeMetrics,
) -> MetricsSnapshot {
  {
    name,
    total: metrics.total_ticks(),
    success: metrics.successes(),
    failure: metrics.failures(),
    running: metrics.running(),
  }
}

///|
/// Format a snapshot as one CSV row for simple observability pipelines.
pub fn MetricsSnapshot::to_csv(self : MetricsSnapshot) -> String {
  "\{self.name},\{self.total},\{self.success},\{self.failure},\{self.running}"
}