///|
/// Runtime-facing commands emitted by the Agent adapter.  These values are
/// intents only; the adapter never owns tasks, queues, or connection state.
pub(all) enum AgentAdapterCommand {
  CancelRequest(request_id~ : RequestId)
  TraceError(method_name~ : String, error~ : JsonRpcError)
  CloseConnection(reason~ : String)
} derive(Eq, Debug)

///|
/// Typed adapter output.  Responses and notifications are wire values that
/// the owner loop serializes; commands are interpreted by the native runtime.
pub(all) enum AgentAdapterOutput {
  Response(JsonRpcResponse)
  Notification(JsonRpcNotification)
  Command(AgentAdapterCommand)
} derive(Eq, Debug)

///|
/// Authentication/lifecycle reservations are immutable protocol state.  A
/// pending reservation prevents a second state-changing request from using a
/// stale snapshot while its first handler is still running.
pub(all) enum AgentAdapterReservation {
  Idle
  AuthenticatePending(RequestId)
  LogoutPending(RequestId)
} derive(Eq, Debug)

///|
/// State owned by the connection/reducer loop.  Handler tasks receive a copy
/// of this value only through an admission result; they never mutate it.
pub(all) struct AgentAdapterState {
  protocol : AgentProtocolState
  reservation : AgentAdapterReservation
} derive(Eq, Debug)

///|
pub fn agent_adapter_state_new(
  protocol~ : AgentProtocolState,
) -> AgentAdapterState {
  { protocol, reservation: Idle }
}

///|
pub fn AgentAdapterState::protocol(
  self : AgentAdapterState,
) -> AgentProtocolState {
  self.protocol
}

///|
pub fn AgentAdapterState::reservation(
  self : AgentAdapterState,
) -> AgentAdapterReservation {
  self.reservation
}

///|
/// One explicit state transition produced by admission or completion.
pub(all) struct AgentAdapterStep {
  state : AgentAdapterState
  outputs : Array[AgentAdapterOutput]
} derive(Eq, Debug)

///|
/// Admission is synchronous and owner-loop controlled.  Initialize and
/// protocol errors are immediate; only typed invocations cross the async task
/// boundary.
pub(all) enum AgentAdapterAdmission {
  Immediate(AgentAdapterStep)
  Invoke(state~ : AgentAdapterState, invocation~ : AgentAdapterInvocation)
}

///|
/// Fail-fast adapter failures indicate an invalid composition boundary or a
/// completion that no longer belongs to the owner-held reservation.
pub(all) suberror AgentAdapterError {
  EndpointMismatch(reason~ : String)
  UnexpectedMessage(kind~ : String)
  LateCompletion(request_id~ : RequestId)
  CompletionMismatch(method_name~ : String)
} derive(Eq, Debug)

///|
fn agent_adapter_response_id(id : RequestId) -> JsonRpcId {
  match id {
    String(value) => String(value)
    Number(value) => Number(value)
    Null => Null
  }
}

///|
fn agent_adapter_response(
  state : AgentAdapterState,
  id : RequestId,
  error : JsonRpcError,
) -> AgentAdapterStep {
  {
    state,
    outputs: [
      Response(JsonRpcResponse::error(id=agent_adapter_response_id(id), error~)),
    ],
  }
}

///|
fn agent_adapter_success(
  state : AgentAdapterState,
  id : RequestId,
  result : Json,
) -> AgentAdapterStep {
  { state, outputs: [Response(JsonRpcResponse::success(id~, result~))] }
}

///|
fn agent_adapter_trace(
  state : AgentAdapterState,
  method_name : String,
  error : JsonRpcError,
) -> AgentAdapterStep {
  { state, outputs: [Command(TraceError(method_name~, error~))] }
}

///|
fn agent_adapter_endpoint_config(
  endpoint : AgentEndpoint,
) -> AgentProtocolConfig {
  {
    agent_capabilities: Value(endpoint.capabilities()),
    auth_methods: endpoint.auth_methods(),
    agent_info: Value(endpoint.info()),
  }
}

