///|
fn trim_quotes(text : String) -> String {
  let value = text.trim().to_owned()
  if value.length() >= 2 && value.has_prefix("\"") && value.has_suffix("\"") {
    value[1:value.length() - 1].to_owned()
  } else {
    value
  }
}

///|
fn parse_bool(text : String) -> Bool? {
  let value = text.trim().to_owned().to_lower()
  if value == "true" {
    Some(true)
  } else if value == "false" {
    Some(false)
  } else {
    None
  }
}

///|
fn parse_int_opt(text : String) -> Int? {
  let value = text.trim().to_owned()
  if value.is_empty() {
    None
  } else {
    let mut result = 0
    for ch in value {
      if ch >= '0' && ch <= '9' {
        result = result * 10 + (ch.to_int() - '0'.to_int())
      } else {
        return None
      }
    }
    Some(result)
  }
}

///|
fn parse_string_array_value(value : String) -> Array[String] {
  let text = value.trim().to_owned()
  if text.has_prefix("[") && text.has_suffix("]") {
    let inner = text[1:text.length() - 1].to_owned()
    if inner.trim().is_empty() {
      []
    } else {
      inner
      .split(",")
      .map(part => trim_quotes(part.to_owned()))
      .filter(item => item != "")
      .collect()
    }
  } else {
    []
  }
}

///|
fn split_key_value(line : String) -> (String, String)? {
  match line.find("=") {
    Some(index) =>
      Some(
        (
          line[:index].to_owned().trim().to_owned(),
          line[index + 1:].to_owned().trim().to_owned(),
        ),
      )
    None => None
  }
}

///|
fn parse_config_lines(lines : Array[String]) -> ScanConfig {
  let mut config = default_scan_config()
  let mut section = ""
  let mut collecting_exclude = false
  let exclude_parts : Array[String] = []
  for raw in lines {
    let line = raw.trim().to_owned()
    if collecting_exclude {
      exclude_parts.push(line)
      if line.contains("]") {
        collecting_exclude = false
        config = {
          ..config,
          exclusions: parse_string_array_value(exclude_parts.join(" ")),
        }
      }
    } else if line.is_empty() || line.has_prefix("#") {
      continue
    } else if line.has_prefix("[") && line.has_suffix("]") {
      section = line[1:line.length() - 1].to_owned().trim().to_owned()
    } else {
      match split_key_value(line) {
        Some((key, value)) =>
          if section == "" && key == "exclude" {
            if value.contains("[") && !value.contains("]") {
              collecting_exclude = true
              exclude_parts.clear()
              exclude_parts.push(value)
            } else {
              config = { ..config, exclusions: parse_string_array_value(value) }
            }
          } else if section == "rules" {
            match parse_bool(value) {
              Some(flag) =>
                if key == "check_readme" {
                  config = {
                    ..config,
                    rules: { ..config.rules, check_readme: flag },
                  }
                } else if key == "check_moon_mod" {
                  config = {
                    ..config,
                    rules: { ..config.rules, check_moon_mod: flag },
                  }
                } else if key == "check_ci" {
                  config = {
                    ..config,
                    rules: { ..config.rules, check_ci: flag },
                  }
                } else if key == "check_generated_mbti" {
                  config = {
                    ..config,
                    rules: { ..config.rules, check_generated_mbti: flag },
                  }
                }
              None => ()
            }
          } else if section == "coverage" && key == "min_public_api_coverage" {
            config = {
              ..config,
              coverage: { min_public_api_coverage: parse_int_opt(value) },
            }
          }
        None => ()
      }
    }
  }
  config
}

///|
fn merge_exclusions(
  left : Array[String],
  right : Array[String],
) -> Array[String] {
  let merged : Array[String] = []
  for item in left {
    if item != "" {
      merged.push(item)
    }
  }
  for item in right {
    if item != "" {
      merged.push(item)
    }
  }
  merged
}

///|
/// Build default scan configuration.
pub fn default_scan_config() -> ScanConfig {
  {
    exclusions: [],
    rules: {
      check_readme: true,
      check_moon_mod: true,
      check_ci: true,
      check_generated_mbti: true,
    },
    coverage: { min_public_api_coverage: None },
  }
}

///|
/// Parse the supported `moondoccheck.toml` configuration subset.
pub fn parse_scan_config(source : String) -> ScanConfig {
  parse_config_lines(source.split("\n").map(line => line.to_owned()).collect())
}

///|
/// Merge configuration-file exclusions with command-line scan options.
pub fn apply_scan_config(
  config : ScanConfig,
  options : ScanOptions,
) -> ScanOptions {
  { exclusions: merge_exclusions(config.exclusions, options.exclusions) }
}