///|
/// An immutable list of sources and source providers in merge order, the
/// registered formats, and the explicit inputs of one run: the variable set,
/// the working directory, and the platform. Each function returns a new
/// builder.
pub struct ConfigBuilder {
  priv entries : Array[BuilderEntry]
  priv formats : Array[Format]
  priv env_vars : Map[String, String]?
  priv working_dir : String?
  priv platform : Platform?
  priv secret_syntax : SecretSyntax
}

///|
priv enum BuilderEntry {
  Source(ConfigSource)
  Provider(SourceProvider)
}

///|
/// A builder with no sources, no formats, and no run inputs.
pub fn builder() -> ConfigBuilder {
  {
    entries: [],
    formats: [],
    env_vars: None,
    working_dir: None,
    platform: None,
    secret_syntax: Standard,
  }
}

///|
/// Set whether one-field `env` / `file` / `command` / `keyring` structs are
/// secret references. The default is `SecretSyntax::standard()`.
pub fn secret_syntax(
  builder : ConfigBuilder,
  syntax : SecretSyntax,
) -> ConfigBuilder {
  { ..builder, secret_syntax: syntax }
}

///|
/// Add `source` after the sources that the builder holds.
pub fn add(builder : ConfigBuilder, source : ConfigSource) -> ConfigBuilder {
  { ..builder, entries: [..builder.entries, Source(source)] }
}

///|
/// Add a source provider. During the load, its sources take its place in
/// merge order.
pub fn provider(builder : ConfigBuilder, p : SourceProvider) -> ConfigBuilder {
  { ..builder, entries: [..builder.entries, Provider(p)] }
}

///|
/// Register `format` after the formats that the builder holds.
pub fn format(builder : ConfigBuilder, format : Format) -> ConfigBuilder {
  { ..builder, formats: [..builder.formats, format.copy()] }
}

///|
/// The registered formats in registration order.
pub fn ConfigBuilder::formats(self : ConfigBuilder) -> Array[Format] {
  self.formats.map(Format::copy)
}

///|
/// The variable set that the builder holds, or `None` when nobody gave one. A
/// host reads the process variables only when this is `None`.
pub fn ConfigBuilder::given_env_vars(
  self : ConfigBuilder,
) -> Map[String, String]? {
  self.env_vars.map(vars => vars.copy())
}

///|
/// The working directory that the builder holds, or `None` when nobody gave one.
pub fn ConfigBuilder::given_working_dir(self : ConfigBuilder) -> String? {
  self.working_dir
}

///|
/// The path texts of the file, candidate and parsed-file sources that the
/// builder holds, in add order, as the caller gave them. The load parses each
/// text one time; an adapter with stricter path rules, such as the browser
/// adapter, checks the texts here before the load. Providers are not included.
pub fn ConfigBuilder::given_file_paths(self : ConfigBuilder) -> Array[String] {
  let paths = []
  for entry in self.entries {
    guard entry is Source(source) else { continue }
    match source.input {
      Path(text) | Parsed(text, _) => paths.push(text)
      Candidates(texts, _) => paths.append(texts)
      _ => ()
    }
  }
  paths
}

///|
/// The platform that the builder holds, or `None` when nobody gave one.
pub fn ConfigBuilder::given_platform(self : ConfigBuilder) -> Platform? {
  self.platform
}

///|
/// Set the variable set of the run. Source providers read variables only from
/// this set.
pub fn env_vars(
  builder : ConfigBuilder,
  vars : Map[String, String],
) -> ConfigBuilder {
  { ..builder, env_vars: Some(vars.copy()) }
}

///|
/// Set the working directory of the run. A relative explicit file path joins
/// it.
pub fn working_dir(builder : ConfigBuilder, dir : String) -> ConfigBuilder {
  { ..builder, working_dir: Some(dir) }
}

///|
/// Set the platform of the run.
pub fn platform(builder : ConfigBuilder, p : Platform) -> ConfigBuilder {
  { ..builder, platform: Some(p) }
}

///|
/// Add a defaults source from a JSON value, for example
/// `Defaults::default().to_json()`.
pub fn defaults(
  builder : ConfigBuilder,
  json : Json,
) -> ConfigBuilder raise ConfigError {
  add(builder, ConfigSource::defaults(json))
}

///|
/// Add a file source at an explicit path, as `ConfigSource::file` gives it.
pub fn file(
  builder : ConfigBuilder,
  path : String,
  role? : FileRole = Explicit,
  name? : String,
  priority? : Int,
) -> ConfigBuilder {
  add(builder, ConfigSource::file(path, role~, name?, priority?))
}