///|
pub(all) enum CommandResult {
  Ok(String)
  Err(String)
} derive(Eq, Debug)

///|
pub impl Show for CommandResult with fn output(self, logger) {
  match self {
    Ok(value) => {
      logger.write_string("Ok(\"")
      logger.write_string(value)
      logger.write_string("\")")
    }
    Err(message) => {
      logger.write_string("Err(\"")
      logger.write_string(message)
      logger.write_string("\")")
    }
  }
}

///|
pub struct InputBinding {
  name : String
  value : String
} derive(Eq, Debug)

///|
pub fn InputBinding::new(name : String, value : String) -> InputBinding {
  { name, value }
}

///|
pub fn InputBinding::name(self : InputBinding) -> String {
  self.name
}

///|
pub fn InputBinding::value(self : InputBinding) -> String {
  self.value
}

///|
priv enum EnvBuildResult {
  BuiltEnv(@formula_common.FormulaEnv)
  EnvError(String)
}

///|
pub fn parse_format_style(style : String) -> @unit.FormatStyle? {
  match style {
    "ascii" => Some(Ascii)
    "si" => Some(Si)
    "latex" => Some(Latex)
    _ => None
  }
}

///|
pub fn run_list() -> CommandResult {
  let entries = @formula_catalog.all().entries()
  let mut output = ""
  for entry in entries {
    output = output + "\{entry.name()}\t\{entry.domain()}\t\{entry.display()}\n"
  }
  Ok(output)
}

///|
fn format_inputs(inputs : Array[@formula_common.FormulaInput]) -> String {
  let mut output = ""
  for input in inputs {
    output = output +
      "  --\{input.name()}   dimension: \{input.dimension()}\n"
  }
  output
}

///|
pub fn run_show(name : String) -> CommandResult {
  match @formula_catalog.all().lookup(name) {
    None => Err("unknown formula `\{name}`")
    Some(entry) =>
      Ok(
        "name: \{entry.name()}\n" +
        "domain: \{entry.domain()}\n" +
        "formula: \{entry.display()}\n" +
        "description: \{entry.description()}\n" +
        "output dimension: \{entry.output_dimension()}\n" +
        "inputs:\n" +
        format_inputs(entry.inputs()),
      )
  }
}

///|
fn lookup_binding(bindings : Array[InputBinding], name : String) -> String? {
  for binding in bindings {
    if binding.name() == name {
      return Some(binding.value())
    }
  }
  None
}

///|
fn build_env(
  inputs : Array[@formula_common.FormulaInput],
  bindings : Array[InputBinding],
) -> EnvBuildResult {
  let mut env = @formula_common.FormulaEnv::new()
  let unit_catalog = @preset.all()
    .with_unit("ohm", @electromagnetism.ohm)
    .with_unit("Ohm", @electromagnetism.ohm)
  for input in inputs {
    match lookup_binding(bindings, input.name()) {
      None => return EnvError("missing input `\{input.name()}`")
      Some(text) => {
        let value = @parser.parse_quantity(unit_catalog, text) catch {
          err =>
            return EnvError("invalid quantity for `\{input.name()}`: \{err}")
        }
        if !value.dimension().is_same(input.dimension()) {
          return EnvError(
            "input `\{input.name()}` expects dimension \{input.dimension()}, got \{value.dimension()}",
          )
        }
        env = env.with_input(input.name(), value)
      }
    }
  }
  BuiltEnv(env)
}

///|
pub fn run_calc(
  name : String,
  bindings : Array[InputBinding],
  format? : String = "ascii",
) -> CommandResult {
  let style = match parse_format_style(format) {
    Some(style) => style
    None =>
      return Err(
        "unknown output format `\{format}`; expected ascii, si, or latex",
      )
  }
  let entry = match @formula_catalog.all().lookup(name) {
    Some(entry) => entry
    None => return Err("unknown formula `\{name}`")
  }
  let env = match build_env(entry.inputs(), bindings) {
    BuiltEnv(env) => env
    EnvError(message) => return Err(message)
  }
  let result = entry.eval(env) catch {
    err => return Err("failed to evaluate `\{name}`: \{err}")
  }
  Ok(@quantity.format_quantity_with(result, style))
}