///|
fn agent_adapter_validate_endpoint(
  state : AgentAdapterState,
  endpoint : AgentEndpoint,
) -> Unit raise AgentAdapterError {
  let expected = agent_adapter_endpoint_config(endpoint)
  if state.protocol.config.agent_capabilities != expected.agent_capabilities {
    raise EndpointMismatch(reason="state capabilities do not match endpoint")
  }
  if state.protocol.config.auth_methods != expected.auth_methods {
    raise EndpointMismatch(reason="state auth methods do not match endpoint")
  }
  match state.protocol.config.agent_info {
    Omitted | Null => ()
    Value(info) =>
      match expected.agent_info {
        Value(expected_info) if info != expected_info =>
          raise EndpointMismatch(
            reason="state agent info does not match endpoint",
          )
        _ => ()
      }
  }
}

///|
fn agent_adapter_state_with_endpoint(
  state : AgentAdapterState,
  endpoint : AgentEndpoint,
) -> AgentAdapterState {
  {
    ..state,
    protocol: {
      ..state.protocol,
      config: agent_adapter_endpoint_config(endpoint),
    },
  }
}

///|
fn agent_adapter_decode_error_message(error : ProtocolDecodeError) -> String {
  match error {
    JsonParse(message~) => message
    ExpectedObject(path~) => "expected object at " + path
    ExpectedArray(path~) => "expected array at " + path
    ExpectedString(path~) => "expected string at " + path
    ExpectedBoolean(path~) => "expected boolean at " + path
    ExpectedNumber(path~) => "expected number at " + path
    MissingField(path~) => "missing field at " + path
    UnknownField(path~) => "unknown field at " + path
    InvalidField(path~, reason~) => path + ": " + reason
    InvalidDiscriminator(path~, value~) => path + ": " + value
    InvalidPath(path~, value~) => path + ": " + value
    InvalidBase64(path~) => "invalid base64 at " + path
  }
}

///|
fn agent_adapter_message_error(error : AgentMessageError) -> JsonRpcError {
  match error {
    InvalidParams(method_name~, cause~) =>
      JsonRpcError::new(
        code=InvalidParams,
        message=method_name + ": " + agent_adapter_decode_error_message(cause),
      )
    UnknownMethod(_) => JsonRpcError::method_not_found()
    WrongDirection(_) => JsonRpcError::method_not_found()
    WrongKind(_) => JsonRpcError::invalid_request()
  }
}

///|
fn agent_adapter_protocol_error(error : AgentProtocolError) -> JsonRpcError {
  match error {
    AuthenticationRequired(_) => JsonRpcError::auth_required()
    AuthenticationMethodNotAdvertised(_) => JsonRpcError::auth_required()
    NotAuthenticated => JsonRpcError::auth_required()
    CapabilityDenied(..) => JsonRpcError::resource_not_found()
    NotInitialized(_) => JsonRpcError::invalid_request()
    AlreadyInitialized => JsonRpcError::invalid_request()
    AuthenticationNotRequired => JsonRpcError::invalid_request()
    AlreadyAuthenticated => JsonRpcError::invalid_request()
    InvalidEvent(_) => JsonRpcError::invalid_request()
  }
}

///|
fn agent_adapter_handler_error(error : HandlerError) -> JsonRpcError {
  match error {
    Application(_) => JsonRpcError::internal_error()
    Cancelled => JsonRpcError::request_cancelled()
    UnavailableOperation(_) => JsonRpcError::resource_not_found()
  }
}

///|
fn agent_adapter_reduce_request(
  state : AgentAdapterState,
  request : AgentRequest,
) -> Result[AgentProtocolStep, JsonRpcError] {
  try agent_protocol_reduce(state.protocol, Request(request)) catch {
    error => Err(agent_adapter_protocol_error(error))
  } noraise {
    step => Ok(step)
  }
}

