///|
let default_max_package_bytes : Int = 128 * 1024 * 1024

///|
let default_max_archive_entries : Int = 8192

///|
let default_max_entry_uncompressed_bytes : Int = 64 * 1024 * 1024

///|
let default_max_total_uncompressed_bytes : Int = 256 * 1024 * 1024

///|
let default_max_total_preserved_source_bytes : Int = default_max_package_bytes +
  65_535

///|
let default_max_xml_part_bytes : Int = 16 * 1024 * 1024

///|
let default_max_total_xml_bytes : Int = 128 * 1024 * 1024

///|
let default_max_xml_markup_tokens : Int = 2_000_000

///|
let default_max_relationship_records : Int = 1_000_000

///|
let default_max_relationship_id_chars : Int = 1_024

///|
let default_max_relationship_type_chars : Int = 16 * 1_024

///|
let default_max_relationship_target_chars : Int = 64 * 1_024

///|
let default_max_total_relationship_chars : Int = 16 * 1_024 * 1_024

///|
let default_max_materialized_cells : Int = 1_000_000

///|
let default_max_materialized_row_column_dimensions : Int = 1_000_000

///|
let default_max_row_column_dimension_work : Int = 1_000_000

///|
let default_max_kdf_iterations : Int = 1_000_000

///|
let default_max_workbook_sheets : Int = 1024

///|
let default_max_parser_items : Int = 2_000_000

///|
let default_max_parser_work_units : Int = 512 * 1024 * 1024

///|
/// A complete fail-closed resource policy for reading an XLSX package.
///
/// The type is opaque so every public read path receives a validated,
/// internally consistent policy. ZIP limits are enforced while inflating;
/// `max_xml_part_bytes` is enforced before any individual XML part is decoded,
/// aggregate decoded XML, markup tokens, materialized worksheet cells, and
/// retained row/column dimensions are bounded across the complete workbook
/// parse. Semantic ceilings also bound workbook fan-out, XML-backed parser
/// items, derived materialization, and cumulative parser work. Row/column
/// dimension expansion work has a separate ceiling so overlapping declarations
/// cannot hide excessive map-update work behind the retained-item limit;
/// `max_kdf_iterations` bounds encrypted-package password derivation.
pub struct ReadLimits {
  priv max_package_bytes : Int
  priv max_archive_entries : Int
  priv max_entry_uncompressed_bytes : Int
  priv max_total_uncompressed_bytes : Int
  priv max_total_preserved_source_bytes : Int
  priv max_xml_part_bytes : Int
  priv max_total_xml_bytes : Int
  priv max_xml_markup_tokens : Int
  priv max_relationship_records : Int
  priv max_relationship_id_chars : Int
  priv max_relationship_type_chars : Int
  priv max_relationship_target_chars : Int
  priv max_total_relationship_chars : Int
  priv max_materialized_cells : Int
  priv max_materialized_row_column_dimensions : Int
  priv max_row_column_dimension_work : Int
  priv max_kdf_iterations : Int
  priv max_workbook_sheets : Int
  priv max_parser_items : Int
  priv max_parser_work_units : Int
}

///|
/// Returns the production XLSX read policy: 128 MiB source packages, 8,192
/// entries, 64 MiB per entry, 256 MiB aggregate expansion, 16 MiB per XML part,
/// 128 MiB decoded XML, two million markup tokens, one million relationship
/// records with 16 MiB retained relationship text, one million materialized
/// cells, one million retained row/column dimensions, one million row/column
/// dimension expansion steps, 1,024 sheets, two million parser items, 512 Mi
/// parser work units, and one million Agile KDF iterations. Call `with_values`
/// for stricter limits.
pub fn ReadLimits::new() -> ReadLimits {
  {
    max_package_bytes: default_max_package_bytes,
    max_archive_entries: default_max_archive_entries,
    max_entry_uncompressed_bytes: default_max_entry_uncompressed_bytes,
    max_total_uncompressed_bytes: default_max_total_uncompressed_bytes,
    max_total_preserved_source_bytes: default_max_total_preserved_source_bytes,
    max_xml_part_bytes: default_max_xml_part_bytes,
    max_total_xml_bytes: default_max_total_xml_bytes,
    max_xml_markup_tokens: default_max_xml_markup_tokens,
    max_relationship_records: default_max_relationship_records,
    max_relationship_id_chars: default_max_relationship_id_chars,
    max_relationship_type_chars: default_max_relationship_type_chars,
    max_relationship_target_chars: default_max_relationship_target_chars,
    max_total_relationship_chars: default_max_total_relationship_chars,
    max_materialized_cells: default_max_materialized_cells,
    max_materialized_row_column_dimensions: default_max_materialized_row_column_dimensions,
    max_row_column_dimension_work: default_max_row_column_dimension_work,
    max_kdf_iterations: default_max_kdf_iterations,
    max_workbook_sheets: default_max_workbook_sheets,
    max_parser_items: default_max_parser_items,
    max_parser_work_units: default_max_parser_work_units,
  }
}

