///|
/// Sort strategy used before packing starts.
pub(all) enum SortOrder {
  InputOrder
  AreaDesc
  HeightDesc
  WidthDesc
  PerimeterDesc
} derive(Eq, @debug.Debug)

///|
/// Packing algorithm.
pub(all) enum Algorithm {
  ShelfFirstFit
  GuillotineBestArea
  SkylineBottomLeft
} derive(Eq, @debug.Debug)

///|
/// Rectangle item requested by a caller.
pub(all) struct Rect {
  id : String
  width : Int
  height : Int
} derive(Eq, @debug.Debug)

///|
/// Two-dimensional bin where rectangles are placed.
pub(all) struct Bin {
  width : Int
  height : Int
} derive(Eq, @debug.Debug)

///|
/// Outer padding reserves a border around the bin. Inner padding is inserted
/// between placements by the built-in algorithms.
pub(all) struct Padding {
  outer : Int
  inner : Int
} derive(Eq, @debug.Debug)

///|
/// Complete pack configuration.
pub(all) struct PackOptions {
  bin : Bin
  padding : Padding
  sort : SortOrder
  algorithm : Algorithm
  allow_rotate : Bool
} derive(Eq, @debug.Debug)

///|
/// One successful placement.
pub(all) struct Placed {
  id : String
  x : Int
  y : Int
  width : Int
  height : Int
  rotated : Bool
  original_width : Int
  original_height : Int
} derive(Eq, @debug.Debug)

///|
/// Resulting layout and the items that did not fit.
pub(all) struct Layout {
  bin : Bin
  placements : Array[Placed]
  unpacked : Array[Rect]
  algorithm : Algorithm
  occupancy_permille : Int
  waste_area : Int
} derive(Eq, @debug.Debug)

///|
/// One packed page in a multipage result.
pub(all) struct Page {
  index : Int
  layout : Layout
} derive(Eq, @debug.Debug)

///|
/// Multipage result for inputs that do not fit in a single bin.
pub(all) struct MultiLayout {
  pages : Array[Page]
  unpacked : Array[Rect]
  total_occupancy_permille : Int
} derive(Eq, @debug.Debug)

///|
/// Parsed text specification.
pub(all) struct Spec {
  options : PackOptions
  rects : Array[Rect]
} derive(Eq, @debug.Debug)

///|
/// Aggregate information about a source rectangle collection.
pub(all) struct RectSetStats {
  count : Int
  total_area : Int
  min_width : Int
  max_width : Int
  min_height : Int
  max_height : Int
  min_area : Int
  max_area : Int
  average_width_permille : Int
  average_height_permille : Int
  average_area_permille : Int
  square_count : Int
  wide_count : Int
  tall_count : Int
  oversized_count : Int
} derive(Eq, @debug.Debug)

///|
/// Derived metrics for a completed layout.
pub(all) struct LayoutStats {
  placed_count : Int
  unpacked_count : Int
  rotated_count : Int
  used_area : Int
  container_area : Int
  padded_capacity : Int
  occupancy_permille : Int
  padded_occupancy_permille : Int
  waste_area : Int
  bounding_x : Int
  bounding_y : Int
  bounding_width : Int
  bounding_height : Int
  bounding_area : Int
  bounding_occupancy_permille : Int
  row_count : Int
  column_count : Int
  horizontal_span : Int
  vertical_span : Int
  fragmentation_score : Int
  compactness_score : Int
} derive(Eq, @debug.Debug)

///|
/// One candidate produced by automatic option exploration.
pub(all) struct PackCandidate {
  options : PackOptions
  layout : Layout
  score : Int
  rank : Int
} derive(Eq, @debug.Debug)

///|
/// Result of comparing several option combinations for the same inputs.
pub(all) struct PackComparison {
  base_options : PackOptions
  candidates : Array[PackCandidate]
  best : PackCandidate
} derive(Eq, @debug.Debug)

///|
/// A coarse grid cell used by density and heatmap reports.
pub(all) struct DensityCell {
  col : Int
  row : Int
  x : Int
  y : Int
  width : Int
  height : Int
  used_area : Int
  capacity : Int
  occupancy_permille : Int
} derive(Eq, @debug.Debug)

///|
/// Coarse grid summary of a layout.
pub(all) struct GridReport {
  cell_width : Int
  cell_height : Int
  cols : Int
  rows : Int
  cells : Array[DensityCell]
  occupied_cells : Int
  empty_cells : Int
  densest_cell : DensityCell
  sparsest_cell : DensityCell
} derive(Eq, @debug.Debug)

///|
/// Severity used by diagnostic reports.
pub(all) enum IssueSeverity {
  Info
  Warning
  Critical
} derive(Eq, @debug.Debug)

///|
/// Machine-readable category for layout diagnostics.
pub(all) enum IssueKind {
  LowOccupancy
  HasUnpacked
  WideBoundingBox
  TallBoundingBox
  SparseGrid
  HeavyRotation
  FragmentedRows
  ConstraintFailed
  SourceRisk
} derive(Eq, @debug.Debug)

///|
/// One diagnostic finding.
pub(all) struct LayoutIssue {
  severity : IssueSeverity
  kind : IssueKind
  id : String
  message : String
  value : Int
} derive(Eq, @debug.Debug)

