// ===========================================================================
// moon-multipart — Core data model
// ===========================================================================

// ---------------------------------------------------------------------------
// Security limits
// ---------------------------------------------------------------------------

///|
/// Configurable security limits for multipart parsing.
/// Set any field to `-1` for unlimited.
pub(all) struct Limits {
  /// Maximum total parts allowed (default 1000)
  max_parts : Int
  /// Maximum header block size per part in bytes (default 8192)
  max_header_size : Int
  /// Maximum text field value in bytes (default 1 MB)
  max_field_size : Int
  /// Maximum file upload in bytes (default 100 MB)
  max_file_size : Int
  /// Maximum total request body in bytes (default 500 MB)
  max_total_size : Int
  /// Maximum filename length in characters (default 255)
  max_filename_len : Int
} derive(Debug)

///|
pub fn Limits::default() -> Limits {
  {
    max_parts: 1000,
    max_header_size: 8192,
    max_field_size: 1048576,
    max_file_size: 104857600,
    max_total_size: 524288000,
    max_filename_len: 255,
  }
}

///|
pub fn Limits::permissive() -> Limits {
  {
    max_parts: 100000,
    max_header_size: 65536,
    max_field_size: 104857600,
    max_file_size: 1073741824,
    max_total_size: -1,
    max_filename_len: 1024,
  }
}

///|
pub fn Limits::strict() -> Limits {
  {
    max_parts: 50,
    max_header_size: 4096,
    max_field_size: 65536,
    max_file_size: 10485760,
    max_total_size: 52428800,
    max_filename_len: 255,
  }
}

// ---------------------------------------------------------------------------
// Parse mode
// ---------------------------------------------------------------------------

///|
/// Parsing strictness mode.
pub(all) enum ParseMode {
  /// Strict RFC 7578 compliance: reject non-standard constructs like filename*
  Strict
  /// Compatible mode: accept common extensions with warnings
  Compatible
} derive(Eq, Debug)

///|
/// Options controlling parser behavior.
pub(all) struct ParseOptions {
  /// Parsing mode (Strict or Compatible)
  mode : ParseMode
  /// Security limits
  limits : Limits
  /// In strict mode, reject filename* (RFC 5987) parameters
  reject_filename_star : Bool
} derive(Debug)

///|
pub fn ParseOptions::default() -> ParseOptions {
  { mode: Compatible, limits: Limits::default(), reject_filename_star: true }
}

///|
pub fn ParseOptions::strict_rfc() -> ParseOptions {
  { mode: Strict, limits: Limits::default(), reject_filename_star: true }
}

///|
pub fn ParseOptions::compatible() -> ParseOptions {
  { mode: Compatible, limits: Limits::default(), reject_filename_star: false }
}

// ---------------------------------------------------------------------------
// Part types
// ---------------------------------------------------------------------------

///|
/// A single parsed multipart part — either a text field or a file upload.
pub(all) enum Part {
  /// Text form field: (name, value)
  Field(String, String)
  /// File upload: (name, filename, content_type?, data)
  File(String, String, String?, Bytes)
} derive(Debug)

///|
pub fn Part::name(self : Part) -> String {
  match self {
    Field(name, _) => name
    File(name, _, _, _) => name
  }
}

///|
pub fn Part::value(self : Part) -> String? {
  match self {
    Field(_, v) => Some(v)
    File(_, _, _, _) => None
  }
}

///|
pub fn Part::filename(self : Part) -> String? {
  match self {
    Field(_, _) => None
    File(_, f, _, _) => Some(f)
  }
}

///|
pub fn Part::content_type(self : Part) -> String? {
  match self {
    Field(_, _) => None
    File(_, _, ct, _) => ct
  }
}

///|
pub fn Part::data(self : Part) -> Bytes? {
  match self {
    Field(_, _) => None
    File(_, _, _, d) => Some(d)
  }
}

///|
pub fn Part::is_field(self : Part) -> Bool {
  match self {
    Field(_, _) => true
    _ => false
  }
}

///|
pub fn Part::is_file(self : Part) -> Bool {
  match self {
    File(_, _, _, _) => true
    _ => false
  }
}

// ---------------------------------------------------------------------------
// MultipartForm — high-level parse result
// ---------------------------------------------------------------------------

///|
/// High-level result of parsing a complete multipart body.
/// Preserves insertion order and supports same-name fields/files.
pub(all) struct MultipartForm {
  /// All parts in original order
  parts : Array[Part]
} derive(Debug)

