// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// A request as it goes on the wire, whichever version carries it.
priv struct Outgoing {
  verb : @method.Method
  scheme : String
  authority : String
  target : String
  fields : Array[@header.Header]
  payload : Bytes
}

///|
/// A response as it comes off the wire, and whether its connection may carry another.
priv struct Reply {
  status : Int32
  reason : String
  version : String
  headers : Array[(String, String)]
  body : Bytes
  reusable : Bool
}

///|
fn ascii(fields : Array[@header.Header]) -> Array[(String, String)] {
  fields.map(f => {
    let (name, value) = f.ascii()
    (name.to_lower(), value)
  })
}

///|
/// One exchange over HTTP/1.1, framed and checked by moonhttp's state machine.
async fn h1(conn : @tcp.Conn, out : Outgoing) -> Reply {
  let wire = @http1.Conn::new(Client)
  // RFC 9110 ยง8.6: a method that defines a body says how long it is, even when empty.
  let framing : @http1.Body = if out.payload.length() > 0 ||
    out.verb is (Post | Put | Patch) {
    Sized(out.payload.length())
  } else {
    Empty
  }
  let head = @http1.Request::of(
    out.verb.name(),
    out.target,
    fields=out.fields,
    host=out.authority,
    body=framing,
  )
  let sent = Buffer()
  sent.write_bytes(wire.send(Request(head)))
  if out.payload.length() > 0 {
    sent.write_bytes(wire.send(Data(out.payload)))
  }
  sent.write_bytes(wire.send(End([])))
  conn.write(sent.to_bytes())
  let body = Buffer()
  let mut answer = None
  for ;; {
    match wire.next() {
      NeedData => wire.receive(conn.read_some(max_len=chunk).unwrap_or(b"")[:])
      Response(r) => answer = Some(r)
      Data(piece) => body.write_bytes(piece)
      End(_) | Closed => break
      Informational(_) | Paused | Request(_) => ()
    }
  }
  guard answer is Some(r) else { raise @http1.Partial }
  {
    status: r.status,
    reason: r.reason,
    version: r.version,
    headers: ascii(r.fields),
    body: body.to_bytes(),
    reusable: wire.ours() is Done &&
    wire.theirs() is Done &&
    wire.trailing().length() == 0,
  }
}