///|
fn agent_adapter_reduce_notification(
  state : AgentAdapterState,
  notification : AgentNotification,
) -> Result[AgentProtocolStep, JsonRpcError] {
  try agent_protocol_reduce(state.protocol, Notification(notification)) catch {
    error => Err(agent_adapter_protocol_error(error))
  } noraise {
    step => Ok(step)
  }
}

///|
fn[T] agent_adapter_encode_success(
  state : AgentAdapterState,
  id : RequestId,
  value : T,
  encode : (T) -> Json raise ProtocolDecodeError,
) -> Result[AgentAdapterStep, JsonRpcError] {
  try encode(value) catch {
    _ => Err(JsonRpcError::internal_error())
  } noraise {
    result => Ok(agent_adapter_success(state, id, result))
  }
}

///|
async fn[T] agent_adapter_call(
  handler : async () -> T,
) -> Result[T, HandlerError] noraise {
  try handler() catch {
    error =>
      if @async.is_cancellation_error(error) {
        Err(Cancelled)
      } else {
        match error {
          HandlerError::Application(message~) => Err(Application(message~))
          HandlerError::Cancelled => Err(Cancelled)
          HandlerError::UnavailableOperation(method_name~) =>
            Err(UnavailableOperation(method_name~))
          _ => Err(Application(message="agent handler failed"))
        }
      }
  } noraise {
    value => Ok(value)
  }
}

///|
fn agent_adapter_busy(
  state : AgentAdapterState,
  id : RequestId,
) -> AgentAdapterAdmission {
  Immediate(agent_adapter_response(state, id, JsonRpcError::invalid_request()))
}

///|
fn agent_adapter_release_reservation(
  state : AgentAdapterState,
  id : RequestId,
) -> AgentAdapterState raise AgentAdapterError {
  match state.reservation {
    AuthenticatePending(reserved) if reserved == id =>
      { ..state, reservation: Idle }
    LogoutPending(reserved) if reserved == id => { ..state, reservation: Idle }
    _ => raise LateCompletion(request_id=id)
  }
}

///|
fn agent_adapter_reservation_busy(state : AgentAdapterState) -> Bool {
  match state.reservation {
    Idle => false
    AuthenticatePending(_) | LogoutPending(_) => true
  }
}

///|
fn agent_adapter_initialize(
  state : AgentAdapterState,
  endpoint : AgentEndpoint,
  id : RequestId,
  params : InitializeParams,
) -> AgentAdapterAdmission {
  match agent_adapter_reduce_request(state, Initialize(params)) {
    Err(error) => Immediate(agent_adapter_response(state, id, error))
    Ok(step) => {
      let next_state = agent_adapter_state_with_endpoint(
        { ..state, protocol: step.state },
        endpoint,
      )
      let result : InitializeResult = {
        protocol_version: 1,
        agent_capabilities: Value(endpoint.capabilities()),
        auth_methods: endpoint.auth_methods(),
        agent_info: Value(endpoint.info()),
        meta: Omitted,
      }
      let encoded = try initialize_result_to_json(result) catch {
        _ => None
      } noraise {
        json => Some(json)
      }
      match encoded {
        None =>
          Immediate(
            agent_adapter_response(
              next_state,
              id,
              JsonRpcError::internal_error(),
            ),
          )
        Some(json) => Immediate(agent_adapter_success(next_state, id, json))
      }
    }
  }
}

///|
fn agent_adapter_authenticate(
  state : AgentAdapterState,
  id : RequestId,
  params : AuthenticateParams,
) -> AgentAdapterAdmission {
  if agent_adapter_reservation_busy(state) {
    return agent_adapter_busy(state, id)
  }
  match agent_adapter_reduce_request(state, Authenticate(params)) {
    Err(error) => Immediate(agent_adapter_response(state, id, error))
    Ok(step) =>
      Invoke(
        state={ protocol: step.state, reservation: AuthenticatePending(id) },
        invocation=AuthenticateInvocation(id~, params~),
      )
  }
}

