// capabilities.mbt — upstream `src/capabilities.ts` mirror: the
// `LodyExtensionCapabilities` advertisement mounted at
// `InitializeResponse.agentCapabilities._meta.lody`. Nine optional feature
// keys; each capability carries its own integer `version` (all version 1 in
// this contract revision), sharing the `LodyVersionOneCapability` base.
//
// Wire key naming: the upstream repo ships TypeScript types only and gives no
// evidence for on-wire JSON key serialization, so the TS camelCase keys are
// emitted verbatim — awaiting on-wire verification.

///|
/// `LodyVersionOneCapability` — the `{ version: 1 }` base shared by every
/// feature key.
pub(all) struct LodyVersionOneCapability {
  version : Int
} derive(Eq, Debug)

///|
pub fn LodyVersionOneCapability::version_one() -> LodyVersionOneCapability {
  LodyVersionOneCapability::{ version: 1, }
}

///|
pub fn LodyVersionOneCapability::to_json(
  self : LodyVersionOneCapability,
) -> Json {
  Json::object(
    Map::from_array([("version", Json::number(self.version.to_double()))]),
  )
}

///|
pub fn LodyVersionOneCapability::from_json(
  raw : Json,
) -> Result[LodyVersionOneCapability, String] {
  try {
    let ctx = "lody.capabilities.versionOne"
    let fields = object_fields(raw, ctx)
    let version = req_int(fields, ctx, "version")
    guard version == 1 else {
      raise DecodeError::Msg(
        "\{ctx}: unsupported version \{version} (expected 1)",
      )
    }
    Ok(LodyVersionOneCapability::{ version, })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodyRateLimitsCapability` — supports rate-limit notifications;
/// `query: true` additionally advertises that `_lody/rate_limits/get` is
/// answerable.
pub(all) struct LodyRateLimitsCapability {
  version : Int
  query : Bool?
} derive(Eq, Debug)

///|
pub fn LodyRateLimitsCapability::to_json(
  self : LodyRateLimitsCapability,
) -> Json {
  let pairs : Array[(String, Json)] = [
    ("version", Json::number(self.version.to_double())),
  ]
  match self.query {
    Some(v) => pairs.push(("query", Json::boolean(v)))
    None => ()
  }
  Json::object(Map::from_array(pairs))
}

///|
pub fn LodyRateLimitsCapability::from_json(
  raw : Json,
) -> Result[LodyRateLimitsCapability, String] {
  try {
    let ctx = "lody.capabilities.rateLimits"
    let fields = object_fields(raw, ctx)
    let version = req_int(fields, ctx, "version")
    guard version == 1 else {
      raise DecodeError::Msg(
        "\{ctx}: unsupported version \{version} (expected 1)",
      )
    }
    Ok(LodyRateLimitsCapability::{
      version,
      query: opt_bool(fields, ctx, "query"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySteeringCapability` — supports `_lody/session/steer`; declares the
/// transport, upstream-turn policy, and config-effectiveness policy.
pub(all) struct LodySteeringCapability {
  version : Int
  transport : LodySteeringTransport
  upstream_turn : LodySteeringUpstreamTurn
  config_policy : LodySteeringConfigPolicy
} derive(Eq, Debug)

///|
pub fn LodySteeringCapability::to_json(self : LodySteeringCapability) -> Json {
  Json::object(
    Map::from_array([
      ("version", Json::number(self.version.to_double())),
      ("transport", Json::string(self.transport.to_wire())),
      ("upstreamTurn", Json::string(self.upstream_turn.to_wire())),
      ("configPolicy", Json::string(self.config_policy.to_wire())),
    ]),
  )
}

///|
pub fn LodySteeringCapability::from_json(
  raw : Json,
) -> Result[LodySteeringCapability, String] {
  try {
    let ctx = "lody.capabilities.steering"
    let fields = object_fields(raw, ctx)
    let version = req_int(fields, ctx, "version")
    guard version == 1 else {
      raise DecodeError::Msg(
        "\{ctx}: unsupported version \{version} (expected 1)",
      )
    }
    Ok(LodySteeringCapability::{
      version,
      transport: wire_of(
        ctx,
        "transport",
        LodySteeringTransport::of_wire(req_string(fields, ctx, "transport")),
      ),
      upstream_turn: wire_of(
        ctx,
        "upstreamTurn",
        LodySteeringUpstreamTurn::of_wire(
          req_string(fields, ctx, "upstreamTurn"),
        ),
      ),
      config_policy: wire_of(
        ctx,
        "configPolicy",
        LodySteeringConfigPolicy::of_wire(
          req_string(fields, ctx, "configPolicy"),
        ),
      ),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodyTaskCapability` — tool_call envelopes may carry `_meta.lody.task`;
/// `background` / `scheduled` declare the supported task subclasses.
pub(all) struct LodyTaskCapability {
  version : Int
  background : Bool?
  scheduled : Bool?
} derive(Eq, Debug)

///|
pub fn LodyTaskCapability::to_json(self : LodyTaskCapability) -> Json {
  let pairs : Array[(String, Json)] = [
    ("version", Json::number(self.version.to_double())),
  ]
  match self.background {
    Some(v) => pairs.push(("background", Json::boolean(v)))
    None => ()
  }
  match self.scheduled {
    Some(v) => pairs.push(("scheduled", Json::boolean(v)))
    None => ()
  }
  Json::object(Map::from_array(pairs))
}

///|
pub fn LodyTaskCapability::from_json(
  raw : Json,
) -> Result[LodyTaskCapability, String] {
  try {
    let ctx = "lody.capabilities.tasks"
    let fields = object_fields(raw, ctx)
    let version = req_int(fields, ctx, "version")
    guard version == 1 else {
      raise DecodeError::Msg(
        "\{ctx}: unsupported version \{version} (expected 1)",
      )
    }
    Ok(LodyTaskCapability::{
      version,
      background: opt_bool(fields, ctx, "background"),
      scheduled: opt_bool(fields, ctx, "scheduled"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySubagentCapability` — supports subagent lifecycle (`lifecycle` is
/// required); `list` / `cancel` / `output` declare the three query methods.
pub(all) struct LodySubagentCapability {
  version : Int
  lifecycle : Bool
  list : Bool?
  cancel : Bool?
  output : Bool?
} derive(Eq, Debug)

///|
pub fn LodySubagentCapability::to_json(self : LodySubagentCapability) -> Json {
  let pairs : Array[(String, Json)] = [
    ("version", Json::number(self.version.to_double())),
    ("lifecycle", Json::boolean(self.lifecycle)),
  ]
  match self.list {
    Some(v) => pairs.push(("list", Json::boolean(v)))
    None => ()
  }
  match self.cancel {
    Some(v) => pairs.push(("cancel", Json::boolean(v)))
    None => ()
  }
  match self.output {
    Some(v) => pairs.push(("output", Json::boolean(v)))
    None => ()
  }
  Json::object(Map::from_array(pairs))
}

///|
pub fn LodySubagentCapability::from_json(
  raw : Json,
) -> Result[LodySubagentCapability, String] {
  try {
    let ctx = "lody.capabilities.subagents"
    let fields = object_fields(raw, ctx)
    let version = req_int(fields, ctx, "version")
    guard version == 1 else {
      raise DecodeError::Msg(
        "\{ctx}: unsupported version \{version} (expected 1)",
      )
    }
    Ok(LodySubagentCapability::{
      version,
      lifecycle: req_bool(fields, ctx, "lifecycle"),
      list: opt_bool(fields, ctx, "list"),
      cancel: opt_bool(fields, ctx, "cancel"),
      output: opt_bool(fields, ctx, "output"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodyGoalCapability` — supports goal management and `_lody/session/goal`;
/// `actions` declares the supported verb set.
pub(all) struct LodyGoalCapability {
  version : Int
  actions : Array[LodyGoalAction]
} derive(Eq, Debug)

///|
pub fn LodyGoalCapability::to_json(self : LodyGoalCapability) -> Json {
  Json::object(
    Map::from_array([
      ("version", Json::number(self.version.to_double())),
      (
        "actions",
        Json::array(
          self.actions.map(fn(action) { Json::string(action.to_wire()) }),
        ),
      ),
    ]),
  )
}

///|
pub fn LodyGoalCapability::from_json(
  raw : Json,
) -> Result[LodyGoalCapability, String] {
  try {
    let ctx = "lody.capabilities.goal"
    let fields = object_fields(raw, ctx)
    let version = req_int(fields, ctx, "version")
    guard version == 1 else {
      raise DecodeError::Msg(
        "\{ctx}: unsupported version \{version} (expected 1)",
      )
    }
    let actions : Array[LodyGoalAction] = []
    for item in req_array(fields, ctx, "actions") {
      match item {
        Json::String(wire) =>
          match LodyGoalAction::of_wire(wire) {
            Ok(action) => actions.push(action)
            Err(message) =>
              raise DecodeError::Msg("\{ctx}: field \"actions\": \{message}")
          }
        _ =>
          raise DecodeError::Msg(
            "\{ctx}: field \"actions\" must contain strings",
          )
      }
    }
    Ok(LodyGoalCapability::{ version, actions, })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodyExtensionCapabilities` — the `_meta.lody` value under
/// `agentCapabilities`. Absent features are simply not advertised; `to_json`
/// emits only the present feature keys, in upstream declaration order.
pub(all) struct LodyExtensionCapabilities {
  usage : LodyVersionOneCapability?
  rate_limits : LodyRateLimitsCapability?
  fork_at_turn : LodyVersionOneCapability?
  steering : LodySteeringCapability?
  tasks : LodyTaskCapability?
  subagents : LodySubagentCapability?
  goal : LodyGoalCapability?
  compaction : LodyVersionOneCapability?
  session_history : LodyVersionOneCapability?
} derive(Eq, Debug)

///|
pub fn LodyExtensionCapabilities::empty() -> LodyExtensionCapabilities {
  LodyExtensionCapabilities::{
    usage: None,
    rate_limits: None,
    fork_at_turn: None,
    steering: None,
    tasks: None,
    subagents: None,
    goal: None,
    compaction: None,
    session_history: None,
  }
}

///|
pub fn LodyExtensionCapabilities::to_json(
  self : LodyExtensionCapabilities,
) -> Json {
  let pairs : Array[(String, Json)] = []
  match self.usage {
    Some(v) => pairs.push(("usage", v.to_json()))
    None => ()
  }
  match self.rate_limits {
    Some(v) => pairs.push(("rateLimits", v.to_json()))
    None => ()
  }
  match self.fork_at_turn {
    Some(v) => pairs.push(("forkAtTurn", v.to_json()))
    None => ()
  }
  match self.steering {
    Some(v) => pairs.push(("steering", v.to_json()))
    None => ()
  }
  match self.tasks {
    Some(v) => pairs.push(("tasks", v.to_json()))
    None => ()
  }
  match self.subagents {
    Some(v) => pairs.push(("subagents", v.to_json()))
    None => ()
  }
  match self.goal {
    Some(v) => pairs.push(("goal", v.to_json()))
    None => ()
  }
  match self.compaction {
    Some(v) => pairs.push(("compaction", v.to_json()))
    None => ()
  }
  match self.session_history {
    Some(v) => pairs.push(("sessionHistory", v.to_json()))
    None => ()
  }
  Json::object(Map::from_array(pairs))
}

///|
pub fn LodyExtensionCapabilities::from_json(
  raw : Json,
) -> Result[LodyExtensionCapabilities, String] {
  try {
    let ctx = "lody.capabilities"
    let fields = object_fields(raw, ctx)
    Ok(LodyExtensionCapabilities::{
      usage: opt_sub(fields, ctx, "usage", LodyVersionOneCapability::from_json),
      rate_limits: opt_sub(
        fields,
        ctx,
        "rateLimits",
        LodyRateLimitsCapability::from_json,
      ),
      fork_at_turn: opt_sub(
        fields,
        ctx,
        "forkAtTurn",
        LodyVersionOneCapability::from_json,
      ),
      steering: opt_sub(
        fields,
        ctx,
        "steering",
        LodySteeringCapability::from_json,
      ),
      tasks: opt_sub(fields, ctx, "tasks", LodyTaskCapability::from_json),
      subagents: opt_sub(
        fields,
        ctx,
        "subagents",
        LodySubagentCapability::from_json,
      ),
      goal: opt_sub(fields, ctx, "goal", LodyGoalCapability::from_json),
      compaction: opt_sub(
        fields,
        ctx,
        "compaction",
        LodyVersionOneCapability::from_json,
      ),
      session_history: opt_sub(
        fields,
        ctx,
        "sessionHistory",
        LodyVersionOneCapability::from_json,
      ),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}