///|
/// Parsed manifest value retained in a form useful to release rules.
pub(all) enum ManifestValue {
  Text(String)
  TextArray(Array[String])
  Raw(String)
} derive(Debug, Eq)

///|
pub fn ManifestValue::as_text(self : ManifestValue) -> String? {
  match self {
    Text(value) => Some(value)
    _ => None
  }
}

///|
pub fn ManifestValue::as_array(self : ManifestValue) -> Array[String]? {
  match self {
    TextArray(value) => Some(value)
    _ => None
  }
}

///|
/// Field captured from moon.mod or moon.pkg with a stable source line.
pub(all) struct ManifestField {
  key : String
  value : ManifestValue
  line : Int
} derive(Debug, Eq)

///|
pub fn ManifestField::location(
  self : ManifestField,
  path : String,
) -> SourceLocation {
  SourceLocation::new(path, self.line, 1)
}

///|
/// Lightweight parse of a MoonBit manifest file.
pub(all) struct MoonManifest {
  path : String
  fields : Array[ManifestField]
  imports : Array[String]
  comments : Int
  nonempty_lines : Int
} derive(Debug, Eq)

///|
pub fn MoonManifest::new(
  path : String,
  fields : Array[ManifestField],
  imports : Array[String],
  comments : Int,
  nonempty_lines : Int,
) -> MoonManifest {
  { path, fields, imports, comments, nonempty_lines }
}

///|
pub fn MoonManifest::field(self : MoonManifest, key : String) -> ManifestField? {
  for field in self.fields {
    if field.key == key {
      return Some(field)
    }
  }
  None
}

///|
pub fn MoonManifest::text(self : MoonManifest, key : String) -> String? {
  match self.field(key) {
    Some(field) => field.value.as_text()
    None => None
  }
}

///|
pub fn MoonManifest::text_array(
  self : MoonManifest,
  key : String,
) -> Array[String] {
  match self.field(key) {
    Some(field) =>
      match field.value.as_array() {
        Some(value) => value
        None => []
      }
    None => []
  }
}

///|
pub fn MoonManifest::has_import(
  self : MoonManifest,
  package_name : String,
) -> Bool {
  self.imports.contains(package_name)
}

///|
pub fn MoonManifest::field_location(
  self : MoonManifest,
  key : String,
) -> SourceLocation? {
  match self.field(key) {
    Some(field) => Some(field.location(self.path))
    None => None
  }
}

///|
fn strip_comment(line : String) -> String {
  match line.split("//").to_array() {
    [] => line
    [first, ..] => first.to_owned()
  }
}

///|
fn quoted_values(value : String) -> Array[String] {
  let pieces = value.split("\"").to_array()
  let values : Array[String] = []
  let mut index = 1
  while index < pieces.length() {
    values.push(pieces[index].to_owned())
    index += 2
  }
  values
}

///|
fn parse_value(value : String) -> ManifestValue {
  let trimmed = value.trim().to_owned()
  let quoted = quoted_values(trimmed)
  if trimmed.has_prefix("[") && trimmed.has_suffix("]") {
    TextArray(quoted)
  } else if quoted.length() == 1 && trimmed.has_prefix("\"") {
    Text(quoted[0])
  } else {
    Raw(trimmed)
  }
}

///|
fn parse_assignment(line : String, line_number : Int) -> ManifestField? {
  let parts = line.split("=").to_array()
  if parts.length() < 2 {
    return None
  }
  let key = parts[0].trim().to_owned()
  if key.length() == 0 || !(key.get_char(0) is Some(_)) {
    return None
  }
  let value_parts : Array[StringView] = []
  for index in 1.. String? {
  let quoted = quoted_values(line)
  if quoted.length() == 0 {
    None
  } else {
    Some(quoted[0])
  }
}

///|
/// Parse moon.mod or moon.pkg without requiring a general TOML parser.
pub fn parse_manifest(path : String, content : String) -> MoonManifest {
  let fields : Array[ManifestField] = []
  let imports : Array[String] = []
  let mut comments = 0
  let mut nonempty_lines = 0
  let mut in_import_block = false
  let mut line_number = 1
  for raw_line in content.split("\n") {
    let raw = raw_line.to_owned()
    let trimmed_raw = raw.trim().to_owned()
    if trimmed_raw.has_prefix("//") {
      comments += 1
      line_number += 1
      continue
    }
    let line = strip_comment(raw).trim().to_owned()
    if line.length() == 0 {
      line_number += 1
      continue
    }
    nonempty_lines += 1
    if line == "import {" {
      in_import_block = true
      line_number += 1
      continue
    }
    if in_import_block {
      if line == "}" {
        in_import_block = false
      } else {
        match import_from_line(line) {
          Some(import_name) => imports.push(import_name)
          None => ()
        }
      }
      line_number += 1
      continue
    }
    match parse_assignment(line, line_number) {
      Some(field) => fields.push(field)
      None => ()
    }
    line_number += 1
  }
  MoonManifest::new(path, fields, imports, comments, nonempty_lines)
}

///|
pub fn parse_manifest_file(file : ProjectFile) -> MoonManifest {
  parse_manifest(file.path, file.content)
}

///|
pub fn parse_root_manifest(inventory : ProjectInventory) -> MoonManifest? {
  match inventory.find_file("moon.mod") {
    Some(file) => Some(parse_manifest_file(file))
    None => None
  }
}