///|
fn agent_adapter_logout(
  state : AgentAdapterState,
  id : RequestId,
  params : LogoutParams,
) -> AgentAdapterAdmission {
  if agent_adapter_reservation_busy(state) {
    return agent_adapter_busy(state, id)
  }
  match agent_adapter_reduce_request(state, Logout(params)) {
    Err(error) => Immediate(agent_adapter_response(state, id, error))
    Ok(step) =>
      Invoke(
        state={ protocol: step.state, reservation: LogoutPending(id) },
        invocation=LogoutInvocation(id~, params~),
      )
  }
}

///|
fn agent_adapter_session_request(
  state : AgentAdapterState,
  id : RequestId,
  request : AgentRequest,
  invocation : AgentAdapterInvocation,
) -> AgentAdapterAdmission {
  match state.reservation {
    LogoutPending(_) => return agent_adapter_busy(state, id)
    AuthenticatePending(_) | Idle => ()
  }
  match agent_adapter_reduce_request(state, request) {
    Err(error) => Immediate(agent_adapter_response(state, id, error))
    Ok(step) => Invoke(state={ ..state, protocol: step.state }, invocation~)
  }
}

///|
fn agent_adapter_admit_request(
  state : AgentAdapterState,
  endpoint : AgentEndpoint,
  id : RequestId,
  request : AgentRequest,
) -> AgentAdapterAdmission {
  match request {
    Initialize(params) => agent_adapter_initialize(state, endpoint, id, params)
    Authenticate(params) => agent_adapter_authenticate(state, id, params)
    Logout(params) => agent_adapter_logout(state, id, params)
    SessionNew(params) =>
      agent_adapter_session_request(
        state,
        id,
        request,
        SessionNewInvocation(id~, params~),
      )
    SessionLoad(params) =>
      agent_adapter_session_request(
        state,
        id,
        request,
        SessionLoadInvocation(id~, params~),
      )
    SessionResume(params) =>
      agent_adapter_session_request(
        state,
        id,
        request,
        SessionResumeInvocation(id~, params~),
      )
    SessionList(params) =>
      agent_adapter_session_request(
        state,
        id,
        request,
        SessionListInvocation(id~, params~),
      )
    SessionDelete(params) =>
      agent_adapter_session_request(
        state,
        id,
        request,
        SessionDeleteInvocation(id~, params~),
      )
    SessionClose(params) =>
      agent_adapter_session_request(
        state,
        id,
        request,
        SessionCloseInvocation(id~, params~),
      )
    SessionSetMode(params) =>
      agent_adapter_session_request(
        state,
        id,
        request,
        SessionSetModeInvocation(id~, params~),
      )
    SessionSetConfigOption(params) =>
      agent_adapter_session_request(
        state,
        id,
        request,
        SessionSetConfigOptionInvocation(id~, params~),
      )
    SessionPrompt(params) =>
      agent_adapter_session_request(
        state,
        id,
        request,
        SessionPromptInvocation(id~, params~),
      )
  }
}

///|
fn agent_adapter_admit_notification(
  state : AgentAdapterState,
  notification : AgentNotification,
) -> AgentAdapterAdmission {
  match notification {
    SessionCancel(params) =>
      match agent_adapter_reduce_notification(state, notification) {
        Err(error) =>
          Immediate(agent_adapter_trace(state, "session/cancel", error))
        Ok(step) =>
          Invoke(
            state={ protocol: step.state, reservation: state.reservation },
            invocation=SessionCancelInvocation(params~),
          )
      }
    CancelRequest(request_id) =>
      match agent_adapter_reduce_notification(state, notification) {
        Err(error) =>
          Immediate(agent_adapter_trace(state, "$/cancel_request", error))
        Ok(step) =>
          Immediate({
            state: { ..state, protocol: step.state },
            outputs: [Command(CancelRequest(request_id~))],
          })
      }
  }
}

