///|
pub(all) struct SingleStageScenario {
  name : String
  feed_flow : Double
  feed_concentration : Double
  solvent_flow : Double
  solvent_concentration : Double
  distribution : Double
} derive(Debug, Eq, ToJson)

///|
pub fn SingleStageScenario::new(
  name~ : String,
  feed_flow~ : Double,
  feed_concentration~ : Double,
  solvent_flow~ : Double,
  solvent_concentration? : Double = 0.0,
  distribution~ : Double,
) -> SingleStageScenario raise @domain.ExtractionError {
  if name.trim().length() == 0 {
    raise @domain.ExtractionError::InvalidArgument(
      message="scenario name cannot be blank",
    )
  }
  @domain.require_positive("feed flow", feed_flow)
  @domain.require_non_negative("feed concentration", feed_concentration)
  @domain.require_positive("solvent flow", solvent_flow)
  @domain.require_non_negative("solvent concentration", solvent_concentration)
  @domain.require_non_negative("distribution coefficient", distribution)
  {
    name,
    feed_flow,
    feed_concentration,
    solvent_flow,
    solvent_concentration,
    distribution,
  }
}

///|
pub(all) struct ScenarioResult {
  name : String
  input : SingleStageScenario
  result : @liquid.SingleStageResult
  extraction_factor : Double
} derive(Debug, Eq, ToJson)

///|
pub fn run_single_stage_scenario(
  scenario : SingleStageScenario,
) -> ScenarioResult raise @domain.ExtractionError {
  let result = @liquid.single_stage_constant_k(
    feed_flow=scenario.feed_flow,
    feed_concentration=scenario.feed_concentration,
    solvent_flow=scenario.solvent_flow,
    solvent_concentration=scenario.solvent_concentration,
    distribution=scenario.distribution,
  )
  {
    name: scenario.name,
    input: scenario,
    result,
    extraction_factor: @liquid.extraction_factor(
      distribution=scenario.distribution,
      solvent_flow=scenario.solvent_flow,
      carrier_flow=scenario.feed_flow,
    ),
  }
}

///|
pub fn run_single_stage_scenarios(
  scenarios : ArrayView[SingleStageScenario],
) -> Array[ScenarioResult] raise @domain.ExtractionError {
  if scenarios.length() == 0 {
    raise @domain.ExtractionError::InsufficientData(required=1, actual=0)
  }
  let seen : Map[String, Unit] = Map([])
  let results : Array[ScenarioResult] = []
  for scenario in scenarios {
    if seen.contains(scenario.name) {
      raise @domain.ExtractionError::InvalidArgument(
        message="duplicate scenario name '\{scenario.name}'",
      )
    }
    seen[scenario.name] = ()
    results.push(run_single_stage_scenario(scenario))
  }
  results
}

///|
pub fn sweep_distribution(
  feed_flow~ : Double,
  feed_concentration~ : Double,
  solvent_flow~ : Double,
  distributions : ArrayView[Double],
) -> Array[ScenarioResult] raise @domain.ExtractionError {
  if distributions.length() == 0 {
    raise @domain.ExtractionError::InsufficientData(required=1, actual=0)
  }
  run_single_stage_scenarios(
    [
      for index, distribution in distributions => {
        SingleStageScenario::new(
          name="K[\{index}]=\{distribution}",
          feed_flow~,
          feed_concentration~,
          solvent_flow~,
          distribution~,
        )
      }
    ],
  )
}

///|
pub fn sweep_solvent_flow(
  feed_flow~ : Double,
  feed_concentration~ : Double,
  solvent_flows : ArrayView[Double],
  distribution~ : Double,
) -> Array[ScenarioResult] raise @domain.ExtractionError {
  if solvent_flows.length() == 0 {
    raise @domain.ExtractionError::InsufficientData(required=1, actual=0)
  }
  run_single_stage_scenarios(
    [
      for index, solvent_flow in solvent_flows => {
        SingleStageScenario::new(
          name="V[\{index}]=\{solvent_flow}",
          feed_flow~,
          feed_concentration~,
          solvent_flow~,
          distribution~,
        )
      }
    ],
  )
}