///|
/// Canonical tool-definition types for the functional Kernel.
///
/// ADR §2.7, §2.8, §2.11: the catalog snapshot is the single source of truth
/// for tool definitions (model-visible) AND for execution owners. The
/// reducer/executor never re-resolves owners from a separate routing cache.
/// Tool execution policy is explicit data — not inferred from the tool name.

///|
/// How a tool may be batched with other tools in the same model completion.
///
/// - `Sequential`: occupies a single-element wave. Safe default; file/shell
///   side effects never race.
/// - `Parallel`: may share a wave with adjacent `Parallel` calls.
/// - `Exclusive`: forms a barrier before and after; never shares a wave.
///
/// `Sequential` is the safe default. Products opt into `Parallel` only for
/// tools whose effects are independently safe, and use `Exclusive` at a
/// visible side-effect boundary.
#derive(Eq, Debug)
pub(all) enum ExecutionPolicy {
  Sequential
  Parallel
  Exclusive
}

///|
pub extend ExecutionPolicy with Eq::{not_equal, equal}

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

///|
pub extend ExecutionPolicy with Show::{output}

///|
pub impl Show for ExecutionPolicy with fn to_string(self : ExecutionPolicy) -> String {
  match self {
    Sequential => "Sequential"
    Parallel => "Parallel"
    Exclusive => "Exclusive"
  }
}

///|
/// Stable identifier for the provider that owns a tool. The Kernel uses this
/// only for owner lookup in the runtime registry; it does not call into the
/// owner through this type.
#derive(Eq, Hash, Debug)
pub(all) struct OwnerId {
  value : String
}

///|
pub extend OwnerId with Eq::{not_equal, equal}

///|
pub extend OwnerId with Hash::{hash, hash_combine}

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

///|
pub fn OwnerId::checked(value : String) -> Result[OwnerId, String] {
  if value == "" {
    Err("OwnerId must be non-empty")
  } else {
    Ok({ value, })
  }
}

///|
pub fn OwnerId::unchecked(value : String) -> OwnerId {
  { value, }
}

///|
pub fn OwnerId::to_string(self : OwnerId) -> String {
  self.value
}

///|
/// Exposure is captured once per operation. Hooks may only narrow it.
#derive(Eq, Debug)
pub(all) enum ToolExposure {
  Direct
  Deferred
  Disabled
}

///|
pub extend ToolExposure with Eq::{equal, not_equal}

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

///|
pub fn ToolExposure::narrow(
  self : ToolExposure,
  other : ToolExposure,
) -> ToolExposure {
  match (self, other) {
    (Disabled, _) | (_, Disabled) => Disabled
    (Deferred, _) | (_, Deferred) => Deferred
    _ => Direct
  }
}

///|
/// A single tool's definition as the model and the executor see it. Owned by
/// the catalog snapshot (T05), not by individual providers.
#derive(Eq, Debug)
pub(all) struct ToolDef {
  name : ToolName
  description : String
  input_schema : Json
  owner : OwnerId
  policy : ExecutionPolicy
  exposure : ToolExposure
  /// Optional provenance label (e.g. provider name, MCP server id). Included
  /// in collision diagnostics; never used for routing decisions.
  provenance : String?
}

///|
pub extend ToolDef with Eq::{not_equal, equal}

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

///|
/// Internal builder used by the catalog. Most fields are required; provenance
/// is optional. The catalog builder (T05) validates the schema and policy
/// before constructing this, so by the time a `ToolDef` reaches the
/// reducer, it has already passed composition.
pub fn ToolDef::ToolDef(
  name~ : ToolName,
  description~ : String,
  input_schema~ : Json,
  owner~ : OwnerId,
  policy~ : ExecutionPolicy,
  provenance~ : String?,
  exposure~ : ToolExposure = Direct,
) -> ToolDef {
  { name, description, input_schema, owner, policy, provenance, exposure, }
}

///|
pub extend ToolDef with Show::{to_string, output}

///|
pub impl Show for ToolDef with fn to_string(self : ToolDef) -> String {
  "Tool(\{self.name.to_string()} @ \{self.owner.to_string()}, policy=\{self.policy.to_string()})"
}

///|
/// Result of validating parsed tool arguments against a `ToolDef`'s
/// input schema. The validator (T05) returns this; the reducer (T03) maps
/// `Err` into a `ToolOutcome::NotExecuted(SchemaMismatch)` carrying the
/// original call id.
///
/// The diagnostic deliberately excludes the raw argument payload; it reports
/// only a JSON path and a type expectation, so that prompts and tool arguments
/// never leak into logs.
#derive(Eq, Debug)
pub(all) struct SchemaDiagnostic {
  json_path : String
  expected : String
  actual : String
}

///|
pub extend SchemaDiagnostic with Eq::{not_equal, equal}

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

///|
pub extend SchemaDiagnostic with Show::{to_string, output}

///|
pub impl Show for SchemaDiagnostic with fn to_string(self : SchemaDiagnostic) -> String {
  "SchemaDiagnostic(path=\{self.json_path}, expected=\{self.expected}, actual=\{self.actual})"
}