///|
/// Builds a validated XLSX read policy. All ceilings must be positive; the
/// per-entry and per-XML ceilings cannot exceed the aggregate expansion limit.
pub fn ReadLimits::with_values(
  max_package_bytes? : Int = default_max_package_bytes,
  max_archive_entries? : Int = default_max_archive_entries,
  max_entry_uncompressed_bytes? : Int = default_max_entry_uncompressed_bytes,
  max_total_uncompressed_bytes? : Int = default_max_total_uncompressed_bytes,
  max_total_preserved_source_bytes? : Int = default_max_total_preserved_source_bytes,
  max_xml_part_bytes? : Int = default_max_xml_part_bytes,
  max_total_xml_bytes? : Int = default_max_total_xml_bytes,
  max_xml_markup_tokens? : Int = default_max_xml_markup_tokens,
  max_relationship_records? : Int = default_max_relationship_records,
  max_relationship_id_chars? : Int = default_max_relationship_id_chars,
  max_relationship_type_chars? : Int = default_max_relationship_type_chars,
  max_relationship_target_chars? : Int = default_max_relationship_target_chars,
  max_total_relationship_chars? : Int = default_max_total_relationship_chars,
  max_materialized_cells? : Int = default_max_materialized_cells,
  max_materialized_row_column_dimensions? : Int = default_max_materialized_row_column_dimensions,
  max_row_column_dimension_work? : Int = default_max_row_column_dimension_work,
  max_kdf_iterations? : Int = default_max_kdf_iterations,
  max_workbook_sheets? : Int = default_max_workbook_sheets,
  max_parser_items? : Int = default_max_parser_items,
  max_parser_work_units? : Int = default_max_parser_work_units,
) -> ReadLimits raise XlsxError {
  if max_package_bytes <= 0 ||
    max_archive_entries <= 0 ||
    max_entry_uncompressed_bytes <= 0 ||
    max_total_uncompressed_bytes <= 0 ||
    max_total_preserved_source_bytes <= 0 ||
    max_xml_part_bytes <= 0 ||
    max_total_xml_bytes <= 0 ||
    max_xml_markup_tokens <= 0 ||
    max_relationship_records <= 0 ||
    max_relationship_id_chars <= 0 ||
    max_relationship_type_chars <= 0 ||
    max_relationship_target_chars <= 0 ||
    max_total_relationship_chars <= 0 ||
    max_materialized_cells <= 0 ||
    max_materialized_row_column_dimensions <= 0 ||
    max_row_column_dimension_work <= 0 ||
    max_kdf_iterations <= 0 ||
    max_workbook_sheets <= 0 ||
    max_parser_items <= 0 ||
    max_parser_work_units <= 0 {
    raise InvalidOptions(msg="XLSX read limits must be positive")
  }
  if max_entry_uncompressed_bytes > max_total_uncompressed_bytes {
    raise InvalidOptions(
      msg="XLSX entry limit exceeds aggregate uncompressed limit",
    )
  }
  if max_xml_part_bytes > max_entry_uncompressed_bytes {
    raise InvalidOptions(msg="XLSX XML limit exceeds entry limit")
  }
  {
    max_package_bytes,
    max_archive_entries,
    max_entry_uncompressed_bytes,
    max_total_uncompressed_bytes,
    max_total_preserved_source_bytes,
    max_xml_part_bytes,
    max_total_xml_bytes,
    max_xml_markup_tokens,
    max_relationship_records,
    max_relationship_id_chars,
    max_relationship_type_chars,
    max_relationship_target_chars,
    max_total_relationship_chars,
    max_materialized_cells,
    max_materialized_row_column_dimensions,
    max_row_column_dimension_work,
    max_kdf_iterations,
    max_workbook_sheets,
    max_parser_items,
    max_parser_work_units,
  }
}

