///|
pub(all) enum BackendEndpointKind {
  SnapshotLoad
  StatusLoad
  OperatorSubmit
  AgentMessageSend
  AgentRunStatus
  ThreadLoad
  HandoffSubmit
  ReviewDecision
  ToolAck
  AgentOperationCancel
  AgentOperationRetry
  GenericEndpoint
} derive(Debug, Eq)

///|
pub(all) enum HttpMethod {
  Get
  Post
  Put
  Patch
  Delete
} derive(Debug, Eq)

///|
pub(all) enum BackendState {
  IdleState
  Loading
  Ready
  Stale
  Failed
  Timeout
  Malformed
  CancelledState
} derive(Debug, Eq)

///|
pub struct BackendEndpoint {
  id : String
  kind : BackendEndpointKind
  http_method : HttpMethod
  path : String
  payload_key : String
  response_key : String
  timeout_ms : Int
  streaming : Bool
  requires_review : Bool
} derive(Debug, Eq)

///|
pub struct BackendContract {
  id : String
  base_url_key : String
  session_token_key : String
  session_header_key : String
  endpoints : Array[BackendEndpoint]
} derive(Debug, Eq)

///|
pub struct BackendBudget {
  max_endpoints : Int
  max_streaming : Int
  max_review_required : Int
} derive(Debug, Eq)

///|
pub struct BackendPlan {
  contract : BackendContract
  endpoint_count : Int
  streaming_count : Int
  review_required_count : Int
  cancel_count : Int
  retry_count : Int
  diagnostics : Array[String]
  summary : String
} derive(Debug, Eq)

///|
pub fn backend_endpoint(
  id~ : String,
  kind~ : BackendEndpointKind,
  http_method~ : HttpMethod,
  path~ : String,
  payload_key? : String = "",
  response_key? : String = "",
  timeout_ms? : Int = 10000,
  streaming? : Bool = false,
  requires_review? : Bool = false,
) -> BackendEndpoint {
  {
    id,
    kind,
    http_method,
    path,
    payload_key,
    response_key,
    timeout_ms,
    streaming,
    requires_review,
  }
}

///|
pub fn backend_contract(
  id~ : String,
  base_url_key? : String = "backendBaseUrl",
  session_token_key? : String = "bunniaSessionId",
  session_header_key? : String = "x-miniapp-session",
  endpoints~ : Array[BackendEndpoint],
) -> BackendContract {
  { id, base_url_key, session_token_key, session_header_key, endpoints }
}

///|
pub fn backend_budget(
  max_endpoints~ : Int,
  max_streaming~ : Int,
  max_review_required~ : Int,
) -> BackendBudget {
  { max_endpoints, max_streaming, max_review_required }
}

///|
pub fn default_backend_budget() -> BackendBudget {
  { max_endpoints: 32, max_streaming: 4, max_review_required: 12 }
}

///|
pub fn plan_backend_contract(contract : BackendContract) -> BackendPlan {
  plan_backend_contract_with_budget(contract, default_backend_budget())
}

///|
pub fn plan_backend_contract_with_budget(
  contract : BackendContract,
  budget : BackendBudget,
) -> BackendPlan {
  let diagnostics : Array[String] = []
  let mut streaming_count = 0
  let mut review_required_count = 0
  let mut cancel_count = 0
  let mut retry_count = 0
  for endpoint in contract.endpoints {
    if endpoint.streaming {
      streaming_count += 1
    }
    if endpoint.requires_review {
      review_required_count += 1
    }
    if endpoint.kind is AgentOperationCancel {
      cancel_count += 1
    }
    if endpoint.kind is AgentOperationRetry {
      retry_count += 1
    }
  }
  if contract.endpoints.length() > budget.max_endpoints {
    diagnostics.push(
      "backend-endpoint-count-over-budget:\{contract.endpoints.length()}/\{budget.max_endpoints}",
    )
  }
  if streaming_count > budget.max_streaming {
    diagnostics.push(
      "backend-streaming-count-over-budget:\{streaming_count}/\{budget.max_streaming}",
    )
  }
  if review_required_count > budget.max_review_required {
    diagnostics.push(
      "backend-review-count-over-budget:\{review_required_count}/\{budget.max_review_required}",
    )
  }
  {
    contract,
    endpoint_count: contract.endpoints.length(),
    streaming_count,
    review_required_count,
    cancel_count,
    retry_count,
    diagnostics,
    summary: "Bunnia backend plan: contract=\{contract.id} endpoints=\{contract.endpoints.length()} streaming=\{streaming_count} review=\{review_required_count} cancel=\{cancel_count} retry=\{retry_count} diagnostics=\{diagnostics.length()}",
  }
}

///|
pub fn endpoint_effect(endpoint : BackendEndpoint, id? : String = "") -> Effect {
  let effect_id = if id == "" { endpoint.id } else { id }
  effect(
    id=effect_id,
    kind=backend_endpoint_effect_kind(endpoint),
    target=endpoint.path,
    payload_key=endpoint.payload_key,
    message=endpoint.id,
  )
}

///|
fn backend_endpoint_effect_kind(endpoint : BackendEndpoint) -> EffectKind {
  match endpoint.kind {
    AgentOperationCancel => Cancel
    AgentOperationRetry => Retry
    _ => if endpoint.streaming { Stream } else { Request }
  }
}

