///|
/// Counts keys by section in a merged view.
pub struct SectionStats {
  section : String
  key_count : Int
  sensitive_count : Int
} derive(@debug.Debug, Eq)

///|
/// Describes how one final value was produced.
pub struct ValueTrace {
  path : String
  final_layer : String
  final_source : String
  override_count : Int
  sensitive : Bool
} derive(@debug.Debug, Eq)

///|
/// Aggregate view statistics.
pub struct ViewStats {
  key_count : Int
  section_count : Int
  sensitive_count : Int
  overridden_count : Int
  diagnostics_count : Int
} derive(@debug.Debug, Eq)

///|
pub fn analyze_view(view : ConfigView) -> ViewStats {
  {
    key_count: view.values.length(),
    section_count: section_stats(view).length(),
    sensitive_count: sensitive_paths(view).length(),
    overridden_count: overridden_values(view).length(),
    diagnostics_count: view.diagnostics.length(),
  }
}

///|
pub fn section_stats(view : ConfigView) -> Array[SectionStats] {
  let sections = collect_analysis_sections(view)
  let stats : Array[SectionStats] = []
  for section_name in sections {
    let mut key_count = 0
    let mut sensitive_count = 0
    for item in view.values {
      if normalized_section(item.section) == section_name {
        key_count = key_count + 1
        if is_sensitive_path(entry_path(item.section, item.key)) {
          sensitive_count = sensitive_count + 1
        }
      }
    }
    stats.push({ section: section_name, key_count, sensitive_count })
  }
  stats
}

///|
fn collect_analysis_sections(view : ConfigView) -> Array[String] {
  let sections : Array[String] = []
  for item in view.values {
    let name = normalized_section(item.section)
    if !analysis_contains(sections, name) {
      sections.push(name)
    }
  }
  sections
}

///|
fn normalized_section(section : String) -> String {
  if section == "" {
    ""
  } else {
    section
  }
}

///|
fn analysis_contains(values : Array[String], target : String) -> Bool {
  for value in values {
    if value == target {
      return true
    }
  }
  false
}

///|
pub fn sensitive_paths(view : ConfigView) -> Array[String] {
  let paths : Array[String] = []
  for item in view.values {
    let path = entry_path(item.section, item.key)
    if is_sensitive_path(path) {
      paths.push(path)
    }
  }
  paths
}

///|
pub fn overridden_values(view : ConfigView) -> Array[ResolvedValue] {
  let values : Array[ResolvedValue] = []
  for item in view.values {
    if item.overwritten_by.length() > 0 {
      values.push(item)
    }
  }
  values
}

///|
pub fn trace_values(view : ConfigView) -> Array[ValueTrace] {
  let traces : Array[ValueTrace] = []
  for item in view.values {
    let path = entry_path(item.section, item.key)
    traces.push({
      path,
      final_layer: item.source_layer,
      final_source: item.source + ":" + item.span.line.to_string(),
      override_count: item.overwritten_by.length(),
      sensitive: is_sensitive_path(path),
    })
  }
  traces
}

///|
pub fn render_stats_markdown(view : ConfigView) -> String {
  let stats = analyze_view(view)
  let builder = StringBuilder()
  builder.write_string("# Configuration Statistics\n\n")
  builder.write_string("- Keys: " + stats.key_count.to_string() + "\n")
  builder.write_string("- Sections: " + stats.section_count.to_string() + "\n")
  builder.write_string(
    "- Sensitive keys: " + stats.sensitive_count.to_string() + "\n",
  )
  builder.write_string(
    "- Overridden keys: " + stats.overridden_count.to_string() + "\n",
  )
  builder.write_string(
    "- Diagnostics: " + stats.diagnostics_count.to_string() + "\n\n",
  )
  builder.write_string("## Sections\n\n")
  builder.write_string("| Section | Keys | Sensitive |\n")
  builder.write_string("| --- | --- | --- |\n")
  for item in section_stats(view) {
    builder.write_string(
      "| " +
      item.section +
      " | " +
      item.key_count.to_string() +
      " | " +
      item.sensitive_count.to_string() +
      " |\n",
    )
  }
  builder.to_string()
}

///|
pub fn render_trace_markdown(view : ConfigView) -> String {
  let builder = StringBuilder()
  builder.write_string(
    "| Path | Final Layer | Source | Overrides | Sensitive |\n",
  )
  builder.write_string("| --- | --- | --- | --- | --- |\n")
  for item in trace_values(view) {
    builder.write_string(
      "| " +
      item.path +
      " | " +
      item.final_layer +
      " | " +
      item.final_source +
      " | " +
      item.override_count.to_string() +
      " | " +
      yes_no(item.sensitive) +
      " |\n",
    )
  }
  builder.to_string()
}

///|
fn yes_no(value : Bool) -> String {
  if value {
    "yes"
  } else {
    "no"
  }
}

///|
/// Measures how many schema fields are filled by current values or defaults.
pub fn schema_coverage(schema : ConfigSchema, view : ConfigView) -> Int {
  let mut total = 0
  let mut covered = 0
  for schema_section in schema.sections {
    for schema_field in schema_section.fields {
      total = total + 1
      if view.get_path(schema_field.path) != None ||
        schema_field.default_value != None {
        covered = covered + 1
      }
    }
  }
  if total == 0 {
    100
  } else {
    covered * 100 / total
  }
}

///|
pub fn missing_schema_fields(
  schema : ConfigSchema,
  view : ConfigView,
) -> Array[String] {
  let missing : Array[String] = []
  for schema_section in schema.sections {
    for schema_field in schema_section.fields {
      if view.get_path(schema_field.path) == None &&
        schema_field.default_value == None {
        missing.push(schema_field.path)
      }
    }
  }
  missing
}

///|
pub fn render_schema_gap_markdown(
  schema : ConfigSchema,
  view : ConfigView,
) -> String {
  let builder = StringBuilder()
  builder.write_string("# Schema Gap Report\n\n")
  builder.write_string("- Schema: " + schema.name + "\n")
  builder.write_string(
    "- Coverage: " + schema_coverage(schema, view).to_string() + "%\n\n",
  )
  let missing = missing_schema_fields(schema, view)
  if missing.length() == 0 {
    builder.write_string("All schema fields are present or have defaults.\n")
  } else {
    builder.write_string("## Missing Fields\n\n")
    for path in missing {
      builder.write_string("- " + path + "\n")
    }
  }
  builder.to_string()
}