///|
/// A named configuration layer, usually base/dev/prod/local/secret.
pub struct ConfigLayer {
  name : String
  priority : Int
  document : ConfigDocument
} derive(@debug.Debug, Eq)

///|
/// One effective value after layered merging.
pub struct ResolvedValue {
  section : String
  key : String
  value : String
  source_layer : String
  source : String
  span : Span
  overwritten_by : Array[String]
} derive(@debug.Debug, Eq)

///|
/// A merged configuration view with traceable values.
pub struct ConfigView {
  values : Array[ResolvedValue]
  diagnostics : Array[Diagnostic]
} derive(@debug.Debug, Eq)

///|
/// Creates a layer with an explicit priority. Higher priority wins.
pub fn layer(
  name : String,
  priority : Int,
  document : ConfigDocument,
) -> ConfigLayer {
  { name, priority, document }
}

///|
/// Merges layers in priority order and keeps the final source for every key.
pub fn merge_layers(layers : Array[ConfigLayer]) -> ConfigView {
  let values : Array[ResolvedValue] = []
  let diagnostics : Array[Diagnostic] = []
  let ordered = sort_layers(layers)
  for layer_item in ordered {
    append_diagnostics(diagnostics, layer_item.document.diagnostics)
    for entry in layer_item.document.entries {
      let path = entry.path()
      let existing_index = find_resolved_index(values, path)
      match existing_index {
        None =>
          values.push({
            section: entry.section,
            key: entry.key,
            value: entry.value,
            source_layer: layer_item.name,
            source: entry.source,
            span: entry.span,
            overwritten_by: [],
          })
        Some(index) => {
          let previous = values[index]
          let overwritten_by = previous.overwritten_by.copy()
          overwritten_by.push(layer_item.name)
          values[index] = {
            section: entry.section,
            key: entry.key,
            value: entry.value,
            source_layer: layer_item.name,
            source: entry.source,
            span: entry.span,
            overwritten_by,
          }
          diagnostics.push(
            info(
              "overlay",
              "key '" +
              path +
              "' from layer '" +
              layer_item.name +
              "' overrides layer '" +
              previous.source_layer +
              "'",
              span=Some(entry.span),
            ),
          )
        }
      }
    }
  }
  { values, diagnostics }
}

///|
fn append_diagnostics(
  target : Array[Diagnostic],
  source : Array[Diagnostic],
) -> Unit {
  for item in source {
    target.push(item)
  }
}

///|
fn sort_layers(layers : Array[ConfigLayer]) -> Array[ConfigLayer] {
  let result = layers.copy()
  let mut i = 1
  while i < result.length() {
    let current = result[i]
    let mut j = i - 1
    while j >= 0 && result[j].priority > current.priority {
      result[j + 1] = result[j]
      j = j - 1
    }
    result[j + 1] = current
    i = i + 1
  }
  result
}

///|
fn find_resolved_index(values : Array[ResolvedValue], path : String) -> Int? {
  let mut index = 0
  while index < values.length() {
    if entry_path(values[index].section, values[index].key) == path {
      return Some(index)
    }
    index = index + 1
  }
  None
}

///|
/// Finds a raw resolved value by section and key.
pub fn ConfigView::get(
  self : ConfigView,
  section : String,
  key : String,
) -> String? {
  let path = entry_path(section, key)
  for item in self.values {
    if entry_path(item.section, item.key) == path {
      return Some(item.value)
    }
  }
  None
}

///|
/// Finds a raw resolved value by dotted path.
pub fn ConfigView::get_path(self : ConfigView, path : String) -> String? {
  for item in self.values {
    if entry_path(item.section, item.key) == path {
      return Some(item.value)
    }
  }
  None
}

///|
/// Reads a boolean value using common configuration spellings.
pub fn ConfigView::get_bool(self : ConfigView, path : String) -> Bool? {
  match self.get_path(path) {
    None => None
    Some(value) => parse_bool_value(value)
  }
}

///|
/// Reads an integer value.
pub fn ConfigView::get_int(self : ConfigView, path : String) -> Int? {
  match self.get_path(path) {
    None => None
    Some(value) => parse_int_value(value)
  }
}

///|
/// Reads a comma-separated list.
pub fn ConfigView::get_list(self : ConfigView, path : String) -> Array[String]? {
  match self.get_path(path) {
    None => None
    Some(value) => {
      let items : Array[String] = []
      for part in value.split(",") {
        let item = part.to_owned().trim().to_owned()
        if item != "" {
          items.push(item)
        }
      }
      Some(items)
    }
  }
}

///|
fn parse_bool_value(value : String) -> Bool? {
  let normalized = ascii_lower_value(value.trim().to_owned())
  if normalized == "true" ||
    normalized == "yes" ||
    normalized == "on" ||
    normalized == "1" {
    Some(true)
  } else if normalized == "false" ||
    normalized == "no" ||
    normalized == "off" ||
    normalized == "0" {
    Some(false)
  } else {
    None
  }
}

///|
fn ascii_lower_value(text : String) -> String {
  let builder = StringBuilder()
  for ch in text {
    if ch >= 'A' && ch <= 'Z' {
      builder.write_char((ch.to_int() + 32).unsafe_to_char())
    } else {
      builder.write_char(ch)
    }
  }
  builder.to_string()
}

///|
fn parse_int_value(value : String) -> Int? {
  let trimmed = value.trim().to_owned()
  if trimmed == "" {
    None
  } else {
    parse_decimal_int(trimmed)
  }
}

///|
fn parse_decimal_int(text : String) -> Int? {
  let mut index = 0
  let mut sign = 1
  if text.has_prefix("-") {
    sign = -1
    index = 1
  } else if text.has_prefix("+") {
    index = 1
  }
  if index >= text.length() {
    return None
  }
  let mut value = 0
  while index < text.length() {
    let ch = text[index]
    if ch < '0' || ch > '9' {
      return None
    }
    value = value * 10 + (ch.to_int() - '0'.to_int())
    index = index + 1
  }
  Some(value * sign)
}