///| 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) => Some(value.split(",").map(part => part.to_owned().trim().to_owned()).collect())
  }
}

fn parse_bool_value(value : String) -> Bool? {
  let normalized = value.trim().to_owned()
  if normalized == "true" || normalized == "TRUE" || normalized == "True" ||
     normalized == "yes" || normalized == "YES" || normalized == "Yes" ||
     normalized == "on" || normalized == "ON" || normalized == "On" ||
     normalized == "1" {
    Some(true)
  } else if normalized == "false" || normalized == "FALSE" || normalized == "False" ||
            normalized == "no" || normalized == "NO" || normalized == "No" ||
            normalized == "off" || normalized == "OFF" || normalized == "Off" ||
            normalized == "0" {
    Some(false)
  } else {
    None
  }
}

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)
}