// methods.mbt — upstream `src/methods.ts` mirror: the `LODY_EXTENSION_METHODS`
// wire names (7 requests + 3 notifications), `normalizeLodyExtensionMethod`,
// and every request/response/notification DTO the contract binds to those
// methods. `LodySubagentTask` itself lives in session.mbt (upstream
// `src/session.ts`).

///|
pub const LODY_RATE_LIMITS_GET : String = "_lody/rate_limits/get"

///|
pub const LODY_SESSION_STEER : String = "_lody/session/steer"

///|
pub const LODY_SESSION_GOAL : String = "_lody/session/goal"

///|
pub const LODY_SESSION_HISTORY_READ : String = "_lody/session/history/read"

///|
pub const LODY_SUBAGENTS_LIST : String = "_lody/subagents/list"

///|
pub const LODY_SUBAGENTS_CANCEL : String = "_lody/subagents/cancel"

///|
pub const LODY_SUBAGENTS_OUTPUT : String = "_lody/subagents/output"

///|
pub const LODY_SESSION_USAGE_UPDATE : String = "_lody/session/usage_update"

///|
pub const LODY_RATE_LIMITS_UPDATE : String = "_lody/rate_limits/update"

///|
pub const LODY_SESSION_STEER_APPLIED : String = "_lody/session/steer_applied"

///|
/// All ten `LODY_EXTENSION_METHODS` wire names, upstream declaration order
/// (7 requests then 3 notifications).
pub fn lody_extension_methods() -> Array[String] {
  [
    LODY_RATE_LIMITS_GET,
    LODY_SESSION_STEER,
    LODY_SESSION_GOAL,
    LODY_SESSION_HISTORY_READ,
    LODY_SUBAGENTS_LIST,
    LODY_SUBAGENTS_CANCEL,
    LODY_SUBAGENTS_OUTPUT,
    LODY_SESSION_USAGE_UPDATE,
    LODY_RATE_LIMITS_UPDATE,
    LODY_SESSION_STEER_APPLIED,
  ]
}

///|
/// `normalizeLodyExtensionMethod`: prepend the `_` namespace prefix when the
/// method does not already start with one.
pub fn normalize_lody_extension_method(method_name : String) -> String {
  if method_name.has_prefix("_") {
    method_name
  } else {
    "_" + method_name
  }
}

///|
/// `LodySteerRequest` — `_lody/session/steer` params. The prompt element
/// content shape is unconstrained upstream (generic `TPrompt`); it passes
/// through as opaque JSON — awaiting on-wire verification.
pub(all) struct LodySteerRequest {
  session_id : String
  prompt : Array[Json]
  steer_id : String
} derive(Debug)

///|
pub fn LodySteerRequest::to_json(self : LodySteerRequest) -> Json {
  Json::object(
    Map::from_array([
      ("sessionId", Json::string(self.session_id)),
      ("prompt", Json::array(self.prompt)),
      ("steerId", Json::string(self.steer_id)),
    ]),
  )
}

