///|
/// The JSON-compatible kinds represented by a configuration value.
pub(all) enum ConfigValueKind {
  Null
  Boolean
  Number
  String
  Array
  Object
} derive(Eq, Debug)

///|
/// A path at which recursive merge encountered incompatible structures.
pub struct ConfigConflict {
  path : ConfigPath
  earlier_kind : ConfigValueKind
  later_kind : ConfigValueKind
} derive(Eq, Debug)

///|
/// The merged value and structural conflicts found while producing it.
pub struct MergeResult {
  value : ConfigValue
  conflicts : Array[ConfigConflict]
} derive(Eq, Debug)

///|
/// Return the path where this conflict occurred.
pub fn ConfigConflict::path(self : ConfigConflict) -> ConfigPath {
  self.path
}

///|
/// Return the kind supplied by the earlier configuration.
pub fn ConfigConflict::earlier_kind(self : ConfigConflict) -> ConfigValueKind {
  self.earlier_kind
}

///|
/// Return the kind supplied by the later configuration.
pub fn ConfigConflict::later_kind(self : ConfigConflict) -> ConfigValueKind {
  self.later_kind
}

///|
/// Return the value produced by the reported merge.
pub fn MergeResult::value(self : MergeResult) -> ConfigValue {
  self.value
}

///|
/// Return the number of structural conflicts found by the merge.
pub fn MergeResult::conflict_count(self : MergeResult) -> Int {
  self.conflicts.length()
}

///|
/// Return one structural conflict, or `None` when the index is out of bounds.
pub fn MergeResult::conflict(
  self : MergeResult,
  index : Int,
) -> ConfigConflict? {
  self.conflicts.get(index)
}

///|
/// A JSON-compatible value used by ConfigScope's merge and analysis engine.
///
/// The underlying representation is intentionally private. This keeps later
/// merge and provenance work independent from the concrete JSON container.
pub struct ConfigValue {
  raw : Json
} derive(Eq, Debug)

///|
/// Construct a null configuration value.
pub fn ConfigValue::null() -> ConfigValue {
  { raw: Json::null() }
}

///|
/// Construct a boolean configuration value.
pub fn ConfigValue::boolean(value : Bool) -> ConfigValue {
  { raw: Json::boolean(value) }
}

///|
/// Construct a numeric configuration value.
pub fn ConfigValue::number(value : Double) -> ConfigValue {
  { raw: Json::number(value) }
}

///|
/// Construct a string configuration value.
pub fn ConfigValue::string(value : String) -> ConfigValue {
  { raw: Json::string(value) }
}

///|
/// Render this configuration value as compact JSON text.
pub fn ConfigValue::to_json_string(self : ConfigValue) -> String {
  self.raw.stringify()
}

///|
/// Construct an array configuration value.
///
/// The input array is copied into the private representation.
pub fn ConfigValue::array(values : Array[ConfigValue]) -> ConfigValue {
  { raw: Json::array(values.map(value => value.raw)) }
}

///|
/// Construct an object configuration value.
///
/// The input map is copied into the private representation, so later changes
/// to the caller's map do not change the constructed value.
pub fn ConfigValue::object(fields : Map[String, ConfigValue]) -> ConfigValue {
  let raw_fields : Map[String, Json] = Map([])
  fields.each((key, value) => raw_fields[key] = value.raw)
  { raw: Json::object(raw_fields) }
}

///|
/// Construct an empty object configuration value.
pub fn ConfigValue::empty_object() -> ConfigValue {
  { raw: Json::empty_object() }
}

///|
/// Return the kind of this value.
pub fn ConfigValue::kind(self : ConfigValue) -> ConfigValueKind {
  match self.raw {
    Null => Null
    True | False => Boolean
    Number(_) => Number
    String(_) => String
    Array(_) => Array
    Object(_) => Object
  }
}

///|
/// Return the boolean payload, or `None` for a different value kind.
pub fn ConfigValue::as_boolean(self : ConfigValue) -> Bool? {
  match self.raw {
    True => Some(true)
    False => Some(false)
    _ => None
  }
}

///|
/// Return the numeric payload, or `None` for a different value kind.
pub fn ConfigValue::as_number(self : ConfigValue) -> Double? {
  match self.raw {
    Number(value, ..) => Some(value)
    _ => None
  }
}

///|
/// Return the string payload, or `None` for a different value kind.
pub fn ConfigValue::as_string(self : ConfigValue) -> String? {
  match self.raw {
    String(value) => Some(value)
    _ => None
  }
}

///|
/// Return the number of elements for an array or fields for an object.
/// Scalar values return `None`.
pub fn ConfigValue::length(self : ConfigValue) -> Int? {
  match self.raw {
    Array(values) => Some(values.length())
    Object(fields) => Some(fields.length())
    _ => None
  }
}

///|
/// Read one array element, or `None` if this is not an array or the index is
/// outside its bounds.
pub fn ConfigValue::element(self : ConfigValue, index : Int) -> ConfigValue? {
  match self.raw {
    Array(values) => values.get(index).map(raw => { raw, })
    _ => None
  }
}

