// QUIC frames (RFC 9000 §19) — the content carried inside a packet's payload. Each
// frame starts with a varint type; its fields are varints too, except the fixed-width
// connection-ID tokens and path-validation data. This covers the full §19 frame set:
// PADDING/PING/ACK, CRYPTO and STREAM, the flow-control family (MAX_*, *_BLOCKED),
// the stream-control frames (RESET_STREAM/STOP_SENDING), connection management
// (NEW_CONNECTION_ID/RETIRE_CONNECTION_ID/NEW_TOKEN), path validation
// (PATH_CHALLENGE/PATH_RESPONSE), and the lifecycle frames
// (HANDSHAKE_DONE/CONNECTION_CLOSE).

///|
/// A decoded QUIC frame. `Padding` collapses a run of zero bytes to its length;
/// `Stream` carries application data on a stream, with a byte `offset` and a `fin`
/// flag marking the end of the stream (RFC 9000 §19.8).
pub(all) enum QuicFrame {
  Padding(Int)
  Ping
  Crypto(offset~ : UInt64, data~ : Bytes)
  Stream(id~ : UInt64, offset~ : UInt64, fin~ : Bool, data~ : Bytes)
  Ack(
    largest~ : UInt64,
    delay~ : UInt64,
    first_range~ : UInt64,
    ranges~ : Array[(UInt64, UInt64)]
  )
  ResetStream(id~ : UInt64, error_code~ : UInt64, final_size~ : UInt64)
  StopSending(id~ : UInt64, error_code~ : UInt64)
  NewToken(token~ : Bytes)
  MaxData(UInt64)
  MaxStreamData(id~ : UInt64, max~ : UInt64)
  // `bidi` selects the 0x12/0x16 (bidirectional) vs 0x13/0x17 (unidirectional) type.
  MaxStreams(bidi~ : Bool, max~ : UInt64)
  DataBlocked(UInt64)
  StreamDataBlocked(id~ : UInt64, max~ : UInt64)
  StreamsBlocked(bidi~ : Bool, max~ : UInt64)
  NewConnectionId(
    seq~ : UInt64,
    retire_prior_to~ : UInt64,
    conn_id~ : Bytes,
    reset_token~ : Bytes
  )
  RetireConnectionId(UInt64)
  PathChallenge(Bytes)
  PathResponse(Bytes)
  HandshakeDone
  // `frame_type` is `Some` for a transport-error close (0x1c) and `None` for an
  // application-error close (0x1d), which omits the triggering frame type.
  ConnectionClose(error_code~ : UInt64, frame_type~ : UInt64?, reason~ : Bytes)
} derive(Eq, Debug)

