///|
/// One media-type parameter after syntactic unquoting.
pub(all) struct Parameter {
  name : String
  value : String
} derive(Debug, Eq)

///|
/// Parsed multipart/form-data Content-Type.
pub(all) struct MediaType {
  media_type : String
  boundary : String
  parameters : Array[Parameter]
} derive(Debug, Eq)

///|
/// One part header. Header names are normalized to lowercase while values
/// remain untrusted protocol text.
pub(all) struct HeaderField {
  name : String
  value : String
} derive(Debug, Eq)

///|
/// Metadata emitted before a part body.
///
/// `filename` is untrusted metadata and must never be used as a filesystem
/// path without an application-owned policy.
pub(all) struct PartMeta {
  index : Int
  name : String
  filename : String?
  content_type : String?
  headers : Array[HeaderField]
} derive(Debug, Eq)

///|
/// Parser and writer resource limits. All limits are hard, fail-closed
/// boundaries; exceeding one must not produce a successful message.
pub(all) struct Limits {
  max_total_bytes : Int64
  max_parts : Int
  max_headers_per_part : Int
  max_header_bytes : Int
  max_header_line_bytes : Int
  max_part_bytes : Int64
} derive(Debug, Eq)

///|
/// Conservative defaults for examples and initial integrations.
///
/// Applications are expected to choose limits appropriate for their threat
/// model before accepting untrusted uploads.
pub fn default_limits() -> Limits {
  {
    max_total_bytes: 1073741824L,
    max_parts: 128,
    max_headers_per_part: 32,
    max_header_bytes: 32768,
    max_header_line_bytes: 8192,
    max_part_bytes: 1073741824L,
  }
}

///|
/// Validate a resource-limit configuration before it is attached to a parser
/// or writer.  This is public to implementation packages but intentionally is
/// not re-exported by the root facade: applications normally construct a
/// `Limits` value and pass it to `MultipartParser::new` or
/// `MultipartWriter::new`.
pub fn validate_limits(limits : Limits) -> Unit raise MultipartError {
  if limits.max_total_bytes <= 0L {
    raise LimitExceeded(
      kind=TotalBytes,
      limit=limits.max_total_bytes,
      observed=0L,
    )
  }
  if limits.max_parts <= 0 {
    raise LimitExceeded(
      kind=Parts,
      limit=limits.max_parts.to_int64(),
      observed=0L,
    )
  }
  if limits.max_headers_per_part <= 0 {
    raise LimitExceeded(
      kind=HeadersPerPart,
      limit=limits.max_headers_per_part.to_int64(),
      observed=0L,
    )
  }
  if limits.max_header_bytes <= 0 {
    raise LimitExceeded(
      kind=HeaderBytes,
      limit=limits.max_header_bytes.to_int64(),
      observed=0L,
    )
  }
  if limits.max_header_line_bytes <= 0 ||
    limits.max_header_line_bytes > limits.max_header_bytes {
    raise LimitExceeded(
      kind=HeaderLineBytes,
      limit=limits.max_header_line_bytes.to_int64(),
      observed=0L,
    )
  }
  if limits.max_part_bytes <= 0L {
    raise LimitExceeded(
      kind=PartBytes,
      limit=limits.max_part_bytes,
      observed=0L,
    )
  }
}

///|
/// Resource dimension that exceeded its configured limit.
pub(all) enum LimitKind {
  TotalBytes
  Parts
  HeadersPerPart
  HeaderBytes
  HeaderLineBytes
  PartBytes
} derive(Debug, Eq)

///|
/// Public lifecycle phase used in deterministic errors and diagnostics.
pub(all) enum ParserPhase {
  Preamble
  Headers
  Body
  Epilogue
  Done
  Failed
} derive(Debug, Eq)

///|
/// Ordered, binary-safe parser output.
pub(all) enum MultipartEvent {
  PartBegin(PartMeta)
  PartData(Bytes)
  PartEnd(Int)
  MessageEnd
} derive(Debug, Eq)