///|
pub fn MultipartForm::new() -> MultipartForm {
  { parts: [] }
}

///|
/// Get ALL field values with the given name (supports same-name fields).
pub fn MultipartForm::field_values(
  self : MultipartForm,
  name : String,
) -> Array[String] {
  let result : Array[String] = []
  for part in self.parts {
    match part {
      Field(n, v) => if n == name { result.push(v.to_string()) } else { () }
      _ => ()
    }
  }
  result
}

///|
/// Get the first field value with the given name (convenience).
pub fn MultipartForm::field(self : MultipartForm, name : String) -> String? {
  for part in self.parts {
    match part {
      Field(n, v) => if n == name { return Some(v.to_string()) } else { () }
      _ => ()
    }
  }
  None
}

///|
/// Get ALL file parts with the given field name (supports multi-file upload).
pub fn MultipartForm::files(self : MultipartForm, name : String) -> Array[Part] {
  let result : Array[Part] = []
  for part in self.parts {
    match part {
      File(n, _, _, _) => if n == name { result.push(part) } else { () }
      _ => ()
    }
  }
  result
}

///|
/// Get the first file with the given field name (convenience).
pub fn MultipartForm::file(self : MultipartForm, name : String) -> Part? {
  for part in self.parts {
    match part {
      File(n, _, _, _) => if n == name { return Some(part) } else { () }
      _ => ()
    }
  }
  None
}

///|
/// Total number of parts.
pub fn MultipartForm::len(self : MultipartForm) -> Int {
  self.parts.length()
}

// ---------------------------------------------------------------------------
// Errors
// ---------------------------------------------------------------------------

///|
/// Errors that can occur during multipart parsing or generation.
pub(all) enum MultipartError {
  MissingBoundary
  InvalidBoundary(String)
  HeaderTooLarge(Int)
  FieldTooLarge(String, Int)
  FileTooLarge(String, Int)
  TotalSizeExceeded(Int)
  TooManyParts(Int)
  MalformedHeader(String)
  MissingName
  MissingDisposition
  PathTraversal(String)
  FilenameTooLong(String, Int)
  IncompleteBody
  InvalidEncoding(String)
  NonCompliantHeader(String)
  ProcessingError(String)
} derive(Debug)

///|
pub fn MultipartError::to_string(self : MultipartError) -> String {
  match self {
    MissingBoundary => "Missing boundary parameter in Content-Type header"
    InvalidBoundary(s) => "Invalid boundary: " + s
    HeaderTooLarge(n) =>
      "Part header exceeds max size " + n.to_string() + " bytes"
    FieldTooLarge(name, n) =>
      "Field '" + name + "' exceeds max size " + n.to_string() + " bytes"
    FileTooLarge(name, n) =>
      "File '" + name + "' exceeds max size " + n.to_string() + " bytes"
    TotalSizeExceeded(n) =>
      "Total body exceeds max size " + n.to_string() + " bytes"
    TooManyParts(n) => "Too many parts: " + n.to_string()
    MalformedHeader(s) => "Malformed header: " + s
    MissingName => "Missing required 'name' parameter in Content-Disposition"
    MissingDisposition => "Missing Content-Disposition header in part"
    PathTraversal(s) => "Path traversal detected: " + s
    FilenameTooLong(name, n) =>
      "Filename '" + name + "' exceeds max length " + n.to_string()
    IncompleteBody => "Incomplete multipart body, closing boundary not found"
    InvalidEncoding(s) => "Invalid encoding: " + s
    NonCompliantHeader(s) => "Non-compliant header (strict mode): " + s
    ProcessingError(s) => "Processing error: " + s
  }
}

// ---------------------------------------------------------------------------
// Internal parser state machine
// ---------------------------------------------------------------------------

///|
enum ParsePhase {
  Preamble
  Headers
  Body
  BoundarySeen
  Done
}

// ---------------------------------------------------------------------------
// Streaming parse events
// ---------------------------------------------------------------------------

///|
/// Events emitted during streaming multipart parsing.
pub(all) enum ParseEvent {
  /// A new part is starting: (name, filename?, content_type?)
  PartBegin(String, String?, String?)
  /// A chunk of body data for the current part
  PartData(Bytes)
  /// The current part is complete
  PartEnd
  /// All parts have been parsed successfully
  Finished
} derive(Debug)