///|
using @message {type ClientRequest, type ClientNotification}

///|
using @jsonrpc {type RequestId}

///|
using @protocol_data {type ProtocolNullable, type ClientCapabilities}

///|
/// The phase of the Client-side protocol lifecycle.
pub(all) enum ClientProtocolPhase {
  AwaitingReady
  Ready
} derive(Eq, Debug)

///|
/// Immutable Client-side state after capability negotiation data is known.
///
/// The capability field remains nullable so an omitted or explicit null
/// `clientCapabilities` value cannot be mistaken for an enabled capability.
pub(all) struct ClientProtocolState {
  phase : ClientProtocolPhase
  capabilities : ProtocolNullable[ClientCapabilities]
} derive(Eq, Debug)

///|
/// Events accepted by the pure Client-side reducer.
pub(all) enum ClientProtocolEvent {
  Ready
  Request(ClientRequest)
  Notification(ClientNotification)
} derive(Eq, Debug)

///|
/// Commands emitted for the runtime or endpoint adapter.  They are data only;
/// no async operation, task, or side effect is started by this reducer.
pub(all) enum ClientProtocolCommand {
  DispatchRequest(ClientRequest)
  DispatchNotification(ClientNotification)
  CancelRequest(request_id~ : RequestId)
} derive(Eq, Debug)

///|
/// State and commands produced by one Client reducer step.
pub(all) struct ClientProtocolStep {
  state : ClientProtocolState
  commands : Array[ClientProtocolCommand]
} derive(Eq, Debug)

///|
/// Explicit failures from Client protocol readiness and capability gates.
pub(all) suberror ClientProtocolError {
  NotReady(method_name~ : String)
  AlreadyReady
  CapabilityDenied(method_name~ : String, capability~ : String)
} derive(Eq, Debug)

///|
/// Construct a Client state that has negotiated capability data but is not yet
/// ready to receive Agent business traffic.
pub fn client_protocol_state_new(
  capabilities~ : ProtocolNullable[ClientCapabilities],
) -> ClientProtocolState {
  { phase: AwaitingReady, capabilities }
}

///|
/// Construct a ready Client state when the caller has already completed the
/// initialize exchange.
pub fn client_protocol_state_ready(
  capabilities~ : ProtocolNullable[ClientCapabilities],
) -> ClientProtocolState {
  { phase: Ready, capabilities }
}

///|
fn client_capabilities(
  state : ClientProtocolState,
) -> ProtocolNullable[ClientCapabilities] {
  state.capabilities
}

///|
fn client_fs_read_supported(state : ClientProtocolState) -> Bool {
  match client_capabilities(state) {
    Value(capabilities) =>
      match capabilities.fs {
        Value(fs) =>
          match fs.read_text_file {
            Value(true) => true
            Omitted | Null | Value(false) => false
          }
        Omitted | Null => false
      }
    Omitted | Null => false
  }
}

///|
fn client_fs_write_supported(state : ClientProtocolState) -> Bool {
  match client_capabilities(state) {
    Value(capabilities) =>
      match capabilities.fs {
        Value(fs) =>
          match fs.write_text_file {
            Value(true) => true
            Omitted | Null | Value(false) => false
          }
        Omitted | Null => false
      }
    Omitted | Null => false
  }
}

///|
fn client_terminal_supported(state : ClientProtocolState) -> Bool {
  match client_capabilities(state) {
    Value(capabilities) =>
      match capabilities.terminal {
        Value(true) => true
        Omitted | Null | Value(false) => false
      }
    Omitted | Null => false
  }
}

///|
fn client_elicitation_form_supported(state : ClientProtocolState) -> Bool {
  match client_capabilities(state) {
    Value(capabilities) =>
      match capabilities.elicitation {
        Value(elicitation) =>
          match elicitation.form {
            Value(_) => true
            Omitted | Null => false
          }
        Omitted | Null => false
      }
    Omitted | Null => false
  }
}

///|
fn client_elicitation_url_supported(state : ClientProtocolState) -> Bool {
  match client_capabilities(state) {
    Value(capabilities) =>
      match capabilities.elicitation {
        Value(elicitation) =>
          match elicitation.url {
            Value(_) => true
            Omitted | Null => false
          }
        Omitted | Null => false
      }
    Omitted | Null => false
  }
}

///|
fn client_require_ready(
  state : ClientProtocolState,
  method_name : String,
) -> Unit raise ClientProtocolError {
  match state.phase {
    AwaitingReady => raise NotReady(method_name~)
    Ready => ()
  }
}

///|
fn client_require_capability(
  supported : Bool,
  method_name : String,
  capability : String,
) -> Unit raise ClientProtocolError {
  if !supported {
    raise CapabilityDenied(method_name~, capability~)
  }
}

///|
fn client_require_terminal(
  state : ClientProtocolState,
  method_name : String,
) -> Unit raise ClientProtocolError {
  client_require_capability(
    client_terminal_supported(state),
    method_name,
    "terminal",
  )
}

///|
fn client_reduce_request(
  state : ClientProtocolState,
  request : ClientRequest,
) -> ClientProtocolStep raise ClientProtocolError {
  let method_name = request.method_name()
  client_require_ready(state, method_name)
  match request {
    SessionRequestPermission(_) =>
      { state, commands: [DispatchRequest(request)] }
    FsReadTextFile(_) => {
      client_require_capability(
        client_fs_read_supported(state),
        method_name,
        "fs.readTextFile",
      )
      { state, commands: [DispatchRequest(request)] }
    }
    FsWriteTextFile(_) => {
      client_require_capability(
        client_fs_write_supported(state),
        method_name,
        "fs.writeTextFile",
      )
      { state, commands: [DispatchRequest(request)] }
    }
    TerminalCreate(_)
    | TerminalOutput(_)
    | TerminalWaitForExit(_)
    | TerminalKill(_)
    | TerminalRelease(_) => {
      client_require_terminal(state, method_name)
      { state, commands: [DispatchRequest(request)] }
    }
    ElicitationCreate(params) => {
      match params {
        Form(_) =>
          client_require_capability(
            client_elicitation_form_supported(state),
            method_name,
            "elicitation.form",
          )
        Url(_) =>
          client_require_capability(
            client_elicitation_url_supported(state),
            method_name,
            "elicitation.url",
          )
      }
      { state, commands: [DispatchRequest(request)] }
    }
  }
}

///|
fn client_reduce_notification(
  state : ClientProtocolState,
  notification : ClientNotification,
) -> ClientProtocolStep raise ClientProtocolError {
  match notification {
    SessionUpdate(_) => {
      let method_name = notification.method_name()
      client_require_ready(state, method_name)
      { state, commands: [DispatchNotification(notification)] }
    }
    ElicitationComplete(_) => {
      let method_name = notification.method_name()
      client_require_ready(state, method_name)
      client_require_capability(
        client_elicitation_url_supported(state),
        method_name,
        "elicitation.url",
      )
      { state, commands: [DispatchNotification(notification)] }
    }
    CancelRequest(request_id) =>
      { state, commands: [CancelRequest(request_id~)] }
  }
}

///|
/// Reduce one Client protocol event without performing I/O or changing any
/// external task state.
pub fn client_protocol_reduce(
  state : ClientProtocolState,
  event : ClientProtocolEvent,
) -> ClientProtocolStep raise ClientProtocolError {
  match event {
    Ready =>
      match state.phase {
        Ready => raise AlreadyReady
        AwaitingReady => { state: { ..state, phase: Ready }, commands: [] }
      }
    Request(request) => client_reduce_request(state, request)
    Notification(notification) =>
      client_reduce_notification(state, notification)
  }
}