///|
type JsonRpcRequest = @jsonrpc.JsonRpcRequest

///|
type JsonRpcNotification = @jsonrpc.JsonRpcNotification

///|
type JsonRpcResponse = @jsonrpc.JsonRpcResponse

///|
type JsonRpcError = @jsonrpc.JsonRpcError

///|
/// Failures at the native runtime boundary.
///
/// These errors deliberately carry only failure categories.  Peer payloads,
/// prompts, files, tokens, and environment values never enter a trace or an
/// error string produced by the runtime.
pub(all) suberror RuntimeError {
  InvalidOptions
  ReaderFailed
  ReaderEmptyChunk
  EventQueueClosed
  EventQueueBackpressure(limit~ : Int)
  WriterQueueClosed
  WriterQueueBackpressure(limit~ : Int)
  WriterFailed
  FramingFailed
  JsonRpcDecodeFailed
  JsonRpcEncodeFailed
  ReducerFailed
  TaskControlMissing
  TaskRequestMismatch
  HandlerFailed
  NotificationFailed
  ResponseHandlerFailed
  OutboundCancelUnavailable
  OutboundCancelFailed
  OutboundCompletionFailed
  CloseCleanupFailed(primary_kind~ : String, cleanup_phase~ : String)
}

///|
/// A redacted runtime trace record.  Only routing metadata and a static
/// failure category are exposed to the sink.
pub(all) struct RuntimeTraceEvent {
  direction : String
  phase : String
  request_id : String
  method_name : String
  error_kind : String
}

///|
/// Caller-owned asynchronous input seam. `None` is the only EOF signal.
pub(all) struct RuntimeReaderPort {
  read : async (Int) -> Bytes?
}

///|
/// Caller-owned asynchronous output seam. Each call receives one complete
/// newline-delimited frame and must either write all bytes or raise.
pub(all) struct RuntimeWriterPort {
  write : async (Bytes) -> Unit
}

///|
pub type RuntimeOutboundFailureHandler = async (
  @jsonrpc.RequestId,
  RuntimeOutboundFailure,
) -> Unit

///|
pub type RuntimeCancelOutboundHandler = async (@jsonrpc.RequestId) -> Unit

///|
/// Closed, finite categories for a locally initiated request completion.
/// Arbitrary close strings never cross this boundary.
pub(all) enum RuntimeOutboundFailureKind {
  Protocol
  Framing
  Reader
  Writer
  Handler
  Notification
  EndOfInput
  EventQueue
  Runtime
} derive(Eq, Debug)

///|
/// Result returned by an inbound request handler. A handler failure is raised
/// through the async port and becomes `RuntimeError::HandlerFailed`.
pub(all) enum RuntimeHandlerResult {
  HandlerSuccess(Json)
  HandlerError(JsonRpcError)
}

///|
/// Protocol dispatch seams. They are values rather than globals so tests and
/// composition roots can inject memory or faulting implementations.
pub(all) struct RuntimeHandlerPort {
  request : async (JsonRpcRequest) -> RuntimeHandlerResult
  notification : async (JsonRpcNotification) -> Unit
  response : async (JsonRpcResponse) -> Unit
  /// Called exactly once when a locally initiated pending request is failed
  /// by connection shutdown. The reason is a static reducer close category.
  outbound_failure : RuntimeOutboundFailureHandler?
}

///|
/// Typed completion data for a locally initiated request. The reason is
/// intentionally a category, never a peer payload or arbitrary exception.
pub(all) struct RuntimeOutboundFailure {
  reason : RuntimeOutboundFailureKind
}

///|
/// All per-connection runtime dependencies.
pub(all) struct RuntimePorts {
  reader : RuntimeReaderPort
  writer : RuntimeWriterPort
  handlers : RuntimeHandlerPort
  /// The owner of outbound requests supplies cancellation. Inbound task
  /// controls are runtime-owned and must never be used for this direction.
  cancel_outbound : RuntimeCancelOutboundHandler?
  trace : (RuntimeTraceEvent) -> Unit
}

///|
/// Native runtime bounds. The event and writer queues are intentionally
/// bounded so backpressure is observable instead of becoming unbounded heap
/// growth.
pub(all) struct RuntimeOptions {
  max_frame_bytes : Int
  queue_capacity : Int
  read_chunk_bytes : Int
}

///|
pub fn runtime_default_options() -> RuntimeOptions {
  { max_frame_bytes: 1024 * 1024, queue_capacity: 64, read_chunk_bytes: 4096 }
}

///|
pub fn runtime_validate_options(
  options : RuntimeOptions,
) -> Unit raise RuntimeError {
  if options.max_frame_bytes <= 0 ||
    options.queue_capacity <= 0 ||
    options.read_chunk_bytes <= 0 {
    raise InvalidOptions
  }
}

///|
pub fn runtime_trace_event(
  direction : String,
  phase : String,
  request_id : String,
  method_name : String,
  error_kind : String,
) -> RuntimeTraceEvent {
  { direction, phase, request_id, method_name, error_kind }
}

///|
pub fn runtime_request_id_text(id : @jsonrpc.RequestId) -> String {
  match id {
    String(value) => value
    Number(value) => value.to_string()
    Null => "null"
  }
}