///|
/// Encode a frame to its wire bytes.
pub fn encode_frame(f : QuicFrame) -> Bytes {
  let buf = Buffer()
  match f {
    Padding(n) =>
      for _i = 0; _i < n; _i = _i + 1 {
        buf.write_byte(b'\x00')
      }
    Ping => buf.write_bytes(quic_varint_encode(1UL)[:])
    Crypto(offset~, data~) => {
      buf.write_bytes(quic_varint_encode(6UL)[:])
      buf.write_bytes(quic_varint_encode(offset)[:])
      buf.write_bytes(quic_varint_encode(data.length().to_uint64())[:])
      buf.write_bytes(data[:])
    }
    Stream(id~, offset~, fin~, data~) => {
      // Always emit with the OFF and LEN bits set (0x0e) so the frame is
      // self-delimiting; the FIN bit rides the low bit.
      let type_byte = 0x0e | (if fin { 1 } else { 0 })
      buf.write_bytes(quic_varint_encode(type_byte.to_uint64())[:])
      buf.write_bytes(quic_varint_encode(id)[:])
      buf.write_bytes(quic_varint_encode(offset)[:])
      buf.write_bytes(quic_varint_encode(data.length().to_uint64())[:])
      buf.write_bytes(data[:])
    }
    Ack(largest~, delay~, first_range~, ranges~) => {
      buf.write_bytes(quic_varint_encode(2UL)[:])
      buf.write_bytes(quic_varint_encode(largest)[:])
      buf.write_bytes(quic_varint_encode(delay)[:])
      buf.write_bytes(quic_varint_encode(ranges.length().to_uint64())[:])
      buf.write_bytes(quic_varint_encode(first_range)[:])
      for r in ranges {
        let (gap, len) = r
        buf.write_bytes(quic_varint_encode(gap)[:])
        buf.write_bytes(quic_varint_encode(len)[:])
      }
    }
    ResetStream(id~, error_code~, final_size~) => {
      buf.write_bytes(quic_varint_encode(4UL)[:])
      buf.write_bytes(quic_varint_encode(id)[:])
      buf.write_bytes(quic_varint_encode(error_code)[:])
      buf.write_bytes(quic_varint_encode(final_size)[:])
    }
    StopSending(id~, error_code~) => {
      buf.write_bytes(quic_varint_encode(5UL)[:])
      buf.write_bytes(quic_varint_encode(id)[:])
      buf.write_bytes(quic_varint_encode(error_code)[:])
    }
    NewToken(token~) => {
      buf.write_bytes(quic_varint_encode(7UL)[:])
      buf.write_bytes(quic_varint_encode(token.length().to_uint64())[:])
      buf.write_bytes(token[:])
    }
    MaxData(v) => {
      buf.write_bytes(quic_varint_encode(0x10UL)[:])
      buf.write_bytes(quic_varint_encode(v)[:])
    }
    MaxStreamData(id~, max~) => {
      buf.write_bytes(quic_varint_encode(0x11UL)[:])
      buf.write_bytes(quic_varint_encode(id)[:])
      buf.write_bytes(quic_varint_encode(max)[:])
    }
    MaxStreams(bidi~, max~) => {
      buf.write_bytes(quic_varint_encode(if bidi { 0x12UL } else { 0x13UL })[:])
      buf.write_bytes(quic_varint_encode(max)[:])
    }
    DataBlocked(v) => {
      buf.write_bytes(quic_varint_encode(0x14UL)[:])
      buf.write_bytes(quic_varint_encode(v)[:])
    }
    StreamDataBlocked(id~, max~) => {
      buf.write_bytes(quic_varint_encode(0x15UL)[:])
      buf.write_bytes(quic_varint_encode(id)[:])
      buf.write_bytes(quic_varint_encode(max)[:])
    }
    StreamsBlocked(bidi~, max~) => {
      buf.write_bytes(quic_varint_encode(if bidi { 0x16UL } else { 0x17UL })[:])
      buf.write_bytes(quic_varint_encode(max)[:])
    }
    NewConnectionId(seq~, retire_prior_to~, conn_id~, reset_token~) => {
      buf.write_bytes(quic_varint_encode(0x18UL)[:])
      buf.write_bytes(quic_varint_encode(seq)[:])
      buf.write_bytes(quic_varint_encode(retire_prior_to)[:])
      buf.write_byte(conn_id.length().to_byte())
      buf.write_bytes(conn_id[:])
      buf.write_bytes(reset_token[:])
    }
    RetireConnectionId(seq) => {
      buf.write_bytes(quic_varint_encode(0x19UL)[:])
      buf.write_bytes(quic_varint_encode(seq)[:])
    }
    PathChallenge(data) => {
      buf.write_bytes(quic_varint_encode(0x1aUL)[:])
      buf.write_bytes(data[:])
    }
    PathResponse(data) => {
      buf.write_bytes(quic_varint_encode(0x1bUL)[:])
      buf.write_bytes(data[:])
    }
    HandshakeDone => buf.write_bytes(quic_varint_encode(0x1eUL)[:])
    ConnectionClose(error_code~, frame_type~, reason~) => {
      match frame_type {
        Some(ft) => {
          buf.write_bytes(quic_varint_encode(0x1cUL)[:])
          buf.write_bytes(quic_varint_encode(error_code)[:])
          buf.write_bytes(quic_varint_encode(ft)[:])
        }
        None => {
          buf.write_bytes(quic_varint_encode(0x1dUL)[:])
          buf.write_bytes(quic_varint_encode(error_code)[:])
        }
      }
      buf.write_bytes(quic_varint_encode(reason.length().to_uint64())[:])
      buf.write_bytes(reason[:])
    }
  }
  buf.to_bytes()
}

