// config.mbt — Config parser for Casbin model files.
//
// The format is a small INI dialect: `[section]` headers and `key = value`
// entries, with `#` and `;` comment lines and blank lines ignored. Values
// keep everything after the first `=`, so matcher expressions containing
// `==` are preserved verbatim. A repeated section header extends the
// existing section, and duplicate keys are kept in declaration order;
// `get` returns the first occurrence.
//
// ponytail: comments are whole-line only and values are single-line (no
// go-ini style continuation); add both when a real model file needs them.

///|
/// One `key = value` entry inside a configuration section.
pub(all) struct ConfigEntry {
  key : String
  value : String
} derive(Debug)

///|
/// The entry key.
pub fn ConfigEntry::key(self : ConfigEntry) -> String {
  self.key
}

///|
/// The entry value with surrounding whitespace removed.
pub fn ConfigEntry::value(self : ConfigEntry) -> String {
  self.value
}

///|
/// A named configuration section with its entries in declaration order.
pub(all) struct ConfigSection {
  name : String
  entries : Array[ConfigEntry]
} derive(Debug)

///|
/// Parsed Casbin configuration (INI-style model file).
pub(all) struct Config {
  sections : Array[ConfigSection]
} derive(Debug)

///|
/// Parses Casbin model configuration text.
///
/// Returns `Err(CasbinErrorKind::ConfigSyntax)` for malformed text; the
/// error carries the 1-based line number.
pub fn Config::parse(text : String) -> Result[Config, CasbinError] {
  Ok(parse_config_raise(text)) catch {
    error => Err(error)
  }
}

///|
/// Internal: parses configuration text, raising on the first violation.
fn parse_config_raise(text : String) -> Config raise CasbinError {
  let sections : Array[ConfigSection] = []
  let mut current : Int = -1
  let mut line_number : Int = 0
  for raw_line in text.split("\n") {
    line_number += 1
    let line = raw_line.trim()
    if line.is_empty() || line.has_prefix("#") || line.has_prefix(";") {
      continue
    }
    if line.has_prefix("[") {
      if !line.has_suffix("]") {
        raise casbin_error_at(
          ConfigSyntax,
          line_number,
          "section header is missing ']'",
        )
      }
      let name = line.sub(start=1, end=line.length() - 1).trim()
      if name.is_empty() {
        raise casbin_error_at(ConfigSyntax, line_number, "empty section name")
      }
      if name.contains("[") || name.contains("]") {
        raise casbin_error_at(
          ConfigSyntax,
          line_number,
          "invalid section name \"" + name.to_owned() + "\"",
        )
      }
      let name = name.to_owned()
      current = find_section(sections, name)
      if current < 0 {
        sections.push({ name, entries: [], })
        current = sections.length() - 1
      }
      continue
    }
    if current < 0 {
      raise casbin_error_at(
        ConfigSyntax,
        line_number,
        "entry outside of any section",
      )
    }
    let (key, value) = match line.split_once("=") {
      Some(pair) => pair
      None =>
        raise casbin_error_at(
          ConfigSyntax,
          line_number,
          "expected \"key = value\"",
        )
    }
    let key = key.trim()
    if key.is_empty() {
      raise casbin_error_at(ConfigSyntax, line_number, "empty key")
    }
    sections[current].entries.push({
      key: key.to_owned(),
      value: value.trim().to_owned(),
    })
  }
  { sections, }
}

///|
/// The value of `key` in `section`, or `None` when either is absent.
/// When a key appears several times the first occurrence wins.
pub fn Config::get(self : Config, section : String, key : String) -> String? {
  for s in self.sections {
    if s.name == section {
      for entry in s.entries {
        if entry.key == key {
          return Some(entry.value)
        }
      }
      return None
    }
  }
  None
}

///|
/// The entries of `section` in declaration order; empty when the section
/// is absent.
pub fn Config::entries(self : Config, section : String) -> Array[ConfigEntry] {
  for s in self.sections {
    if s.name == section {
      return s.entries
    }
  }
  []
}

///|
/// The names of all declared sections in declaration order.
pub fn Config::section_names(self : Config) -> Array[String] {
  let names : Array[String] = []
  for s in self.sections {
    names.push(s.name)
  }
  names
}

///|
fn find_section(sections : Array[ConfigSection], name : String) -> Int {
  for i in 0..