///|
/// Distribution metrics for reviewing a theme before it is exported.
pub struct TokenMetrics {
  total : Int
  colors : Int
  numbers : Int
  texts : Int
  booleans : Int
  aliases : Int
  documented : Int
  empty_values : Int
} derive(Debug, Eq)

///|
pub fn ThemeDocument::metrics(self : ThemeDocument) -> TokenMetrics {
  let counts = self.kind_counts()
  let mut documented = 0
  let mut empty_values = 0
  for token in self.tokens {
    if token.description.trim().to_owned() != "" {
      documented += 1
    }
    if theme_value_is_empty(token.value) {
      empty_values += 1
    }
  }
  {
    total: self.tokens.length(),
    colors: counts[0],
    numbers: counts[1],
    texts: counts[2],
    booleans: counts[3],
    aliases: self.aliases.length(),
    documented,
    empty_values,
  }
}

///|
pub fn TokenMetrics::documentation_rate(self : TokenMetrics) -> Int {
  if self.total == 0 {
    100
  } else {
    self.documented * 100 / self.total
  }
}

///|
pub fn TokenMetrics::empty_rate(self : TokenMetrics) -> Int {
  if self.total == 0 {
    0
  } else {
    self.empty_values * 100 / self.total
  }
}

///|
pub fn TokenMetrics::kind_count(self : TokenMetrics, kind : TokenKind) -> Int {
  match kind {
    Color => self.colors
    Number => self.numbers
    Text => self.texts
    Boolean => self.booleans
  }
}

///|
pub fn TokenMetrics::density_score(self : TokenMetrics) -> Int {
  let mut score = 100
  if self.empty_rate() > 0 {
    score -= self.empty_rate()
  }
  if self.documentation_rate() < 80 {
    score -= 20
  }
  if self.total == 0 {
    score -= 50
  }
  if score < 0 {
    0
  } else {
    score
  }
}

///|
pub fn TokenMetrics::is_healthy(self : TokenMetrics) -> Bool {
  self.total > 0 && self.empty_values == 0 && self.documentation_rate() >= 80
}

///|
pub fn TokenMetrics::to_markdown(self : TokenMetrics) -> String {
  "| Metric | Value |\n| --- | ---: |\n" +
  "| Total tokens | " +
  self.total.to_string() +
  " |\n" +
  "| Color tokens | " +
  self.colors.to_string() +
  " |\n" +
  "| Number tokens | " +
  self.numbers.to_string() +
  " |\n" +
  "| Text tokens | " +
  self.texts.to_string() +
  " |\n" +
  "| Boolean tokens | " +
  self.booleans.to_string() +
  " |\n" +
  "| Aliases | " +
  self.aliases.to_string() +
  " |\n" +
  "| Documentation rate | " +
  self.documentation_rate().to_string() +
  "% |\n" +
  "| Empty-value rate | " +
  self.empty_rate().to_string() +
  "% |\n" +
  "| Density score | " +
  self.density_score().to_string() +
  "/100 |"
}

///|
pub fn TokenMetrics::to_json(self : TokenMetrics) -> String {
  "{\"total\":" +
  self.total.to_string() +
  ",\"colors\":" +
  self.colors.to_string() +
  ",\"numbers\":" +
  self.numbers.to_string() +
  ",\"texts\":" +
  self.texts.to_string() +
  ",\"booleans\":" +
  self.booleans.to_string() +
  ",\"aliases\":" +
  self.aliases.to_string() +
  ",\"documentationRate\":" +
  self.documentation_rate().to_string() +
  ",\"densityScore\":" +
  self.density_score().to_string() +
  "}"
}

///|
pub fn ThemeDocument::metric_markdown(self : ThemeDocument) -> String {
  self.metrics().to_markdown()
}

///|
pub fn ThemeDocument::metric_json(self : ThemeDocument) -> String {
  self.metrics().to_json()
}

///|
pub fn ThemeDocument::has_only_documented_tokens(self : ThemeDocument) -> Bool {
  self.metrics().documented == self.metrics().total
}

///|
pub fn ThemeDocument::has_kind(self : ThemeDocument, kind : TokenKind) -> Bool {
  self.metrics().kind_count(kind) > 0
}

///|
pub fn ThemeDocument::value_for(
  self : ThemeDocument,
  path : String,
) -> ThemeValue? {
  match self.find(path) {
    Some(token) => Some(token.value)
    None => None
  }
}

///|
pub fn ThemeDocument::values_for_kind(
  self : ThemeDocument,
  kind : TokenKind,
) -> Array[ThemeValue] {
  self.tokens
  .filter(fn(token) { token.kind == kind })
  .map(fn(token) { token.value })
}

///|
pub fn ThemeDocument::paths_for_kind(
  self : ThemeDocument,
  kind : TokenKind,
) -> Array[String] {
  self.tokens
  .filter(fn(token) { token.kind == kind })
  .map(fn(token) { token.path.canonical() })
}

///|
fn theme_value_is_empty(value : ThemeValue) -> Bool {
  match value {
    ColorValue(text) => text.trim().to_owned() == ""
    NumberValue(number) => number == 0
    TextValue(text) => text.trim().to_owned() == ""
    BooleanValue(_) => false
  }
}