///| Cells follow the margin array order. Active weights have strictly positive

///|
/// lower bounds; fixed structural-zero rows use base=lower=upper=0.
pub(all) struct Row {
  id : String
  cells : Array[String]
  base_weight : Double
  lower : Double
  upper : Double
} derive(Eq, @debug.Debug, ToJson)

///|
pub(all) struct Target {
  category : String
  total : Double
} derive(Eq, @debug.Debug, ToJson)

///|
pub(all) struct Margin {
  name : String
  targets : Array[Target]
} derive(Eq, @debug.Debug, ToJson)

///|
pub(all) struct Problem {
  rows : Array[Row]
  margins : Array[Margin]
} derive(@debug.Debug)

///|
/// Residuals use |actual-target|/max(1,target). Tolerance is 1e-10..1e-3.
pub(all) struct Options {
  max_iterations : Int
  tolerance : Double
} derive(Eq, @debug.Debug, ToJson)

///|
pub(all) enum Status {
  Converged
  IterationLimit
  BaselineRejected
} derive(Eq, @debug.Debug, ToJson)

///|
pub(all) struct Residual {
  margin : String
  category : String
  target : Double
  actual : Double
  scaled_error : Double
} derive(Eq, @debug.Debug, ToJson)

///|
pub(all) struct Diagnostics {
  residuals : Array[Residual]
  maximum_error : Double
  bounds_violations : Int
  total_weight : Double
  effective_sample_size : Double
  passes : Bool
} derive(Eq, @debug.Debug, ToJson)

///|
pub(all) struct WeightedRow {
  id : String
  weight : Double
} derive(Eq, @debug.Debug, ToJson)

///|
/// Trace errors are recomputed after complete sweeps, not during projections.
pub(all) struct Fit {
  status : Status
  iterations : Int
  weights : Array[WeightedRow]
  trace : Array[Double]
  diagnostics : Diagnostics
} derive(Eq, @debug.Debug, ToJson)

///|
pub(all) enum RakeError {
  SizeLimit
  InvalidOptions
  InvalidRow(String)
  DuplicateRow(String)
  InvalidMargin(String)
  UnknownCategory(String, String)
  InconsistentTotals
  InfeasibleMargin(String, String)
  InvalidWeights
  NotOneWay
  NumericFailure
} derive(Eq, @debug.Debug)

///|
priv struct Group {
  margin : String
  category : String
  target : Double
  members : Array[Int]
}

///|
priv struct Compiled {
  rows : Array[Row]
  groups : Array[Group]
}

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

///|
pub extend Row with @debug.Debug::{to_repr}

///|
pub extend Row with ToJson::{to_json}

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

///|
pub extend Target with @debug.Debug::{to_repr}

///|
pub extend Target with ToJson::{to_json}

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

///|
pub extend Margin with @debug.Debug::{to_repr}

///|
pub extend Margin with ToJson::{to_json}

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

///|
pub extend Options with @debug.Debug::{to_repr}

///|
pub extend Options with ToJson::{to_json}

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

///|
pub extend Status with @debug.Debug::{to_repr}

///|
pub extend Status with ToJson::{to_json}

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

///|
pub extend Residual with @debug.Debug::{to_repr}

///|
pub extend Residual with ToJson::{to_json}

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

///|
pub extend Diagnostics with @debug.Debug::{to_repr}

///|
pub extend Diagnostics with ToJson::{to_json}

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

///|
pub extend WeightedRow with @debug.Debug::{to_repr}

///|
pub extend WeightedRow with ToJson::{to_json}

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

///|
pub extend Fit with @debug.Debug::{to_repr}

///|
pub extend Fit with ToJson::{to_json}

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

///|
pub extend RakeError with @debug.Debug::{to_repr}

///|
pub extend Problem with @debug.Debug::{to_repr}