///|
/// Canonical model-step types for the functional Kernel.
///
/// ADR `docs/design/08-functional-kernel-contract.md` §2.1, §2.11: the
/// assistant completion is the single source of truth for tool calls and
/// reasoning. The canonical type has one authoritative owner for each fact.

///|
/// Token usage reported by the model for a single step. The Kernel only counts
/// what is actually reported; absent dimensions are `None`, **never** silently
/// coerced to 0 (ADR §2.9 — unknown usage is never reported as 0).
pub(all) struct Usage {
  input_tokens : Int?
  output_tokens : Int?
  total_tokens : Int?
} derive(Eq, Debug)

///|
pub impl Show for Usage with fn to_string(self : Usage) -> String {
  let fmt = fn(opt : Int?) -> String {
    match opt {
      Some(n) => n.to_string()
      None => "?"
    }
  }
  "Usage(in=\{fmt(self.input_tokens)}, out=\{fmt(self.output_tokens)}, total=\{fmt(self.total_tokens)})"
}

///|
/// A completed assistant step. The `message` field is the canonical assistant
/// message that the reducer appends to the transcript; tool calls and reasoning
/// live there (single source of truth, ADR §2.11). The `usage` field reports
/// token consumption observed for this step, which the reducer may aggregate
/// into the Run budget.
///
/// Construction note: do NOT build this by hand in production code. The
/// `from_assistant_message` constructor enforces the invariant that the message
/// payload is consistent (assistant role, no `ToolMessage` shape, etc.).
pub(all) struct Completion {
  message : CompletionPayload
  usage : Usage?
} derive(Eq, Debug)

///|
/// Re-flattened assistant payload used inside `Completion`. We cannot
/// reuse `Message::AssistantMessage` directly because that variant
/// already carries `finish_reason`; the completion's `message` field is the
/// transcript entry, while the `usage` lives at the completion level. This
/// struct mirrors only the fields that travel together as the assistant's
/// contribution to a single step.
pub(all) struct CompletionPayload {
  content : Array[Content]
  tool_calls : Array[ToolCall]
  reasoning : Reasoning?
  finish_reason : FinishReason
} derive(Eq, Debug)

///|
/// Build an `Completion` from its parts. The reducer constructs this
/// from a `ModelCompleted` input after parsing the model adapter's wire
/// representation.
pub fn Completion::Completion(
  content~ : Array[Content],
  tool_calls~ : Array[ToolCall],
  reasoning~ : Reasoning?,
  finish_reason~ : FinishReason,
  usage~ : Usage?,
) -> Completion {
  let message : CompletionPayload = {
    content: content.copy(),
    tool_calls: tool_calls.copy(),
    reasoning,
    finish_reason,
  }
  { message, usage }
}

///|
/// Convenience: does this completion request any tool calls?
pub fn Completion::has_tool_calls(self : Completion) -> Bool {
  !self.message.tool_calls.is_empty()
}

///|
/// Returns the tool calls in source order (already a copy of the internal
/// array).
pub fn Completion::tool_calls(self : Completion) -> Array[ToolCall] {
  self.message.tool_calls.copy()
}

///|
/// Returns the finish reason. `Length` is preserved as a distinct variant so
/// the reducer can refuse to emit `ExecuteTool` effects for a truncated
/// completion (ADR §2.7, M0 `StreamAccumulator::to_response` behaviour).
pub fn Completion::finish_reason(self : Completion) -> FinishReason {
  self.message.finish_reason
}

///|
/// Categorises why a model step failed (ADR §2.4). The reducer maps this to a
/// `FailureReason::Model*` variant. Each variant carries a safe bounded label,
/// never a raw provider payload.
pub(all) enum ModelFailure {
  RequestBuild(reason~ : String)
  Transport(reason~ : String)
  Runtime(reason~ : String)
  Parse(reason~ : String)
  /// The host rejected a reducer-declared model effect before invoking the
  /// provider (for example, an explicit policy gate). This is deliberately
  /// distinct from provider failures so callers can preserve the rejection's
  /// typed meaning instead of string-matching a model error.
  HostRejected(reason~ : String)
} derive(Eq, Debug)

///|
pub impl Show for ModelFailure with fn to_string(self : ModelFailure) -> String {
  match self {
    RequestBuild(reason~) =>
      "ModelFailure::RequestBuild(\{safe_model_label(reason)})"
    Transport(reason~) => "ModelFailure::Transport(\{safe_model_label(reason)})"
    Runtime(reason~) => "ModelFailure::Runtime(\{safe_model_label(reason)})"
    Parse(reason~) => "ModelFailure::Parse(\{safe_model_label(reason)})"
    HostRejected(reason~) =>
      "ModelFailure::HostRejected(\{safe_model_label(reason)})"
  }
}

///|
fn safe_model_label(value : String) -> String {
  let chars : Array[Char] = []
  let mut truncated = false
  for char in value {
    if chars.length() >= 64 {
      truncated = true
      break
    }
    match char {
      '\n' | '\r' => chars.push(' ')
      other => chars.push(other)
    }
  }
  let label = String::from_array(chars)
  if truncated {
    label + "...(truncated)"
  } else {
    label
  }
}