///|
pub fn LodySteerRequest::from_json(
  raw : Json,
) -> Result[LodySteerRequest, String] {
  try {
    let ctx = "lody.steerRequest"
    let fields = object_fields(raw, ctx)
    Ok(LodySteerRequest::{
      session_id: req_string(fields, ctx, "sessionId"),
      prompt: req_array(fields, ctx, "prompt"),
      steer_id: req_string(fields, ctx, "steerId"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySteerResponse` — `_lody/session/steer` result.
pub(all) struct LodySteerResponse {
  outcome : LodySteerOutcome
} derive(Eq, Debug)

///|
pub fn LodySteerResponse::to_json(self : LodySteerResponse) -> Json {
  Json::object(
    Map::from_array([("outcome", Json::string(self.outcome.to_wire()))]),
  )
}

///|
pub fn LodySteerResponse::from_json(
  raw : Json,
) -> Result[LodySteerResponse, String] {
  try {
    let ctx = "lody.steerResponse"
    let fields = object_fields(raw, ctx)
    Ok(LodySteerResponse::{
      outcome: wire_of(
        ctx,
        "outcome",
        LodySteerOutcome::of_wire(req_string(fields, ctx, "outcome")),
      ),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySteerApplied` — `_lody/session/steer_applied` notification payload.
pub(all) struct LodySteerApplied {
  session_id : String
  steer_id : String
} derive(Eq, Debug)

///|
pub fn LodySteerApplied::to_json(self : LodySteerApplied) -> Json {
  Json::object(
    Map::from_array([
      ("sessionId", Json::string(self.session_id)),
      ("steerId", Json::string(self.steer_id)),
    ]),
  )
}

///|
pub fn LodySteerApplied::from_json(
  raw : Json,
) -> Result[LodySteerApplied, String] {
  try {
    let ctx = "lody.steerApplied"
    let fields = object_fields(raw, ctx)
    Ok(LodySteerApplied::{
      session_id: req_string(fields, ctx, "sessionId"),
      steer_id: req_string(fields, ctx, "steerId"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodyGoalControlRequest` — `_lody/session/goal` params, discriminated on
/// `action`: `set` carries an objective; pause/resume/clear do not.
pub(all) enum LodyGoalControlRequest {
  Set(session_id~ : String, objective~ : String)
  Pause(session_id~ : String)
  Resume(session_id~ : String)
  Clear(session_id~ : String)
} derive(Eq, Debug)

///|
pub fn LodyGoalControlRequest::to_json(self : LodyGoalControlRequest) -> Json {
  match self {
    Set(session_id~, objective~) =>
      Json::object(
        Map::from_array([
          ("action", Json::string("set")),
          ("sessionId", Json::string(session_id)),
          ("objective", Json::string(objective)),
        ]),
      )
    Pause(session_id~) =>
      Json::object(
        Map::from_array([
          ("action", Json::string("pause")),
          ("sessionId", Json::string(session_id)),
        ]),
      )
    Resume(session_id~) =>
      Json::object(
        Map::from_array([
          ("action", Json::string("resume")),
          ("sessionId", Json::string(session_id)),
        ]),
      )
    Clear(session_id~) =>
      Json::object(
        Map::from_array([
          ("action", Json::string("clear")),
          ("sessionId", Json::string(session_id)),
        ]),
      )
  }
}

///|
pub fn LodyGoalControlRequest::from_json(
  raw : Json,
) -> Result[LodyGoalControlRequest, String] {
  try {
    let ctx = "lody.goalControl"
    let fields = object_fields(raw, ctx)
    match req_string(fields, ctx, "action") {
      "set" =>
        Ok(
          Set(
            session_id=req_string(fields, ctx, "sessionId"),
            objective=req_string(fields, ctx, "objective"),
          ),
        )
      "pause" => Ok(Pause(session_id=req_string(fields, ctx, "sessionId")))
      "resume" => Ok(Resume(session_id=req_string(fields, ctx, "sessionId")))
      "clear" => Ok(Clear(session_id=req_string(fields, ctx, "sessionId")))
      action =>
        raise DecodeError::Msg(
          "\{ctx}: field \"action\": unknown lody goal action: \"\{action}\"",
        )
    }
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodyGoalControlResponse` — `_lody/session/goal` result; `goal` is a
/// required key that is nullable upstream: `null` when no goal is currently
/// set (a missing key is a decode error).
pub(all) struct LodyGoalControlResponse {
  goal : LodyGoalSnapshot?
} derive(Eq, Debug)

///|
pub fn LodyGoalControlResponse::to_json(self : LodyGoalControlResponse) -> Json {
  match self.goal {
    Some(goal) => Json::object(Map::from_array([("goal", goal.to_json())]))
    None => Json::object(Map::from_array([("goal", Json::null())]))
  }
}

///|
pub fn LodyGoalControlResponse::from_json(
  raw : Json,
) -> Result[LodyGoalControlResponse, String] {
  try {
    let ctx = "lody.goalControlResponse"
    let fields = object_fields(raw, ctx)
    Ok(LodyGoalControlResponse::{
      goal: req_sub_nullable(fields, ctx, "goal", LodyGoalSnapshot::from_json),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySessionHistoryReadRequest` — `_lody/session/history/read` params.
pub(all) struct LodySessionHistoryReadRequest {
  session_id : String
} derive(Eq, Debug)

///|
pub fn LodySessionHistoryReadRequest::to_json(
  self : LodySessionHistoryReadRequest,
) -> Json {
  Json::object(Map::from_array([("sessionId", Json::string(self.session_id))]))
}

///|
pub fn LodySessionHistoryReadRequest::from_json(
  raw : Json,
) -> Result[LodySessionHistoryReadRequest, String] {
  try {
    let ctx = "lody.historyRead.request"
    let fields = object_fields(raw, ctx)
    Ok(LodySessionHistoryReadRequest::{
      session_id: req_string(fields, ctx, "sessionId"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySessionHistoryReadResponse` — an empty object upstream. How the
/// actual history content is carried is missing from the contract (likely a
/// placeholder) — awaiting on-wire verification. Decoding tolerates and
/// ignores any extra fields.
pub(all) struct LodySessionHistoryReadResponse {} derive(Eq, Debug)

///|
pub fn LodySessionHistoryReadResponse::to_json(
  _self : LodySessionHistoryReadResponse,
) -> Json {
  Json::object(Map::from_array([]))
}

///|
pub fn LodySessionHistoryReadResponse::from_json(
  raw : Json,
) -> Result[LodySessionHistoryReadResponse, String] {
  try {
    let _ = object_fields(raw, "lody.historyRead.response")
    Ok(LodySessionHistoryReadResponse::{ })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySubagentsListRequest` — `_lody/subagents/list` params.
pub(all) struct LodySubagentsListRequest {
  session_id : String
  active_only : Bool?
} derive(Eq, Debug)

///|
pub fn LodySubagentsListRequest::to_json(
  self : LodySubagentsListRequest,
) -> Json {
  let pairs : Array[(String, Json)] = [
    ("sessionId", Json::string(self.session_id)),
  ]
  match self.active_only {
    Some(v) => pairs.push(("activeOnly", Json::boolean(v)))
    None => ()
  }
  Json::object(Map::from_array(pairs))
}

///|
pub fn LodySubagentsListRequest::from_json(
  raw : Json,
) -> Result[LodySubagentsListRequest, String] {
  try {
    let ctx = "lody.subagents.list.request"
    let fields = object_fields(raw, ctx)
    Ok(LodySubagentsListRequest::{
      session_id: req_string(fields, ctx, "sessionId"),
      active_only: opt_bool(fields, ctx, "activeOnly"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySubagentsListResponse` — `_lody/subagents/list` result.
pub(all) struct LodySubagentsListResponse {
  tasks : Array[LodySubagentTask]
} derive(Eq, Debug)

///|
pub fn LodySubagentsListResponse::to_json(
  self : LodySubagentsListResponse,
) -> Json {
  Json::object(
    Map::from_array([
      ("tasks", Json::array(self.tasks.map(fn(task) { task.to_json() }))),
    ]),
  )
}

///|
pub fn LodySubagentsListResponse::from_json(
  raw : Json,
) -> Result[LodySubagentsListResponse, String] {
  try {
    let ctx = "lody.subagents.list.response"
    let fields = object_fields(raw, ctx)
    let tasks : Array[LodySubagentTask] = req_array(fields, ctx, "tasks").map(item => {
      match LodySubagentTask::from_json(item) {
        Ok(task) => task
        Err(message) => raise DecodeError::Msg(message)
      }
    })
    Ok(LodySubagentsListResponse::{ tasks, })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySubagentCancelRequest` — `_lody/subagents/cancel` params.
pub(all) struct LodySubagentCancelRequest {
  session_id : String
  task_id : String
  reason : String?
} derive(Eq, Debug)

///|
pub fn LodySubagentCancelRequest::to_json(
  self : LodySubagentCancelRequest,
) -> Json {
  let pairs : Array[(String, Json)] = [
    ("sessionId", Json::string(self.session_id)),
    ("taskId", Json::string(self.task_id)),
  ]
  match self.reason {
    Some(v) => pairs.push(("reason", Json::string(v)))
    None => ()
  }
  Json::object(Map::from_array(pairs))
}

///|
pub fn LodySubagentCancelRequest::from_json(
  raw : Json,
) -> Result[LodySubagentCancelRequest, String] {
  try {
    let ctx = "lody.subagents.cancel.request"
    let fields = object_fields(raw, ctx)
    Ok(LodySubagentCancelRequest::{
      session_id: req_string(fields, ctx, "sessionId"),
      task_id: req_string(fields, ctx, "taskId"),
      reason: opt_string(fields, ctx, "reason"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySubagentCancelResponse` — an empty object upstream. Decoding
/// tolerates and ignores any extra fields.
pub(all) struct LodySubagentCancelResponse {} derive(Eq, Debug)

///|
pub fn LodySubagentCancelResponse::to_json(
  _self : LodySubagentCancelResponse,
) -> Json {
  Json::object(Map::from_array([]))
}

///|
pub fn LodySubagentCancelResponse::from_json(
  raw : Json,
) -> Result[LodySubagentCancelResponse, String] {
  try {
    let _ = object_fields(raw, "lody.subagents.cancel.response")
    Ok(LodySubagentCancelResponse::{ })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySubagentOutputRequest` — `_lody/subagents/output` params.
pub(all) struct LodySubagentOutputRequest {
  session_id : String
  task_id : String
  tail : Int?
} derive(Eq, Debug)

///|
pub fn LodySubagentOutputRequest::to_json(
  self : LodySubagentOutputRequest,
) -> Json {
  let pairs : Array[(String, Json)] = [
    ("sessionId", Json::string(self.session_id)),
    ("taskId", Json::string(self.task_id)),
  ]
  match self.tail {
    Some(v) => pairs.push(("tail", Json::number(v.to_double())))
    None => ()
  }
  Json::object(Map::from_array(pairs))
}

///|
pub fn LodySubagentOutputRequest::from_json(
  raw : Json,
) -> Result[LodySubagentOutputRequest, String] {
  try {
    let ctx = "lody.subagents.output.request"
    let fields = object_fields(raw, ctx)
    Ok(LodySubagentOutputRequest::{
      session_id: req_string(fields, ctx, "sessionId"),
      task_id: req_string(fields, ctx, "taskId"),
      tail: opt_int(fields, ctx, "tail"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}

///|
/// `LodySubagentOutputResponse` — `_lody/subagents/output` result.
pub(all) struct LodySubagentOutputResponse {
  output : String
} derive(Eq, Debug)

///|
pub fn LodySubagentOutputResponse::to_json(
  self : LodySubagentOutputResponse,
) -> Json {
  Json::object(Map::from_array([("output", Json::string(self.output))]))
}

///|
pub fn LodySubagentOutputResponse::from_json(
  raw : Json,
) -> Result[LodySubagentOutputResponse, String] {
  try {
    let ctx = "lody.subagents.output.response"
    let fields = object_fields(raw, ctx)
    Ok(LodySubagentOutputResponse::{
      output: req_string(fields, ctx, "output"),
    })
  } catch {
    DecodeError::Msg(message) => Err(message)
  }
}