///|
/// Collision operators supported by the reusable solver.
pub(all) enum CollisionModel {
  Bgk(Double)
  Regularized(Double)
  Mrt(Double)
} derive(Debug)

///|
/// Construct a BGK collision model.
pub fn CollisionModel::bgk(omega~ : Double) -> CollisionModel {
  Bgk(omega)
}

///|
/// Construct a regularized collision model.
pub fn CollisionModel::regularized(omega~ : Double) -> CollisionModel {
  Regularized(omega)
}

///|
/// Construct a two-relaxation-time MRT model.
pub fn CollisionModel::mrt(omega~ : Double) -> CollisionModel {
  Mrt(omega)
}

///|
/// Read the shear relaxation parameter from a collision model.
pub fn CollisionModel::omega(self : CollisionModel) -> Double {
  match self {
    Bgk(omega) => omega
    Regularized(omega) => omega
    Mrt(omega) => omega
  }
}

///|
/// Outer-boundary behavior for the advanced solver.
pub(all) enum BoundaryMode {
  BounceBack
  Periodic
  Open
} derive(Debug, Eq)

///|
/// Configuration shared by collision, streaming, and stability checks.
pub(all) struct SimulationOptions {
  model : CollisionModel
  boundary : BoundaryMode
  force_x : Double
  force_y : Double
  density_floor : Double
  max_mach : Double
} derive(Debug)

///|
/// Build conservative solver options.
pub fn SimulationOptions::new(
  model~ : CollisionModel,
  boundary? : BoundaryMode = BounceBack,
  force_x? : Double = 0.0,
  force_y? : Double = 0.0,
  density_floor? : Double = 0.0000000001,
  max_mach? : Double = 0.35,
) -> SimulationOptions {
  { model, boundary, force_x, force_y, density_floor, max_mach }
}

///|
/// Configuration errors are explicit so callers can reject unsafe runs.
pub(all) enum ValidationError {
  InvalidRelaxation(Double)
  InvalidDomain(Size)
  InvalidDensityFloor(Double)
  InvalidMachLimit(Double)
} derive(Debug)

///|
/// Validate solver options before allocating a simulation.
pub fn validate_options(
  options : SimulationOptions,
) -> Result[Unit, ValidationError] {
  if options.model.omega() <= 0.0 || options.model.omega() >= 2.0 {
    Err(InvalidRelaxation(options.model.omega()))
  } else if options.density_floor <= 0.0 {
    Err(InvalidDensityFloor(options.density_floor))
  } else if options.max_mach <= 0.0 {
    Err(InvalidMachLimit(options.max_mach))
  } else {
    Ok(())
  }
}

///|
/// Validate a rectangular domain size.
pub fn validate_size(size : Size) -> Result[Unit, ValidationError] {
  if size.width <= 0 || size.height <= 0 {
    Err(InvalidDomain(size))
  } else {
    Ok(())
  }
}