///|
/// INI file value type
pub(all) enum IniValue {
  String(String)
  Int(Int)
  Double(Double)
  Bool(Bool)
} derive(Show, Eq)

///|
/// INI file structure: sections map to key-value pairs
pub struct IniFile {
  sections : Map[String, Map[String, IniValue]]
} derive(Show)

///|
/// Create a new empty INI file
pub fn IniFile::new() -> IniFile {
  { sections: {} }
}

///|
/// Get value from a section
pub fn IniFile::get(
  self : IniFile,
  section : String,
  key : String,
) -> IniValue? {
  match self.sections.get(section) {
    Some(section_map) => section_map.get(key)
    None => None
  }
}

///|
/// Get string value from a section
pub fn IniFile::get_string(
  self : IniFile,
  section : String,
  key : String,
) -> String? {
  match self.get(section, key) {
    Some(String(s)) => Some(s)
    _ => None
  }
}

///|
/// Get int value from a section
pub fn IniFile::get_int(self : IniFile, section : String, key : String) -> Int? {
  match self.get(section, key) {
    Some(Int(i)) => Some(i)
    _ => None
  }
}

///|
/// Get double value from a section
pub fn IniFile::get_double(
  self : IniFile,
  section : String,
  key : String,
) -> Double? {
  match self.get(section, key) {
    Some(Double(d)) => Some(d)
    _ => None
  }
}

///|
/// Get bool value from a section
pub fn IniFile::get_bool(
  self : IniFile,
  section : String,
  key : String,
) -> Bool? {
  match self.get(section, key) {
    Some(Bool(b)) => Some(b)
    _ => None
  }
}

///|
/// Set a value in a section
pub fn IniFile::set(
  self : IniFile,
  section : String,
  key : String,
  value : IniValue,
) -> Unit {
  match self.sections.get(section) {
    Some(section_map) => section_map[key] = value
    None => {
      let section_map : Map[String, IniValue] = {}
      section_map[key] = value
      self.sections[section] = section_map
    }
  }
}

///|
/// Check if a section exists
pub fn IniFile::has_section(self : IniFile, section : String) -> Bool {
  self.sections.contains(section)
}

///|
/// Get all section names
pub fn IniFile::sections(self : IniFile) -> Array[String] {
  let result : Array[String] = []
  for section, _ in self.sections {
    result.push(section)
  }
  result
}

///|
/// Get all keys in a section
pub fn IniFile::keys(self : IniFile, section : String) -> Array[String] {
  match self.sections.get(section) {
    Some(section_map) => {
      let result : Array[String] = []
      for key, _ in section_map {
        result.push(key)
      }
      result
    }
    None => []
  }
}

///|
/// Parse error type
pub(all) suberror ParseError {
  InvalidSection(Int, String)
  InvalidKeyValue(Int, String)
  EmptyKey(Int)
} derive(Show, Eq)

///|
/// Parse an INI file from a string
pub fn parse(input : String) -> IniFile raise ParseError {
  let ini = IniFile::new()
  let mut current_section = ""
  let mut line_number = 0
  for line_view in input.split("\n") {
    line_number += 1
    // Convert to string for easier processing
    let line = line_view.to_string()
    let trimmed = line.trim_space().to_string()

    // Skip empty lines and comments
    if trimmed.is_empty() || trimmed.has_prefix(";") || trimmed.has_prefix("#") {
      continue
    }

    // Parse section header [section]
    if trimmed.has_prefix("[") {
      if not(trimmed.has_suffix("]")) {
        raise ParseError::InvalidSection(line_number, line)
      }
      let section_name = try! trimmed[1:trimmed.length() - 1]
        .trim_space()
        .to_string()
      if section_name.is_empty() {
        raise ParseError::InvalidSection(line_number, line)
      }
      current_section = section_name
      if not(ini.has_section(current_section)) {
        ini.sections[current_section] = {}
      }
      continue
    }

    // Parse key=value
    match trimmed.find("=") {
      Some(eq_pos) => {
        let key = try! trimmed[0:eq_pos].trim_space().to_string()
        let value_str = try! trimmed[eq_pos + 1:trimmed.length()]
          .trim_space()
          .to_string()
        if key.is_empty() {
          raise ParseError::EmptyKey(line_number)
        }
        let value = parse_value(value_str)
        ini.set(current_section, key, value)
      }
      None => raise ParseError::InvalidKeyValue(line_number, line)
    }
  }
  ini
}

///|
/// Parse a value string into an IniValue
fn parse_value(s : String) -> IniValue {
  let trimmed = s.trim_space().to_string()

  // Try to parse as boolean
  let lower = trimmed.to_lower()
  if lower == "true" || lower == "yes" || lower == "on" || lower == "1" {
    return Bool(true)
  }
  if lower == "false" || lower == "no" || lower == "off" || lower == "0" {
    return Bool(false)
  }

  // Try to parse as integer
  let int_result = try? @strconv.parse_int(trimmed)
  if int_result is Ok(i) {
    return Int(i)
  }

  // Try to parse as double
  let double_result = try? @strconv.parse_double(trimmed)
  if double_result is Ok(d) {
    return Double(d)
  }

  // Default to string
  // Remove quotes if present
  if (trimmed.has_prefix("\"") && trimmed.has_suffix("\"")) ||
    (trimmed.has_prefix("'") && trimmed.has_suffix("'")) {
    String((try! trimmed[1:trimmed.length() - 1]).to_string())
  } else {
    String(trimmed)
  }
}

///|
/// Convert IniFile to string format
pub fn IniFile::to_string(self : IniFile) -> String {
  let buffer = @buffer.new()
  let mut first_section = true
  for section, pairs in self.sections {
    if not(first_section) {
      buffer.write_char('\n')
    }
    first_section = false
    buffer.write_char('[')
    buffer.write_string(section)
    buffer.write_string("]\n")
    for key, value in pairs {
      buffer.write_string(key)
      buffer.write_string(" = ")
      match value {
        String(s) =>
          // Quote strings that contain special characters
          if s.contains(" ") ||
            s.contains("=") ||
            s.contains(";") ||
            s.contains("#") {
            buffer.write_char('"')
            buffer.write_string(s)
            buffer.write_char('"')
          } else {
            buffer.write_string(s)
          }
        Int(i) => buffer.write_string(i.to_string())
        Double(d) => buffer.write_string(d.to_string())
        Bool(b) => buffer.write_string(if b { "true" } else { "false" })
      }
      buffer.write_char('\n')
    }
  }
  buffer.to_string()
}