///|
/// Decode and admit one message.  This function is synchronous and pure from
/// the owner loop's perspective; it never calls an application handler.
pub fn agent_adapter_admit(
  state : AgentAdapterState,
  endpoint : AgentEndpoint,
  message : JsonRpcMessage,
) -> AgentAdapterAdmission raise AgentAdapterError {
  agent_adapter_validate_endpoint(state, endpoint)
  match message {
    Request(request) => {
      let id = request.id
      let decoded = try
        decode_agent_request(request.method_name, request.params)
      catch {
        error => Err(agent_adapter_message_error(error))
      } noraise {
        value => Ok(value)
      }
      match decoded {
        Err(error) => Immediate(agent_adapter_response(state, id, error))
        Ok(request) => agent_adapter_admit_request(state, endpoint, id, request)
      }
    }
    Notification(notification) => {
      let decoded = try
        decode_agent_notification(notification.method_name, notification.params)
      catch {
        error => Err(agent_adapter_message_error(error))
      } noraise {
        value => Ok(value)
      }
      match decoded {
        Err(error) =>
          Immediate(agent_adapter_trace(state, notification.method_name, error))
        Ok(notification) =>
          agent_adapter_admit_notification(state, notification)
      }
    }
    Response(_) => raise UnexpectedMessage(kind="response")
  }
}

///|
fn[T] agent_adapter_complete_result(
  state : AgentAdapterState,
  id : RequestId,
  result : Result[T, HandlerError],
  encode : (T) -> Json raise ProtocolDecodeError,
) -> AgentAdapterStep {
  match result {
    Err(error) =>
      agent_adapter_response(state, id, agent_adapter_handler_error(error))
    Ok(value) =>
      match agent_adapter_encode_success(state, id, value, encode) {
        Ok(step) => step
        Err(error) => agent_adapter_response(state, id, error)
      }
  }
}

///|
fn agent_adapter_complete_authenticate(
  state : AgentAdapterState,
  id : RequestId,
  params : AuthenticateParams,
  result : Result[AuthenticateResult, HandlerError],
) -> AgentAdapterStep raise AgentAdapterError {
  let released = agent_adapter_release_reservation(state, id)
  match result {
    Err(error) =>
      agent_adapter_response(released, id, agent_adapter_handler_error(error))
    Ok(value) => {
      let reduced = agent_protocol_reduce(
        released.protocol,
        AuthenticationSucceeded(method_id=params.method_id),
      ) catch {
        error =>
          return agent_adapter_response(
            released,
            id,
            agent_adapter_protocol_error(error),
          )
      }
      let next = { ..released, protocol: reduced.state }
      match
        agent_adapter_encode_success(
          next, id, value, authenticate_result_to_json,
        ) {
        Ok(step) => step
        Err(error) => agent_adapter_response(next, id, error)
      }
    }
  }
}

///|
fn agent_adapter_complete_logout(
  state : AgentAdapterState,
  id : RequestId,
  result : Result[LogoutResult, HandlerError],
) -> AgentAdapterStep raise AgentAdapterError {
  let released = agent_adapter_release_reservation(state, id)
  match result {
    Err(error) =>
      agent_adapter_response(released, id, agent_adapter_handler_error(error))
    Ok(value) => {
      let reduced = agent_protocol_reduce(released.protocol, LoggedOut) catch {
        error =>
          return agent_adapter_response(
            released,
            id,
            agent_adapter_protocol_error(error),
          )
      }
      let next = { ..released, protocol: reduced.state }
      match
        agent_adapter_encode_success(next, id, value, logout_result_to_json) {
        Ok(step) => step
        Err(error) => agent_adapter_response(next, id, error)
      }
    }
  }
}