///|
/// Parse one frame at the start of `b`, returning it and the bytes it occupied, or
/// `None` on a truncated frame or a type this brick does not yet decode. A PADDING
/// frame absorbs the whole run of leading zero bytes.
pub fn parse_frame(b : BytesView) -> (QuicFrame, Int)? {
  guard quic_varint_decode(b) is Some((ftype, tlen)) else { return None }
  match ftype {
    0UL => {
      let mut n = 0
      while n < b.length() && b[n].to_int() == 0 {
        n = n + 1
      }
      Some((Padding(n), n))
    }
    1UL => Some((Ping, tlen))
    2UL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((largest, l1)) else {
        return None
      }
      off = off + l1
      guard quic_varint_decode(b[off:]) is Some((delay, l2)) else {
        return None
      }
      off = off + l2
      guard quic_varint_decode(b[off:]) is Some((count, l3)) else {
        return None
      }
      off = off + l3
      guard quic_varint_decode(b[off:]) is Some((first_range, l4)) else {
        return None
      }
      off = off + l4
      let ranges : Array[(UInt64, UInt64)] = []
      for _i = 0; _i < count.to_int(); _i = _i + 1 {
        guard quic_varint_decode(b[off:]) is Some((gap, lg)) else {
          return None
        }
        off = off + lg
        guard quic_varint_decode(b[off:]) is Some((rlen, lr)) else {
          return None
        }
        off = off + lr
        ranges.push((gap, rlen))
      }
      Some((Ack(largest~, delay~, first_range~, ranges~), off))
    }
    6UL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((offset, olen)) else {
        return None
      }
      off = off + olen
      guard quic_varint_decode(b[off:]) is Some((length, llen)) else {
        return None
      }
      off = off + llen
      let dlen = length.to_int()
      if b.length() < off + dlen {
        return None
      }
      let data = b[off:off + dlen].to_owned()
      off = off + dlen
      Some((Crypto(offset~, data~), off))
    }
    ty if ty >= 8UL && ty <= 15UL => {
      let ti = ty.to_int()
      let has_off = (ti & 0x04) != 0
      let has_len = (ti & 0x02) != 0
      let fin = (ti & 0x01) != 0
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((id, ilen)) else { return None }
      off = off + ilen
      let mut offset = 0UL
      if has_off {
        guard quic_varint_decode(b[off:]) is Some((o, olen)) else {
          return None
        }
        offset = o
        off = off + olen
      }
      let data = if has_len {
        guard quic_varint_decode(b[off:]) is Some((length, llen)) else {
          return None
        }
        off = off + llen
        let dlen = length.to_int()
        if b.length() < off + dlen {
          return None
        }
        let d = b[off:off + dlen].to_owned()
        off = off + dlen
        d
      } else {
        // No length field: the stream data runs to the end of the buffer.
        let d = b[off:].to_owned()
        off = b.length()
        d
      }
      Some((Stream(id~, offset~, fin~, data~), off))
    }
    4UL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((id, l1)) else { return None }
      off = off + l1
      guard quic_varint_decode(b[off:]) is Some((error_code, l2)) else {
        return None
      }
      off = off + l2
      guard quic_varint_decode(b[off:]) is Some((final_size, l3)) else {
        return None
      }
      off = off + l3
      Some((ResetStream(id~, error_code~, final_size~), off))
    }
    5UL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((id, l1)) else { return None }
      off = off + l1
      guard quic_varint_decode(b[off:]) is Some((error_code, l2)) else {
        return None
      }
      off = off + l2
      Some((StopSending(id~, error_code~), off))
    }
    7UL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((length, ll)) else {
        return None
      }
      off = off + ll
      let n = length.to_int()
      if b.length() < off + n {
        return None
      }
      let token = b[off:off + n].to_owned()
      off = off + n
      Some((NewToken(token~), off))
    }
    0x10UL => {
      guard quic_varint_decode(b[tlen:]) is Some((v, vl)) else { return None }
      Some((MaxData(v), tlen + vl))
    }
    0x11UL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((id, l1)) else { return None }
      off = off + l1
      guard quic_varint_decode(b[off:]) is Some((max, l2)) else { return None }
      off = off + l2
      Some((MaxStreamData(id~, max~), off))
    }
    ty if ty == 0x12UL || ty == 0x13UL => {
      guard quic_varint_decode(b[tlen:]) is Some((max, vl)) else { return None }
      Some((MaxStreams(bidi=ty == 0x12UL, max~), tlen + vl))
    }
    0x14UL => {
      guard quic_varint_decode(b[tlen:]) is Some((v, vl)) else { return None }
      Some((DataBlocked(v), tlen + vl))
    }
    0x15UL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((id, l1)) else { return None }
      off = off + l1
      guard quic_varint_decode(b[off:]) is Some((max, l2)) else { return None }
      off = off + l2
      Some((StreamDataBlocked(id~, max~), off))
    }
    ty if ty == 0x16UL || ty == 0x17UL => {
      guard quic_varint_decode(b[tlen:]) is Some((max, vl)) else { return None }
      Some((StreamsBlocked(bidi=ty == 0x16UL, max~), tlen + vl))
    }
    0x18UL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((seq, l1)) else { return None }
      off = off + l1
      guard quic_varint_decode(b[off:]) is Some((retire_prior_to, l2)) else {
        return None
      }
      off = off + l2
      if b.length() < off + 1 {
        return None
      }
      let cid_len = b[off].to_int()
      off = off + 1
      if b.length() < off + cid_len + 16 {
        return None
      }
      let conn_id = b[off:off + cid_len].to_owned()
      off = off + cid_len
      let reset_token = b[off:off + 16].to_owned()
      off = off + 16
      Some(
        (NewConnectionId(seq~, retire_prior_to~, conn_id~, reset_token~), off),
      )
    }
    0x19UL => {
      guard quic_varint_decode(b[tlen:]) is Some((seq, vl)) else { return None }
      Some((RetireConnectionId(seq), tlen + vl))
    }
    0x1aUL => {
      if b.length() < tlen + 8 {
        return None
      }
      Some((PathChallenge(b[tlen:tlen + 8].to_owned()), tlen + 8))
    }
    0x1bUL => {
      if b.length() < tlen + 8 {
        return None
      }
      Some((PathResponse(b[tlen:tlen + 8].to_owned()), tlen + 8))
    }
    0x1eUL => Some((HandshakeDone, tlen))
    0x1cUL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((error_code, l1)) else {
        return None
      }
      off = off + l1
      guard quic_varint_decode(b[off:]) is Some((ft, l2)) else { return None }
      off = off + l2
      guard quic_varint_decode(b[off:]) is Some((rlen, l3)) else { return None }
      off = off + l3
      let n = rlen.to_int()
      if b.length() < off + n {
        return None
      }
      let reason = b[off:off + n].to_owned()
      off = off + n
      Some((ConnectionClose(error_code~, frame_type=Some(ft), reason~), off))
    }
    0x1dUL => {
      let mut off = tlen
      guard quic_varint_decode(b[off:]) is Some((error_code, l1)) else {
        return None
      }
      off = off + l1
      guard quic_varint_decode(b[off:]) is Some((rlen, l2)) else { return None }
      off = off + l2
      let n = rlen.to_int()
      if b.length() < off + n {
        return None
      }
      let reason = b[off:off + n].to_owned()
      off = off + n
      Some((ConnectionClose(error_code~, frame_type=None, reason~), off))
    }
    _ => None
  }
}