///|
/// Optional policy checks for callers that need production constraints.
pub(all) struct LayoutConstraints {
  min_occupancy_permille : Int
  max_waste_permille : Int
  min_gap : Int
  max_row_count : Int
  max_column_count : Int
  max_rotated_permille : Int
  require_all_packed : Bool
} derive(Eq, @debug.Debug)

///|
/// One failed production constraint.
pub(all) struct ConstraintViolation {
  name : String
  expected : Int
  actual : Int
  severity : IssueSeverity
} derive(Eq, @debug.Debug)

///|
/// Built-in fixture families for examples, smoke tests, and benchmarks.
pub(all) enum FixtureKind {
  SpriteSheet
  UiPanels
  DocumentBlocks
  MixedIcons
  StressSlivers
} derive(Eq, @debug.Debug)

///|
/// Change type for comparing two layouts.
pub(all) enum PlacementChangeKind {
  Added
  Removed
  Moved
  Resized
  RotationChanged
  Unchanged
} derive(Eq, @debug.Debug)

///|
/// One placement-level difference between two layouts.
pub(all) struct PlacementChange {
  id : String
  kind : PlacementChangeKind
  old_x : Int
  old_y : Int
  old_width : Int
  old_height : Int
  new_x : Int
  new_y : Int
  new_width : Int
  new_height : Int
  area_delta : Int
} derive(Eq, @debug.Debug)

///|
/// Summary of differences between two layouts.
pub(all) struct LayoutDiff {
  left_algorithm : Algorithm
  right_algorithm : Algorithm
  left_occupancy_permille : Int
  right_occupancy_permille : Int
  occupancy_delta : Int
  added_count : Int
  removed_count : Int
  moved_count : Int
  resized_count : Int
  rotation_changed_count : Int
  unchanged_count : Int
  changes : Array[PlacementChange]
} derive(Eq, @debug.Debug)

///|
/// Errors are explicit so callers can surface actionable diagnostics.
pub(all) enum PackError {
  InvalidBin(width~ : Int, height~ : Int)
  InvalidPadding(outer~ : Int, inner~ : Int)
  InvalidRect(id~ : String, width~ : Int, height~ : Int)
  InvalidPageLimit(limit~ : Int)
  InvalidCellSize(width~ : Int, height~ : Int)
  DuplicateId(id~ : String)
  OutOfBounds(id~ : String)
  Overlap(left~ : String, right~ : String)
  ParseError(line~ : Int, message~ : String)
} derive(Eq, @debug.Debug)

///|
/// Convenience rectangle constructor.
pub fn Rect::make(id : StringView, width : Int, height : Int) -> Rect {
  Rect::{ id: id.to_owned(), width, height }
}

///|
/// Convenience bin constructor.
pub fn Bin::make(width : Int, height : Int) -> Bin {
  Bin::{ width, height }
}

///|
/// Convenience padding constructor.
pub fn Padding::make(outer : Int, inner : Int) -> Padding {
  Padding::{ outer, inner }
}

///|
/// Recommended default options for deterministic asset-style packing.
pub fn PackOptions::default(width : Int, height : Int) -> PackOptions {
  PackOptions::{
    bin: Bin::{ width, height },
    padding: Padding::{ outer: 0, inner: 0 },
    sort: AreaDesc,
    algorithm: ShelfFirstFit,
    allow_rotate: false,
  }
}

///|
/// Pack rectangles using the algorithm selected by `options`.
pub fn pack(
  rects : ArrayView[Rect],
  options : PackOptions,
) -> Result[Layout, PackError] {
  match validate_input(rects, options) {
    Err(err) => Err(err)
    Ok(_) =>
      match options.algorithm {
        ShelfFirstFit => Ok(pack_shelf(rects, options))
        GuillotineBestArea => Ok(pack_guillotine(rects, options))
        SkylineBottomLeft => Ok(pack_skyline(rects, options))
      }
  }
}

///|
/// Pack a textual spec in one call.
pub fn pack_spec(source : String) -> Result[Layout, PackError] {
  match parse_spec(source) {
    Err(err) => Err(err)
    Ok(spec) => pack(spec.rects, spec.options)
  }
}

///|
/// Built-in sample used by the CLI smoke test and README.
pub fn sample_spec() -> String {
  "bin 64 32\npadding 1 1\nalgorithm guillotine\nsort area\nrotate true\nrect hero 18 12\nrect logo 13 7\nrect icon 8 8\nrect badge 9 5\nrect chip 5 4\n"
}

///|
/// Built-in sample layout. The return type stays explicit so CI catches
/// regressions in the example path.
pub fn sample_layout() -> Result[Layout, PackError] {
  pack_spec(sample_spec())
}

///|
/// Stable display text for errors.
pub fn error_message(err : PackError) -> String {
  match err {
    InvalidBin(width~, height~) => "invalid bin size \{width}x\{height}"
    InvalidPadding(outer~, inner~) =>
      "invalid padding outer=\{outer} inner=\{inner}"
    InvalidRect(id~, width~, height~) =>
      "invalid rectangle '\{id}' size \{width}x\{height}"
    InvalidPageLimit(limit~) => "invalid page limit \{limit}"
    InvalidCellSize(width~, height~) =>
      "invalid preview cell size \{width}x\{height}"
    DuplicateId(id~) => "duplicate id '\{id}'"
    OutOfBounds(id~) => "placement '\{id}' is outside the bin"
    Overlap(left~, right~) => "placements '\{left}' and '\{right}' overlap"
    ParseError(line~, message~) => "line \{line}: \{message}"
  }
}