///|
/// Read one object field, or `None` if this is not an object or the key is
/// absent.
pub fn ConfigValue::field(self : ConfigValue, key : String) -> ConfigValue? {
  match self.raw {
    Object(fields) => fields.get(key).map(raw => { raw, })
    _ => None
  }
}

///|
/// Return whether an object contains a field with the given key.
pub fn ConfigValue::contains_field(self : ConfigValue, key : String) -> Bool {
  match self.raw {
    Object(fields) => fields.contains(key)
    _ => false
  }
}

///|
/// Classify a JSON value as a configuration value kind.
fn ConfigValueKind::from_json(value : Json) -> ConfigValueKind {
  match value {
    Null => Null
    True | False => Boolean
    Number(_) => Number
    String(_) => String
    Array(_) => Array
    Object(_) => Object
  }
}

///|
/// Return the stable lowercase name used in diagnostics and reports.
pub fn ConfigValueKind::to_string(self : ConfigValueKind) -> String {
  match self {
    Null => "null"
    Boolean => "boolean"
    Number => "number"
    String => "string"
    Array => "array"
    Object => "object"
  }
}

///|
/// Read a nested value using a dot-separated configuration path.
///
/// The lookup succeeds only when every path segment selects an object field.
/// Arrays are intentionally not indexed by this first path API.
pub fn ConfigValue::get(self : ConfigValue, path : ConfigPath) -> ConfigValue? {
  let mut current = self
  for segment in path.segments {
    match current.raw {
      Object(fields) =>
        match fields.get(segment) {
          Some(raw) => current = { raw, }
          None => return None
        }
      _ => return None
    }
  }
  Some(current)
}

///|
/// Merge an earlier configuration value with a later override.
///
/// Object fields are combined at the current level, and fields from the
/// override replace fields with the same key. Nested objects are replaced as
/// whole values; recursive merging will be added separately.
pub fn ConfigValue::merge(
  self : ConfigValue,
  later : ConfigValue,
) -> ConfigValue {
  match (self.raw, later.raw) {
    (Object(base_fields), Object(later_fields)) => {
      let merged_fields : Map[String, Json] = Map([])
      base_fields.each((key, value) => merged_fields[key] = value)
      later_fields.each((key, value) => merged_fields[key] = value)
      { raw: Json::object(merged_fields) }
    }
    (_, later_raw) => { raw: later_raw }
  }
}

///|
/// Recursively merge an earlier configuration value with a later override.
///
/// When both values at a field are objects, their fields are merged
/// recursively. Arrays and scalar values are replaced by the later value.
fn deep_merge_with_report_raw(
  base : Json,
  later : Json,
  segments : Array[String],
  conflicts : Array[ConfigConflict],
) -> Json {
  match (base, later) {
    (Object(base_fields), Object(later_fields)) => {
      let merged_fields : Map[String, Json] = Map([])
      base_fields.each((key, value) => merged_fields[key] = value)
      later_fields.each((key, value) => {
        let next_segments = segments.copy()
        next_segments.push(key)
        match base_fields.get(key) {
          Some(base_value) =>
            merged_fields[key] = deep_merge_with_report_raw(
              base_value, value, next_segments, conflicts,
            )
          None => merged_fields[key] = value
        }
      })
      Json::object(merged_fields)
    }
    (base_value, later_value) => {
      if (base_value is Object(_)) != (later_value is Object(_)) {
        conflicts.push({
          path: config_path_from_segments(segments),
          earlier_kind: ConfigValueKind::from_json(base_value),
          later_kind: ConfigValueKind::from_json(later_value),
        })
      }
      later_value
    }
  }
}

///|
fn deep_merge_raw(base : Json, later : Json) -> Json {
  match (base, later) {
    (Object(base_fields), Object(later_fields)) => {
      let merged_fields : Map[String, Json] = Map([])
      base_fields.each((key, value) => merged_fields[key] = value)
      later_fields.each((key, value) => {
        match base_fields.get(key) {
          Some(base_value) =>
            merged_fields[key] = deep_merge_raw(base_value, value)
          None => merged_fields[key] = value
        }
      })
      Json::object(merged_fields)
    }
    (_, later_value) => later_value
  }
}

///|
/// Describe a recursive merge together with structural type conflicts.
pub fn ConfigValue::deep_merge_with_report(
  self : ConfigValue,
  later : ConfigValue,
) -> MergeResult {
  let conflicts : Array[ConfigConflict] = []
  let merged = deep_merge_with_report_raw(self.raw, later.raw, [], conflicts)
  { value: { raw: merged }, conflicts }
}

///|
/// Recursively merge two configuration values, with the later value winning.
///
/// Nested objects preserve fields from both inputs. A later scalar, array, or
/// type change replaces the earlier value at that field.
pub fn ConfigValue::deep_merge(
  self : ConfigValue,
  later : ConfigValue,
) -> ConfigValue {
  { raw: deep_merge_raw(self.raw, later.raw) }
}

///|
/// Parse a JSON text into a configuration value.
///
/// The parser accepts every JSON value, including a root scalar or array.
pub fn ConfigValue::parse_json(
  source : String,
) -> ConfigValue raise @json.ParseError {
  { raw: @json.parse(source) }
}