///|
/// Diagnostic severity. Warning policy can be promoted by the caller.
pub(all) enum Severity {
  Error
  Warning
  Info
} derive(Eq, Debug)

///|
pub(all) struct Position {
  line : Int32
  column : Int32
} derive(Eq, Debug)

///|
pub(all) struct Span {
  start : Position
  end : Position
} derive(Eq, Debug)

///|
pub(all) struct Diagnostic {
  code : String
  severity : Severity
  message : String
  file : String
  span : Span
  entry : Int32?
  related : Span?
  hint : String
} derive(Eq, Debug)

///|
pub(all) struct Comment {
  kind : String
  text : String
  span : Span
} derive(Eq, Debug)

///|
pub(all) struct Translation {
  index : Int32
  value : String
  span : Span
} derive(Eq, Debug)

///|
pub(all) struct Entry {
  context : String?
  id : String
  plural : String?
  translations : Array[Translation]
  flags : Array[String]
  comments : Array[Comment]
  references : Array[String]
  obsolete : Bool
  span : Span
  id_span : Span
  plural_span : Span?
} derive(Eq, Debug)

///|
pub(all) struct Catalog {
  file : String
  entries : Array[Entry]
  diagnostics : Array[Diagnostic]
} derive(Debug)

///|
pub(all) struct Options {
  empty_severity : Severity
  fuzzy_severity : Severity
  strict : Bool
  plural_max : Int32
  expression_depth : Int32
  expression_budget : Int32
  max_entries : Int32
  max_line_chars : Int32
} derive(Eq, Debug)

///|
pub fn default_options() -> Options {
  {
    empty_severity: Warning,
    fuzzy_severity: Warning,
    strict: false,
    plural_max: 1000,
    expression_depth: 64,
    expression_budget: 10000,
    max_entries: 100000,
    max_line_chars: 1048576,
  }
}

///|
pub(all) struct Statistics {
  total : Int32
  active : Int32
  obsolete : Int32
  translated : Int32
  untranslated : Int32
  fuzzy : Int32
  plural : Int32
  errors : Int32
  warnings : Int32
  infos : Int32
} derive(Eq, Debug)

///|
pub(all) struct Report {
  catalog : Catalog
  diagnostics : Array[Diagnostic]
  stats : Statistics
  exit_code : Int32
  plural_checked_through : Int32?
} derive(Debug)

///|
fn point(line : Int32, column : Int32) -> Position {
  { line, column, }
}

///|
fn line_span(line : Int32, column : Int32, length : Int32) -> Span {
  { start: point(line, column), end: point(line, column + length), }
}

///|
fn issue(
  code : String,
  severity : Severity,
  message : String,
  file : String,
  span : Span,
  entry? : Int32,
  related? : Span,
  hint~ : String = "",
) -> Diagnostic {
  { code, severity, message, file, span, entry, related, hint, }
}

///|
pub fn Entry::translation(self : Entry, index : Int32) -> String? {
  for value in self.translations {
    if value.index == index {
      return Some(value.value)
    }
  }
  None
}

///|
pub fn Entry::has_flag(self : Entry, flag : String) -> Bool {
  self.flags.contains(flag)
}

///|
pub fn Entry::is_header(self : Entry) -> Bool {
  self.id == "" && self.context is None && !self.obsolete
}

///|
pub fn Entry::is_translated(self : Entry) -> Bool {
  !self.translations.is_empty() && self.translations.all(t => t.value != "")
}

///|
pub fn Catalog::header(self : Catalog) -> Entry? {
  for entry in self.entries {
    if entry.is_header() {
      return Some(entry)
    }
  }
  None
}

///|
pub fn severity_name(severity : Severity) -> String {
  match severity {
    Error => "error"
    Warning => "warning"
    Info => "info"
  }
}

///|
pub fn statistics(
  catalog : Catalog,
  diagnostics : Array[Diagnostic],
) -> Statistics {
  let mut active = 0
  let mut obsolete = 0
  let mut translated = 0
  let mut untranslated = 0
  let mut fuzzy = 0
  let mut plural = 0
  for entry in catalog.entries {
    if entry.obsolete {
      obsolete += 1
      continue
    }
    if entry.is_header() {
      continue
    }
    active += 1
    if entry.is_translated() {
      translated += 1
    } else {
      untranslated += 1
    }
    if entry.has_flag("fuzzy") {
      fuzzy += 1
    }
    if entry.plural is Some(_) {
      plural += 1
    }
  }
  let mut errors = 0
  let mut warnings = 0
  let mut infos = 0
  for diagnostic in diagnostics {
    match diagnostic.severity {
      Error => errors += 1
      Warning => warnings += 1
      Info => infos += 1
    }
  }
  {
    total: active + obsolete,
    active,
    obsolete,
    translated,
    untranslated,
    fuzzy,
    plural,
    errors,
    warnings,
    infos,
  }
}