///|
fn hex_text(value : Int64) -> String raise ConfigError {
  if value < 0L {
    raise Invalid("negative hex value")
  }
  if value == 0L {
    return "0x0"
  }
  let digits = "0123456789abcdef"
  let out : Array[String] = []
  let mut rest = value
  while rest != 0L {
    let index = (rest % 16L).to_int()
    out.push(digits[index:index + 1].to_owned())
    rest /= 16L
  }
  let ordered = []
  for i = out.length() - 1; i >= 0; i = i - 1 {
    ordered.push(out[i])
  }
  "0x" + ordered.join("")
}

///|
fn config_line(
  name : String,
  kind : Kind,
  value : Value,
) -> String raise ConfigError {
  match value {
    Unset => ""
    Logic(N) => "# CONFIG_" + name + " is not set\n"
    Logic(t) => "CONFIG_" + name + "=" + t.text() + "\n"
    Text(s) => "CONFIG_" + name + "=" + quote_string(s) + "\n"
    RawNumber(raw, _) => "CONFIG_" + name + "=" + raw + "\n"
    Number(n) =>
      "CONFIG_" +
      name +
      "=" +
      (if kind == Hex { hex_text(n) } else { n.to_string() }) +
      "\n"
  }
}

///|
fn Schema::check_snapshot(
  self : Schema,
  result : Resolution,
) -> Unit raise ConfigError {
  if self.defs.length() != result.values.length() {
    raise Invalid("snapshot/schema symbols differ")
  }
  for d in self.defs {
    let value = result.value(d.name)
    let kind = self.kind(d.name)
    if value == Unset && (kind == Integer || kind == Hex) {
      continue
    }
    if self.linux612 && value is RawNumber(_, k) && k == kind {
      continue
    }
    if !kind.accepts(value) {
      raise Invalid("snapshot/schema type differs: " + d.name)
    }
  }
}

///|
/// Deterministic complete value export in declaration order, with no host I/O.
pub fn Schema::export_config(
  self : Schema,
  result : Resolution,
) -> String raise ConfigError {
  self.check_snapshot(result)
  let lines = ["# Generated by MoonKconfig; do not edit derived values.\n"]
  for d in self.defs {
    lines.push(config_line(d.name, self.kind(d.name), result.value(d.name)))
  }
  lines.join("")
}

///|
/// C preprocessor header; module values use CONFIG_NAME_MODULE, not CONFIG_NAME.
pub fn Schema::export_header(
  self : Schema,
  result : Resolution,
) -> String raise ConfigError {
  self.check_snapshot(result)
  let lines = [
    "/* Generated by MoonKconfig. */\n#ifndef MOONKCONFIG_GENERATED_H\n#define MOONKCONFIG_GENERATED_H\n",
  ]
  for d in self.defs {
    let value = result.value(d.name)
    let prefix = "#define CONFIG_" + d.name
    let definition = match value {
      Unset | Logic(N) => ""
      Logic(Y) => prefix + " 1\n"
      Logic(M) => prefix + "_MODULE 1\n"
      Text(s) => prefix + " " + quote_string(s) + "\n"
      RawNumber(raw, _) => prefix + " " + raw + "\n"
      Number(n) =>
        prefix +
        " " +
        (if self.kind(d.name) == Hex { hex_text(n) } else { n.to_string() }) +
        "\n"
    }
    lines.push(definition)
  }
  lines.push("#endif\n")
  lines.join("")
}