///|
priv struct Config {
  mut frontend : String
  mut frontend_provider : String?
  mut frontend_profile : String?
  mut backend : String
  mut output : String
  mut package_name : String
  mut source : String
  mut inference : String
  transforms : Array[@scheme.Value]
}

///|
fn config_runtime(limits : @scheme.Limits) -> @scheme.Runtime raise {
  let runtime = @pipeline.new_runtime(limits~)
  for
    name in [
      "frontend", "frontend-provider", "frontend-profile", "backend", "output", "package-name",
      "source", "inference",
    ] {
    runtime.define_native(name, fn(_rt, args) raise {
      @scheme.arity(args, 1, name)
      @scheme.Record(Map::from_array([(name, @scheme.Text(args[0].as_text()))]))
    })
  }
  runtime.define_native("transform", fn(_rt, args) raise {
    @scheme.arity(args, 1, "transform")
    if !(args[0] is @scheme.Value::Closure(_) ||
      args[0] is @scheme.Value::Native(_)) {
      raise @scheme.Invalid("transform expects a procedure")
    }
    @scheme.Record(Map::from_array([("transform", args[0])]))
  })
  runtime.define_native("pipeline", fn(_rt, args) { @scheme.list(args) })
  runtime
}

///|
/// `origin` is the file of `script`, which an invalid `inference` value names.
fn Config::apply(
  self : Config,
  runtime : @scheme.Runtime,
  script : String,
  origin~ : String = "",
) -> Unit raise {
  let entries = runtime.eval(script).to_array()
  for entry in entries {
    let fields = match entry {
      Record(fields) => fields
      _ => raise @scheme.Invalid("pipeline expects option records")
    }
    for name, value in fields {
      match name {
        "frontend" => self.frontend = value.as_text()
        "frontend-provider" =>
          self.frontend_provider = Some(
            source_constraint(value.as_text(), "provider"),
          )
        "frontend-profile" =>
          self.frontend_profile = Some(
            source_constraint(value.as_text(), "profile"),
          )
        "backend" => self.backend = value.as_text()
        "output" => {
          let output = normalize(value.as_text())
          if output == "" {
            raise @scheme.Invalid("Output directory must be nonempty")
          }
          self.output = output
        }
        "package-name" => self.package_name = value.as_text()
        "source" => self.source = normalize(value.as_text())
        "transform" => self.transforms.push(value)
        "inference" => {
          let text = value.as_text()
          let _ = inference_mode(text) catch {
            @configuration.Invalid(message) =>
              raise @configuration.Invalid(
                if origin == "" {
                  message
                } else {
                  message + " (set in " + origin + ")"
                },
              )
            e => raise e
          }
          self.inference = text
        }
        _ => raise @scheme.Invalid("Unknown pipeline option: " + name)
      }
    }
  }
}