///|
pub fn backend_contract_effects(contract : BackendContract) -> Array[Effect] {
  let output : Array[Effect] = []
  for endpoint in contract.endpoints {
    output.push(endpoint_effect(endpoint))
  }
  output
}

///|
pub fn plan_backend_effects_for_platform(
  contract : BackendContract,
  platform : @core.Platform,
) -> EffectPlan {
  plan_effects_for_platform(backend_contract_effects(contract), platform)
}

///|
pub fn plan_backend_effects_for_adapter(
  contract : BackendContract,
  adapter : @core.PlatformAdapter,
) -> EffectPlan {
  plan_effects_for_adapter(backend_contract_effects(contract), adapter)
}

///|
pub fn snapshot_load_endpoint(
  id : String,
  path : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(
    id~,
    kind=SnapshotLoad,
    http_method=Get,
    path~,
    response_key~,
  )
}

///|
pub fn status_load_endpoint(
  id : String,
  path : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(id~, kind=StatusLoad, http_method=Get, path~, response_key~)
}

///|
pub fn operator_submit_endpoint(
  id : String,
  path : String,
  payload_key : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(
    id~,
    kind=OperatorSubmit,
    http_method=Post,
    path~,
    payload_key~,
    response_key~,
    requires_review=true,
  )
}

///|
pub fn agent_message_endpoint(
  id : String,
  path : String,
  payload_key : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(
    id~,
    kind=AgentMessageSend,
    http_method=Post,
    path~,
    payload_key~,
    response_key~,
  )
}

///|
pub fn agent_stream_endpoint(
  id : String,
  path : String,
  payload_key : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(
    id~,
    kind=AgentRunStatus,
    http_method=Get,
    path~,
    payload_key~,
    response_key~,
    streaming=true,
  )
}

///|
pub fn thread_load_endpoint(
  id : String,
  path : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(id~, kind=ThreadLoad, http_method=Get, path~, response_key~)
}

///|
pub fn handoff_submit_endpoint(
  id : String,
  path : String,
  payload_key : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(
    id~,
    kind=HandoffSubmit,
    http_method=Post,
    path~,
    payload_key~,
    response_key~,
    requires_review=true,
  )
}

///|
pub fn review_decision_endpoint(
  id : String,
  path : String,
  payload_key : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(
    id~,
    kind=ReviewDecision,
    http_method=Post,
    path~,
    payload_key~,
    response_key~,
    requires_review=true,
  )
}

///|
pub fn tool_ack_endpoint(
  id : String,
  path : String,
  payload_key : String,
) -> BackendEndpoint {
  backend_endpoint(id~, kind=ToolAck, http_method=Post, path~, payload_key~)
}

///|
pub fn agent_cancel_endpoint(
  id : String,
  path : String,
  payload_key : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(
    id~,
    kind=AgentOperationCancel,
    http_method=Post,
    path~,
    payload_key~,
    response_key~,
  )
}

///|
pub fn agent_retry_endpoint(
  id : String,
  path : String,
  payload_key : String,
  response_key : String,
) -> BackendEndpoint {
  backend_endpoint(
    id~,
    kind=AgentOperationRetry,
    http_method=Post,
    path~,
    payload_key~,
    response_key~,
  )
}

///|
pub fn generic_endpoint(
  id : String,
  path : String,
  http_method : HttpMethod,
  payload_key? : String = "",
  response_key? : String = "",
) -> BackendEndpoint {
  backend_endpoint(
    id~,
    kind=GenericEndpoint,
    http_method~,
    path~,
    payload_key~,
    response_key~,
  )
}

///|
pub fn backend_state_patch(scope : String, state : BackendState) -> @core.Patch {
  @core.set_string("backend.\{scope}.state", backend_state_id(state))
}

///|
pub fn backend_error_patch(scope : String, message : String) -> @core.Patch {
  @core.set_string("backend.\{scope}.error", message)
}

///|
pub fn backend_response_patch(
  scope : String,
  value_json : String,
) -> @core.Patch {
  @core.set_json("backend.\{scope}.response", value_json)
}

///|
pub fn backend_state_id(state : BackendState) -> String {
  match state {
    IdleState => "idle"
    Loading => "loading"
    Ready => "ready"
    Stale => "stale"
    Failed => "failed"
    Timeout => "timeout"
    Malformed => "malformed"
    CancelledState => "cancelled"
  }
}

///|
pub fn backend_endpoint_kind_id(kind : BackendEndpointKind) -> String {
  match kind {
    SnapshotLoad => "snapshot-load"
    StatusLoad => "status-load"
    OperatorSubmit => "operator-submit"
    AgentMessageSend => "agent-message-send"
    AgentRunStatus => "agent-run-status"
    ThreadLoad => "thread-load"
    HandoffSubmit => "handoff-submit"
    ReviewDecision => "review-decision"
    ToolAck => "tool-ack"
    AgentOperationCancel => "agent-operation-cancel"
    AgentOperationRetry => "agent-operation-retry"
    GenericEndpoint => "generic"
  }
}

///|
pub fn http_method_id(input : HttpMethod) -> String {
  match input {
    Get => "GET"
    Post => "POST"
    Put => "PUT"
    Patch => "PATCH"
    Delete => "DELETE"
  }
}