///|
/// Complete one admitted invocation in the owner loop.  A state-changing
/// completion must match its immutable reservation; duplicate/late auth and
/// logout completions fail explicitly instead of producing a second response.
pub fn agent_adapter_complete(
  state : AgentAdapterState,
  invocation : AgentAdapterInvocation,
  completion : AgentAdapterCompletion,
) -> AgentAdapterStep raise AgentAdapterError {
  match invocation {
    AuthenticateInvocation(id~, params~) =>
      match completion {
        AuthenticateCompleted(id=completion_id, result~) if completion_id == id =>
          agent_adapter_complete_authenticate(state, id, params, result)
        AuthenticateCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="authenticate")
      }
    LogoutInvocation(id~, params=_) =>
      match completion {
        LogoutCompleted(id=completion_id, result~) if completion_id == id =>
          agent_adapter_complete_logout(state, id, result)
        LogoutCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="logout")
      }
    SessionNewInvocation(id~, params=_) =>
      match completion {
        SessionNewCompleted(id=completion_id, result~) if completion_id == id =>
          agent_adapter_complete_result(
            state, id, result, new_session_result_to_json,
          )
        SessionNewCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="session/new")
      }
    SessionLoadInvocation(id~, params=_) =>
      match completion {
        SessionLoadCompleted(id=completion_id, result~) if completion_id == id =>
          agent_adapter_complete_result(
            state, id, result, load_session_result_to_json,
          )
        SessionLoadCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="session/load")
      }
    SessionResumeInvocation(id~, params=_) =>
      match completion {
        SessionResumeCompleted(id=completion_id, result~) if completion_id == id =>
          agent_adapter_complete_result(
            state, id, result, resume_session_result_to_json,
          )
        SessionResumeCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="session/resume")
      }
    SessionListInvocation(id~, params=_) =>
      match completion {
        SessionListCompleted(id=completion_id, result~) if completion_id == id =>
          agent_adapter_complete_result(
            state, id, result, list_sessions_result_to_json,
          )
        SessionListCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="session/list")
      }
    SessionDeleteInvocation(id~, params=_) =>
      match completion {
        SessionDeleteCompleted(id=completion_id, result~) if completion_id == id =>
          agent_adapter_complete_result(
            state, id, result, delete_session_result_to_json,
          )
        SessionDeleteCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="session/delete")
      }
    SessionCloseInvocation(id~, params=_) =>
      match completion {
        SessionCloseCompleted(id=completion_id, result~) if completion_id == id =>
          agent_adapter_complete_result(
            state, id, result, close_session_result_to_json,
          )
        SessionCloseCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="session/close")
      }
    SessionSetModeInvocation(id~, params=_) =>
      match completion {
        SessionSetModeCompleted(id=completion_id, result~) if completion_id ==
          id =>
          agent_adapter_complete_result(
            state, id, result, set_session_mode_result_to_json,
          )
        SessionSetModeCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="session/set_mode")
      }
    SessionSetConfigOptionInvocation(id~, params=_) =>
      match completion {
        SessionSetConfigOptionCompleted(id=completion_id, result~) if completion_id ==
          id =>
          agent_adapter_complete_result(
            state, id, result, set_session_config_option_result_to_json,
          )
        SessionSetConfigOptionCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="session/set_config_option")
      }
    SessionPromptInvocation(id~, params=_) =>
      match completion {
        SessionPromptCompleted(id=completion_id, result~) if completion_id == id =>
          agent_adapter_complete_result(
            state, id, result, prompt_result_to_json,
          )
        SessionPromptCompleted(id=completion_id, result=_) =>
          raise LateCompletion(request_id=completion_id)
        _ => raise CompletionMismatch(method_name="session/prompt")
      }
    SessionCancelInvocation(_) =>
      match completion {
        SessionCancelCompleted(result~) =>
          match result {
            Ok(_) => { state, outputs: [] }
            Err(error) =>
              agent_adapter_trace(
                state,
                "session/cancel",
                agent_adapter_handler_error(error),
              )
          }
        _ => raise CompletionMismatch(method_name="session/cancel")
      }
  }
}