///|
/// Redacted counters for one parser instance.
///
/// The value deliberately contains only counts and byte sizes. It never
/// stores body bytes, header values, field names, or filenames.
pub(all) struct ParserStats {
  bytes_seen : Int64
  parts_started : Int
  parts_completed : Int
  header_bytes : Int64
  body_bytes : Int64
  events_emitted : Int64
  max_pending_body_bytes : Int
  max_matcher_retained_bytes : Int
  max_buffered_bytes : Int
} derive(Debug, Eq)

///|
/// A stable, redacted view of a protocol error for logs and metrics.
pub(all) struct MultipartDiagnostic {
  code : String
  phase : ParserPhase?
  offset : Int64?
  part_index : Int?
  limit_kind : LimitKind?
  limit : Int64?
  observed : Int64?
} derive(Debug, Eq)

///|
/// Exact protocol, resource, and lifecycle failures.
pub(all) suberror MultipartError {
  InvalidContentType(reason~ : String)
  InvalidBoundary(reason~ : String)
  MalformedHeader(part_index~ : Int, reason~ : String)
  MissingDisposition(part_index~ : Int)
  MissingName(part_index~ : Int)
  UnexpectedEof(phase~ : ParserPhase, offset~ : Int64)
  LimitExceeded(kind~ : LimitKind, limit~ : Int64, observed~ : Int64)
  InvalidState(operation~ : String, phase~ : ParserPhase)
  BoundaryCollision(part_index~ : Int)
  BodyLengthMismatch(part_index~ : Int, expected~ : Int64, observed~ : Int64)
  Unsupported(feature~ : String)
} derive(Debug, Eq)

///|
/// Convert a protocol error into log-safe structured data.
///
/// Reasons, header values, field names, filenames, and body bytes are never
/// copied into the result.
pub fn redact_error(error : MultipartError) -> MultipartDiagnostic {
  match error {
    InvalidContentType(..) =>
      {
        code: "invalid_content_type",
        phase: None,
        offset: None,
        part_index: None,
        limit_kind: None,
        limit: None,
        observed: None,
      }
    InvalidBoundary(..) =>
      {
        code: "invalid_boundary",
        phase: None,
        offset: None,
        part_index: None,
        limit_kind: None,
        limit: None,
        observed: None,
      }
    MalformedHeader(part_index~, ..) =>
      {
        code: "malformed_header",
        phase: Some(Headers),
        offset: None,
        part_index: Some(part_index),
        limit_kind: None,
        limit: None,
        observed: None,
      }
    MissingDisposition(part_index~) =>
      {
        code: "missing_disposition",
        phase: Some(Headers),
        offset: None,
        part_index: Some(part_index),
        limit_kind: None,
        limit: None,
        observed: None,
      }
    MissingName(part_index~) =>
      {
        code: "missing_name",
        phase: Some(Headers),
        offset: None,
        part_index: Some(part_index),
        limit_kind: None,
        limit: None,
        observed: None,
      }
    UnexpectedEof(phase~, offset~) =>
      {
        code: "unexpected_eof",
        phase: Some(phase),
        offset: Some(offset),
        part_index: None,
        limit_kind: None,
        limit: None,
        observed: None,
      }
    LimitExceeded(kind~, limit~, observed~) =>
      {
        code: "limit_exceeded",
        phase: None,
        offset: None,
        part_index: None,
        limit_kind: Some(kind),
        limit: Some(limit),
        observed: Some(observed),
      }
    InvalidState(phase~, ..) =>
      {
        code: "invalid_state",
        phase: Some(phase),
        offset: None,
        part_index: None,
        limit_kind: None,
        limit: None,
        observed: None,
      }
    BoundaryCollision(part_index~) =>
      {
        code: "boundary_collision",
        phase: None,
        offset: None,
        part_index: Some(part_index),
        limit_kind: None,
        limit: None,
        observed: None,
      }
    BodyLengthMismatch(part_index~, expected~, observed~) =>
      {
        code: "body_length_mismatch",
        phase: Some(Body),
        offset: None,
        part_index: Some(part_index),
        limit_kind: None,
        limit: Some(expected),
        observed: Some(observed),
      }
    Unsupported(..) =>
      {
        code: "unsupported",
        phase: None,
        offset: None,
        part_index: None,
        limit_kind: None,
        limit: None,
        observed: None,
      }
  }
}