///|
/// Returns the maximum accepted compressed XLSX package size in bytes.
pub fn ReadLimits::max_package_bytes(self : ReadLimits) -> Int {
  self.max_package_bytes
}

///|
/// Returns the maximum number of ZIP entries accepted in one package.
pub fn ReadLimits::max_archive_entries(self : ReadLimits) -> Int {
  self.max_archive_entries
}

///|
/// Returns the maximum inflated size of one ZIP entry in bytes.
pub fn ReadLimits::max_entry_uncompressed_bytes(self : ReadLimits) -> Int {
  self.max_entry_uncompressed_bytes
}

///|
/// Returns the maximum aggregate inflated size of all ZIP entries in bytes.
pub fn ReadLimits::max_total_uncompressed_bytes(self : ReadLimits) -> Int {
  self.max_total_uncompressed_bytes
}

///|
/// Returns the maximum aggregate compressed source bytes a bounded archive may
/// preserve for lossless republishing.
pub fn ReadLimits::max_total_preserved_source_bytes(self : ReadLimits) -> Int {
  self.max_total_preserved_source_bytes
}

///|
/// Returns the maximum size of one XML-like OOXML part in bytes.
pub fn ReadLimits::max_xml_part_bytes(self : ReadLimits) -> Int {
  self.max_xml_part_bytes
}

///|
/// Returns the maximum aggregate XML-like bytes decoded during one read.
pub fn ReadLimits::max_total_xml_bytes(self : ReadLimits) -> Int {
  self.max_total_xml_bytes
}

///|
/// Returns the maximum aggregate markup-token starts scanned while decoding.
pub fn ReadLimits::max_xml_markup_tokens(self : ReadLimits) -> Int {
  self.max_xml_markup_tokens
}

///|
/// Returns the maximum aggregate relationship records scanned in one package.
pub fn ReadLimits::max_relationship_records(self : ReadLimits) -> Int {
  self.max_relationship_records
}

///|
/// Returns the maximum retained relationship id length in characters.
pub fn ReadLimits::max_relationship_id_chars(self : ReadLimits) -> Int {
  self.max_relationship_id_chars
}

///|
/// Returns the maximum relationship type URI length in characters.
pub fn ReadLimits::max_relationship_type_chars(self : ReadLimits) -> Int {
  self.max_relationship_type_chars
}

///|
/// Returns the maximum internal/external relationship target length.
pub fn ReadLimits::max_relationship_target_chars(self : ReadLimits) -> Int {
  self.max_relationship_target_chars
}

///|
/// Returns the maximum aggregate relationship attribute characters scanned.
pub fn ReadLimits::max_total_relationship_chars(self : ReadLimits) -> Int {
  self.max_total_relationship_chars
}

///|
/// Returns the maximum worksheet cells materialized across the workbook.
pub fn ReadLimits::max_materialized_cells(self : ReadLimits) -> Int {
  self.max_materialized_cells
}

///|
/// Returns the maximum retained row/column dimensions across the workbook.
pub fn ReadLimits::max_materialized_row_column_dimensions(
  self : ReadLimits,
) -> Int {
  self.max_materialized_row_column_dimensions
}

///|
/// Returns the maximum row/column dimension expansion work across the workbook.
pub fn ReadLimits::max_row_column_dimension_work(self : ReadLimits) -> Int {
  self.max_row_column_dimension_work
}

///|
/// Returns the maximum accepted Agile password-KDF work factor.
pub fn ReadLimits::max_kdf_iterations(self : ReadLimits) -> Int {
  self.max_kdf_iterations
}

///|
/// Returns the maximum number of logical sheets accepted in one workbook.
pub fn ReadLimits::max_workbook_sheets(self : ReadLimits) -> Int {
  self.max_workbook_sheets
}

