// The HTTP/3 connection setup and unidirectional-stream demultiplexer (RFC 9114 §6.2): once the
// QUIC handshake completes, each endpoint opens a control stream and sends its SETTINGS first, then
// classifies every unidirectional stream the peer opens — control (whose SETTINGS it reads and
// keeps), the two QPACK instruction streams, a server push stream, or a reserved/greased type it
// ignores. This is the connection state that sits above the QUIC streams and drives the frame and
// QPACK codecs; the live QUIC connection supplies the stream bytes.

///|
/// Which side of the connection an endpoint is (RFC 9114 §3.1): a server must not receive a push
/// stream, so the role decides whether one is a protocol violation.
pub(all) enum Http3Role {
  ClientSide
  ServerSide
} derive(Eq, Debug)

///|
/// A connection-level HTTP/3 error (RFC 9114 §8): a broken control stream or a stream a peer must
/// not have opened.
pub suberror Http3ConnError {
  Http3ConnError(String)
}

///|
/// The result of accepting a peer's unidirectional stream: the control stream carries the peer's
/// SETTINGS, the QPACK streams carry table instructions, a push stream carries its id, and an
/// unrecognized type is reserved/greased.
pub(all) enum Http3UniEvent {
  ControlEstablished(Http3Settings)
  QpackEncoder
  QpackDecoder
  Push(UInt64)
  Reserved(UInt64)
} derive(Eq, Debug)

///|
/// One endpoint of an HTTP/3 connection: its role, the settings it advertises, and — once the peer
/// opens its control stream — the peer's settings.
pub struct Http3Conn {
  role : Http3Role
  local_settings : Http3Settings
  mut peer_settings : Http3Settings?
  mut control_seen : Bool
}

///|
/// A connection that will advertise `local_settings`.
pub fn Http3Conn::new(
  role : Http3Role,
  local_settings : Http3Settings,
) -> Http3Conn {
  { role, local_settings, peer_settings: None, control_seen: false, }
}

///|
/// The bytes to write when opening our control stream: the control-stream type then our SETTINGS
/// frame, which a control stream must send first (RFC 9114 §6.2.1).
pub fn Http3Conn::open_control_stream(self : Http3Conn) -> Bytes {
  http3_encode_control_stream(self.local_settings.to_pairs())
}

///|
/// Accept a peer's unidirectional stream from its opening bytes: classify it and, for the control
/// stream, decode and keep the peer's SETTINGS. A second control stream, a control stream not
/// opening with SETTINGS, and a server receiving a push stream are all connection errors
/// (RFC 9114 §6.2).
pub fn Http3Conn::accept_uni_stream(
  self : Http3Conn,
  bytes : Bytes,
) -> Http3UniEvent raise {
  guard http3_decode_stream_type(bytes[:]) is Some((stream_type, _)) else {
    raise Http3ConnError("truncated unidirectional stream header")
  }
  match stream_type {
    ControlStream => {
      if self.control_seen {
        raise Http3ConnError(
          "a second control stream is a connection error (RFC 9114 §6.2.1)",
        )
      }
      let settings = match http3_decode_control_stream(bytes[:]) {
        Some(s) => s
        None =>
          raise Http3ConnError(
            "a control stream must open with a SETTINGS frame",
          )
      }
      self.control_seen = true
      self.peer_settings = Some(settings)
      ControlEstablished(settings)
    }
    QpackEncoderStream => QpackEncoder
    QpackDecoderStream => QpackDecoder
    PushStream(id) => {
      if self.role == ServerSide {
        raise Http3ConnError(
          "a server must not receive a push stream (RFC 9114 §6.2.2)",
        )
      }
      Push(id)
    }
    ReservedStream(t) => Reserved(t)
  }
}

///|
/// Whether the peer's control-stream SETTINGS have been received (connection setup complete).
pub fn Http3Conn::settings_established(self : Http3Conn) -> Bool {
  self.peer_settings is Some(_)
}

///|
/// The peer's advertised settings, once its control stream has been accepted.
pub fn Http3Conn::peer_settings(self : Http3Conn) -> Http3Settings? {
  self.peer_settings
}