///|
/// OpenPGP packet type IDs (RFC 9580 §5).
pub(all) enum PacketTag {
  Pkesk
  Skesk
  Signature
  OnePassSig
  SecretKey
  PublicKey
  SecretSubkey
  Compressed
  Sed
  Marker
  Literal
  Trust
  UserId
  PublicSubkey
  UserAttribute
  Seipd
  Mdc
  AeadEncData
  Unknown(Int32)
} derive(Eq, Debug)

///|
pub fn PacketTag::number(self : PacketTag) -> Int32 {
  match self {
    Pkesk => 1
    Signature => 2
    Skesk => 3
    OnePassSig => 4
    SecretKey => 5
    PublicKey => 6
    SecretSubkey => 7
    Compressed => 8
    Sed => 9
    Marker => 10
    Literal => 11
    Trust => 12
    UserId => 13
    PublicSubkey => 14
    UserAttribute => 17
    Seipd => 18
    Mdc => 19
    AeadEncData => 20
    Unknown(n) => n
  }
}

///|
pub fn tag_of_number(n : Int32) -> PacketTag {
  match n {
    1 => Pkesk
    2 => Signature
    3 => Skesk
    4 => OnePassSig
    5 => SecretKey
    6 => PublicKey
    7 => SecretSubkey
    8 => Compressed
    9 => Sed
    10 => Marker
    11 => Literal
    12 => Trust
    13 => UserId
    14 => PublicSubkey
    17 => UserAttribute
    18 => Seipd
    19 => Mdc
    20 => AeadEncData
    _ => Unknown(n)
  }
}

///|
/// One parsed packet: type plus the raw packet body (header stripped).
/// Higher-level parsers decode bodies into typed structures.
pub(all) struct RawPacket {
  tag : PacketTag
  body : Bytes
} derive(Debug)

///|
pub fn RawPacket::tag(self : RawPacket) -> PacketTag {
  self.tag
}

///|
pub fn RawPacket::body(self : RawPacket) -> Bytes {
  self.body
}

///|
/// Parse a sequence of packet headers+bodies from `data`. Handles both old
/// and new format headers and accumulates partial body lengths.
pub fn parse_packets(data : BytesView) -> Array[RawPacket] raise {
  let packets : Array[RawPacket] = []
  let r = Reader::new(data)
  while r.remaining() > 0 {
    packets.push(read_one_packet(r))
  }
  packets
}

///|
/// Serialize one packet with a new-format header.
pub fn serialize_packet(tag : PacketTag, body : BytesView) -> Bytes {
  let w = Writer::new()
  w.u8(0xC0 | tag.number())
  write_new_length(w, body.length())
  w.bytes(body)
  w.to_bytes()
}

///|
/// Serialize one packet with an old-format header and fixed-length encoding.
pub fn serialize_packet_old(tag : PacketTag, body : BytesView) -> Bytes {
  let w = Writer::new()
  let len = body.length()
  let mode : Int32 = if len < 256 { 0 } else if len < 65536 { 1 } else { 2 }
  w.u8(0x80 | (tag.number() << 2) | mode)
  write_old_length(w, len)
  w.bytes(body)
  w.to_bytes()
}

///|
fn write_new_length(w : Writer, length : Int32) -> Unit {
  if length < 192 {
    w.u8(length)
  } else if length < 8384 {
    let enc = length - 192 + 192 * 256
    w.u8(enc >> 8)
    w.u8(enc)
  } else {
    w.u8(255)
    w.u32(length)
  }
}

///|
fn write_old_length(w : Writer, length : Int32) -> Unit {
  if length < 256 {
    w.u8(length)
  } else if length < 65536 {
    w.u16(length)
  } else {
    w.u32(length)
  }
}

///|
fn read_one_packet(r : Reader) -> RawPacket raise {
  guard r.remaining() >= 2 else { @error.fail("packet: truncated header") }
  let first = r.read_u8()
  guard (first & 0x80) != 0 else {
    @error.fail("packet: invalid header octet \{first}")
  }
  let tag : PacketTag = if (first & 0x40) != 0 {
    // New format.
    tag_of_number(first & 0x3F)
  } else {
    // Old format.
    tag_of_number((first >> 2) & 0x0F)
  }
  let chunks : Array[Bytes] = []
  if (first & 0x40) != 0 {
    // New format lengths.
    while true {
      let len = read_new_length(r)
      if len < 0 {
        chunks.push(r.read_bytes(-len - 1))
      } else {
        chunks.push(r.read_bytes(len))
        break
      }
    }
  } else {
    // Old format lengths: 0,1,2,3 = 1,2,4 octets or indeterminate.
    let mode = first & 0x03
    match mode {
      0 => chunks.push(r.read_bytes(r.read_u8()))
      1 => chunks.push(r.read_bytes(r.read_u16()))
      2 => chunks.push(r.read_bytes(r.read_u32()))
      _ =>
        // Mode 3 (indeterminate length): the body runs to the end of the
        // enclosing stream — the same read-to-end semantics go-crypto uses.
        // RFC 4880 §4.2.1 constrains this form to packets whose extent is
        // unknown in advance (de facto the last packet of a stream), so a
        // mode-3 packet followed by more packets is malformed input and
        // those trailing packets are consumed into its body (N6, documented
        // limitation: there is no in-band marker to find the true end).
        chunks.push(r.read_rest())
    }
  }
  let body : Bytes = if chunks.length() == 1 {
    chunks[0]
  } else {
    // Partial-length reassembly in one pass: repeated Bytes::add would
    // copy the whole prefix per chunk (O(n^2) on attacker-chosen input).
    let mut total = 0
    for c in chunks {
      total = total + c.length()
    }
    let out : Array[Byte] = Array(capacity=total)
    for c in chunks {
      for b in c {
        out.push(b)
      }
    }
    Bytes::from_array(out)
  }
  RawPacket::{ tag, body, }
}

///|
/// Returns the length, or `-(code)-1` for a partial length code.
fn read_new_length(r : Reader) -> Int32 raise {
  let first = r.read_u8()
  if first < 192 {
    first
  } else if first < 224 {
    let second = r.read_u8()
    ((first - 192) << 8) + second + 192
  } else if first < 255 {
    // Partial body length: 1 << (first & 0x1F) octets.
    -(1 << (first & 0x1F)) - 1
  } else {
    r.read_u32()
  }
}

///|
pub extend PacketTag with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend PacketTag with Eq::{not_equal, equal}

///|
pub extend RawPacket with @moonbitlang/core/debug.Debug::{to_repr}