///|
/// A named analytical element and its total dissolved concentration.
///
/// Concentrations are mol/L.  The solver never guesses a unit conversion;
/// callers must provide values in this unit.
pub(all) struct ElementDef {
  name : String
  total_mol_l : Double
} derive(Debug, ToJson, FromJson)

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

///|
pub extend ElementDef with @json.ToJson::{to_json}

///|
pub extend ElementDef with @json.FromJson::{from_json}

///|
/// A term in a mass-action or mineral dissolution expression.
///
/// `coefficient` is the signed stoichiometric exponent.  A negative value is
/// allowed for an expression written in the opposite direction.
pub(all) struct ComponentTerm {
  component : Int32
  coefficient : Double
} derive(Debug, ToJson, FromJson)

///|
/// A charged aqueous species.  Primary species point to one free component;
/// secondary species use `component_terms` and `log_k` to derive their
/// activity from the primary activities.
pub(all) struct SpeciesDef {
  name : String
  charge : Int32
  element_stoich : Array[Double]
  component_terms : Array[ComponentTerm]
  base_component : Int32?
  log_k : Double
  alkalinity : Double
  source : String
} derive(Debug, ToJson, FromJson)

///|
/// A mineral saturation expression.  The MVP reports saturation only; it does
/// not change the dissolved totals or perform precipitation phase switching.
pub(all) struct MineralDef {
  name : String
  component_terms : Array[ComponentTerm]
  log_ksp : Double
  source : String
} derive(Debug, ToJson, FromJson)

///|
/// An input water sample and its explicitly versioned species database.
pub(all) struct Problem {
  temperature_k : Double
  volume_l : Double
  elements : Array[ElementDef]
  species : Array[SpeciesDef]
  minerals : Array[MineralDef]
  hydrogen_component : Int32
  fixed_p_h : Double?
  alkalinity_mol_l : Double?
  activity_model : String
  database_version : String
  thermodynamics : ThermodynamicData
} derive(Debug, ToJson, FromJson)

///|
/// Numerical controls.  All tolerances are dimensionless residual tolerances
/// except `activity_tolerance`, which applies to log10 activity coefficients.
pub(all) struct SolveOptions {
  max_inner_iterations : Int32
  max_outer_iterations : Int32
  residual_tolerance : Double
  activity_tolerance : Double
  finite_difference_step : Double
  check_multiple_solutions : Bool
} derive(Debug, ToJson, FromJson)

///|
pub fn default_options() -> SolveOptions {
  {
    max_inner_iterations: 80,
    max_outer_iterations: 100,
    residual_tolerance: 1.0e-12,
    activity_tolerance: 1.0e-11,
    finite_difference_step: 1.0e-5,
    check_multiple_solutions: true,
  }
}

///|
/// Structured error states are kept separate so a caller can distinguish
/// invalid data, an inconsistent constraint set, and numerical stagnation.
pub(all) enum SolveError {
  InvalidInput(String)
  DatabaseInconsistent(String)
  InconsistentConstraints(String)
  DimensionMismatch(String)
  Stalled(String)
  MultipleSolutionsSuspected(String)
  ActivityModelOutOfRange(String)
  NegativeConcentration(String)
} derive(Debug)

///|
pub fn SolveError::message(self : SolveError) -> String {
  match self {
    InvalidInput(message) => message
    DatabaseInconsistent(message) => message
    InconsistentConstraints(message) => message
    DimensionMismatch(message) => message
    Stalled(message) => message
    MultipleSolutionsSuspected(message) => message
    ActivityModelOutOfRange(message) => message
    NegativeConcentration(message) => message
  }
}

///|
pub(all) struct SpeciesResult {
  name : String
  concentration_mol_l : Double
  activity : Double
  log_activity : Double
  activity_coefficient : Double
  charge : Int32
  primary : Bool
  source : String
} derive(Debug, ToJson, FromJson)

///|
pub(all) struct SaturationResult {
  mineral : String
  log_iap : Double
  log_ksp : Double
  saturation_index : Double
  state : String
  source : String
} derive(Debug, ToJson, FromJson)

///|
pub(all) struct SpeciationResult {
  status : String
  temperature_k : Double
  volume_l : Double
  activity_model : String
  p_h : Double
  ionic_strength : Double
  charge_residual_mol_l : Double
  alkalinity_mol_l : Double
  element_residuals_mol_l : Array[Double]
  species : Array[SpeciesResult]
  saturation : Array[SaturationResult]
  activity_coefficients : Array[Double]
  inner_iterations : Int32
  outer_iterations : Int32
  max_scaled_residual : Double
  diagnostics : Array[String]
  database_version : String
  solver_options : SolveOptions
  thermodynamics : ThermodynamicData
  iteration_trace : Array[IterationRecord]
  max_log_gamma_error : Double
} derive(Debug, ToJson, FromJson)