///|
/// Real HTTP transport for generated SDKs.
///
/// This is the only place in the SDK runtime that touches
/// `moonbitlang/async/http`. Generated operations never import the HTTP
/// library directly: they call `Client::send_request`, which either replays a
/// `CaptureTransport` (unit tests) or delegates here through `transmit`.

///|
/// Map the canonical HTTP method string onto the async HTTP client enum.
///
/// The generator only emits methods from the V1 profile; anything outside it
/// fails loudly rather than silently degrading to GET.
fn request_method(
  name : String,
  operation_id : String,
) -> @http.RequestMethod raise SdkError {
  match name {
    "GET" => @http.Get
    "HEAD" => @http.Head
    "POST" => @http.Post
    "PUT" => @http.Put
    "DELETE" => @http.Delete
    "OPTIONS" => @http.Options
    "TRACE" => @http.Trace
    "PATCH" => @http.Patch
    _ => raise Unsupported(operation_id, "unsupported HTTP method: " + name)
  }
}

///|
/// Render query parameters into a request target suffix.
///
/// Names and values are percent-encoded with the same RFC 3986 rules the path
/// encoder uses, so repeated keys keep their declared order.
///
/// Returns `""` when there is nothing to encode and `"?k=v&k2=v2"` otherwise.
pub fn render_query(params : Array[(String, String)]) -> String {
  if params.length() == 0 {
    return ""
  }
  let encoded : Array[(String, String)] = []
  for pair in params {
    let (key, value) = pair
    encoded.push((percent_encode(key), percent_encode(value)))
  }
  "?" + build_query_string(encoded)
}

///|
/// Perform one request against the origin of `config`.
///
/// The body is already JSON-encoded by the generated operation, so this
/// function only computes `Content-Length` and writes the bytes. Responses with
/// status 204/304 never carry a body, matching the SDK response policy.
///
/// Any I/O failure becomes `Transport`; the HTTP status itself is not judged
/// here, because `expect_status` owns that decision.
pub async fn transmit(
  config : Config,
  request : Request,
  operation_id? : String = "",
) -> Response raise SdkError {
  let target = config.base_path() + request.path + render_query(request.query)
  let headers = request.headers
  match request.body {
    Some(text) =>
      headers["Content-Length"] = @utf8.encode(text).length().to_string()
    None => ()
  }
  try {
    let client = @http.Client::Client(config.origin)
    client.request(
      request_method(request.http_method, operation_id),
      target,
      extra_headers=headers,
    )
    match request.body {
      Some(text) => client.write(text)
      None => ()
    }
    let response = client.end_request()
    let body = if response.code is (204 | 304) {
      ""
    } else {
      client.read_all().text()
    }
    client.close()
    { status: response.code, headers: response.headers, body }
  } catch {
    err => raise Transport(operation_id, err.to_string())
  }
}