///|
/// Returns the maximum cumulative XML-backed and derived items that a read may
/// inspect or materialize.
pub fn ReadLimits::max_parser_items(self : ReadLimits) -> Int {
  self.max_parser_items
}

///|
/// Returns the maximum cumulative XML source units plus derived expansion work
/// accepted by the parser.
pub fn ReadLimits::max_parser_work_units(self : ReadLimits) -> Int {
  self.max_parser_work_units
}

///|
/// A fail-closed resource policy for XLSX serialization. The archive ceilings
/// are enforced before each generated part is retained; multi-stage VML
/// composition also debits every simultaneously retained intermediate from the
/// remaining aggregate allowance. `max_output_bytes` is then enforced by ZIP's
/// storage-free sizing pass before package allocation.
pub struct WriteLimits {
  priv max_output_bytes : Int
  priv max_archive_entries : Int
  priv max_entry_uncompressed_bytes : Int
  priv max_total_uncompressed_bytes : Int
}

///|
/// Builds a validated XLSX write policy. All ceilings must be positive and the
/// per-entry expansion ceiling cannot exceed the aggregate archive ceiling.
pub fn WriteLimits::with_values(
  max_output_bytes~ : Int,
  max_archive_entries~ : Int,
  max_entry_uncompressed_bytes~ : Int,
  max_total_uncompressed_bytes~ : Int,
) -> WriteLimits raise XlsxError {
  if max_output_bytes <= 0 ||
    max_archive_entries <= 0 ||
    max_entry_uncompressed_bytes <= 0 ||
    max_total_uncompressed_bytes <= 0 {
    raise InvalidOptions(msg="XLSX write limits must be positive")
  }
  if max_entry_uncompressed_bytes > max_total_uncompressed_bytes {
    raise InvalidOptions(
      msg="XLSX write entry limit exceeds aggregate uncompressed limit",
    )
  }
  {
    max_output_bytes,
    max_archive_entries,
    max_entry_uncompressed_bytes,
    max_total_uncompressed_bytes,
  }
}

///|
pub fn WriteLimits::max_output_bytes(self : WriteLimits) -> Int {
  self.max_output_bytes
}

///|
pub fn WriteLimits::max_archive_entries(self : WriteLimits) -> Int {
  self.max_archive_entries
}

///|
pub fn WriteLimits::max_entry_uncompressed_bytes(self : WriteLimits) -> Int {
  self.max_entry_uncompressed_bytes
}

///|
pub fn WriteLimits::max_total_uncompressed_bytes(self : WriteLimits) -> Int {
  self.max_total_uncompressed_bytes
}

///|
pub struct Options {
  max_calc_iterations : UInt
  password : String
  raw_cell_value : Bool
  tmp_dir : String
  short_date_pattern : String
  long_date_pattern : String
  long_time_pattern : String
  culture_info : String
} derive(Debug)

///|
pub fn Options::new() -> Options {
  {
    max_calc_iterations: 0,
    password: "",
    raw_cell_value: false,
    tmp_dir: "",
    short_date_pattern: "",
    long_date_pattern: "",
    long_time_pattern: "",
    culture_info: "",
  }
}

///|
pub fn Options::with_values(
  max_calc_iterations? : UInt = 0,
  password? : String = "",
  raw_cell_value? : Bool = false,
  tmp_dir? : String = "",
  short_date_pattern? : String = "",
  long_date_pattern? : String = "",
  long_time_pattern? : String = "",
  culture_info? : String = "",
) -> Options {
  {
    max_calc_iterations,
    password,
    raw_cell_value,
    tmp_dir,
    short_date_pattern,
    long_date_pattern,
    long_time_pattern,
    culture_info,
  }
}

///|
fn options_with_password(options : Options, password : String) -> Options {
  if password == "" || options.password == password {
    options
  } else {
    Options::with_values(
      max_calc_iterations=options.max_calc_iterations,
      password~,
      raw_cell_value=options.raw_cell_value,
      tmp_dir=options.tmp_dir,
      short_date_pattern=options.short_date_pattern,
      long_date_pattern=options.long_date_pattern,
      long_time_pattern=options.long_time_pattern,
      culture_info=options.culture_info,
    )
  }
}