///|
pub struct TokenSet {
  name : String
  tokens : Array[Token]
} derive(Debug, Eq)

///|
pub fn TokenSet::new(name~ : String, tokens~ : Array[Token]) -> TokenSet {
  { name, tokens }
}

///|
pub fn TokenSet::size(self : TokenSet) -> Int {
  self.tokens.length()
}

///|
pub fn TokenSet::name(self : TokenSet) -> String {
  self.name
}

///|
pub fn TokenSet::all(self : TokenSet) -> Array[Token] {
  self.tokens
}

///|
pub fn TokenSet::get(self : TokenSet, name : String) -> Token? {
  find_token(self.tokens, name)
}

///|
pub fn TokenSet::canonical(self : TokenSet) -> TokenSet {
  { ..self, tokens: canonical_tokens(self.tokens) }
}

///|
pub fn TokenSet::merge(self : TokenSet, overlay : TokenSet) -> TokenSet {
  { name: overlay.name, tokens: merge_tokens(self.tokens, overlay.tokens) }
}

///|
pub fn TokenSet::diff(self : TokenSet, other : TokenSet) -> Array[TokenChange] {
  diff_tokens(self.tokens, other.tokens)
}

///|
pub fn TokenSet::with_prefix(self : TokenSet, prefix : String) -> TokenSet {
  { ..self, tokens: prefix_tokens(self.tokens, prefix) }
}

///|
pub fn TokenSet::render(self : TokenSet, format : OutputFormat) -> String {
  render_output(self.tokens, format)
}

///|
pub fn TokenSet::valid(self : TokenSet) -> Bool {
  validate_all(self.tokens).length() == 0
}

///|
pub fn TokenSet::validate(self : TokenSet) -> Array[TokenProblem] {
  validate_all(self.tokens)
}

///|
pub fn token_set(name~ : String, tokens~ : Array[Token]) -> TokenSet {
  TokenSet::new(name~, tokens~)
}

///|
pub fn token_names(tokens : Array[Token]) -> Array[String] {
  [
    for token in tokens => token.name
  ]
}

///|
pub fn find_token(tokens : Array[Token], name : String) -> Token? {
  for token in tokens {
    if token.name == name {
      return Some(token)
    }
  }
  None
}

///|
pub fn tokens_of_kind(tokens : Array[Token], kind : TokenKind) -> Array[Token] {
  [
    for token in tokens if token.kind == kind => token
  ]
}

///|
pub fn tokens_with_prefix(
  tokens : Array[Token],
  prefix : String,
) -> Array[Token] {
  [
    for token in tokens if token.name.has_prefix(prefix) => token
  ]
}

///|
pub fn token_names_with_prefix(
  tokens : Array[Token],
  prefix : String,
) -> Array[String] {
  [
    for token in tokens if token.name.has_prefix(prefix) => token.name
  ]
}

///|
pub fn token_count_by_kind(tokens : Array[Token], kind : TokenKind) -> Int {
  tokens_of_kind(tokens, kind).length()
}

///|
pub fn has_duplicate_names(tokens : Array[Token]) -> Bool {
  for i in 0.. Array[Token] {
  let result : Array[Token] = []
  for token in tokens {
    if find_token(result, token.name) is None {
      result.push(token)
    }
  }
  result
}

///|
pub fn merge_tokens(
  base : Array[Token],
  overlay : Array[Token],
) -> Array[Token] {
  let result = base.copy()
  for incoming in overlay {
    let mut replaced = false
    for i in 0.. Array[Token] {
  [
    for token in tokens if !contains_string(names, token.name) => token
  ]
}

///|
fn contains_string(values : Array[String], wanted : String) -> Bool {
  for value in values {
    if value == wanted {
      return true
    }
  }
  false
}

///|
pub enum TokenChange {
  Added(Token)
  Removed(Token)
  Changed(Token, Token)
} derive(Debug, Eq)

///|
pub fn diff_tokens(
  before : Array[Token],
  after : Array[Token],
) -> Array[TokenChange] {
  let changes : Array[TokenChange] = []
  for old in before {
    match find_token(after, old.name) {
      None => changes.push(Removed(old))
      Some(new) => if old != new { changes.push(Changed(old, new)) }
    }
  }
  for new in after {
    if find_token(before, new.name) is None {
      changes.push(Added(new))
    }
  }
  changes
}

///|
pub fn TokenChange::kind(self : TokenChange) -> String {
  match self {
    Added(_) => "added"
    Removed(_) => "removed"
    Changed(_, _) => "changed"
  }
}

///|
pub fn TokenChange::name(self : TokenChange) -> String {
  match self {
    Added(token) => token.name
    Removed(token) => token.name
    Changed(_, token) => token.name
  }
}

///|
pub fn diff_summary(changes : Array[TokenChange]) -> String {
  let mut added = 0
  let mut removed = 0
  let mut changed = 0
  for change in changes {
    match change {
      Added(_) => added += 1
      Removed(_) => removed += 1
      Changed(_, _) => changed += 1
    }
  }
  "added=\{added}, removed=\{removed}, changed=\{changed}"
}

///|
pub fn prefix_tokens(tokens : Array[Token], prefix : String) -> Array[Token] {
  [
    for token in tokens => { ..token, name: "\{prefix}\{token.name}" }
  ]
}

///|
pub fn rename_token(
  tokens : Array[Token],
  old_name : String,
  new_name : String,
) -> Array[Token] {
  [
    for token in tokens => {
      if token.name == old_name {
        { ..token, name: new_name }
      } else {
        token
      }
    }
  ]
}

///|
pub fn map_descriptions(tokens : Array[Token], prefix : String) -> Array[Token] {
  [
    for token in tokens => {
      { ..token, description: "\{prefix}\{token.description}" }
    }
  ]
}

///|
pub fn normalize_name(name : String) -> String {
  let output : Array[String] = []
  let mut previous_separator = false
  for character in name.to_lower() {
    if is_name_character(character) {
      output.push("\{character}")
      previous_separator = false
    } else if !previous_separator && output.length() > 0 {
      output.push("-")
      previous_separator = true
    }
  }
  let value = output.join("")
  if value.has_suffix("-") && value.length() > 0 {
    value[0:value.length() - 1].to_owned()
  } else {
    value
  }
}

///|
fn is_name_character(character : Char) -> Bool {
  (character >= 'a' && character <= 'z') ||
  (character >= '0' && character <= '9')
}

///|
pub fn normalize_tokens(tokens : Array[Token]) -> Array[Token] {
  [
    for token in tokens => { ..token, name: normalize_name(token.name) }
  ]
}

///|
pub fn sort_tokens_by_name(tokens : Array[Token]) -> Array[Token] {
  let result = tokens.copy()
  for i in 1.. 0 && result[j - 1].name > current.name {
      result[j] = result[j - 1]
      j -= 1
    }
    result[j] = current
  }
  result
}

///|
pub fn canonical_tokens(tokens : Array[Token]) -> Array[Token] {
  sort_tokens_by_name(unique_tokens(normalize_tokens(tokens)))
}

///|
pub(all) struct TokenStats {
  total : Int
  valid : Int
  aliases : Int
  duplicate_names : Int
  empty_values : Int
  longest_name : Int
  longest_value : Int
} derive(Debug, Eq)

///|
pub fn TokenStats::total(self : TokenStats) -> Int {
  self.total
}

///|
pub fn TokenStats::valid(self : TokenStats) -> Int {
  self.valid
}

///|
pub fn TokenStats::aliases(self : TokenStats) -> Int {
  self.aliases
}

///|
pub fn TokenStats::duplicate_names(self : TokenStats) -> Int {
  self.duplicate_names
}

///|
pub fn TokenStats::empty_values(self : TokenStats) -> Int {
  self.empty_values
}

///|
pub fn TokenStats::longest_name(self : TokenStats) -> Int {
  self.longest_name
}

///|
pub fn TokenStats::longest_value(self : TokenStats) -> Int {
  self.longest_value
}

///|
pub fn TokenStats::summary(self : TokenStats) -> String {
  "total=\{self.total}, valid=\{self.valid}, aliases=\{self.aliases}, duplicates=\{self.duplicate_names}, empty=\{self.empty_values}, longest-name=\{self.longest_name}, longest-value=\{self.longest_value}"
}

///|
pub fn analyze_tokens(tokens : Array[Token]) -> TokenStats {
  let mut valid = 0
  let mut aliases = 0
  let mut empty_values = 0
  let mut longest_name = 0
  let mut longest_value = 0
  for token in tokens {
    if token.validate().length() == 0 {
      valid += 1
    }
    if token.value.has_prefix("{") && token.value.has_suffix("}") {
      aliases += 1
    }
    if token.value == "" {
      empty_values += 1
    }
    if token.name.length() > longest_name {
      longest_name = token.name.length()
    }
    if token.value.length() > longest_value {
      longest_value = token.value.length()
    }
  }
  {
    total: tokens.length(),
    valid,
    aliases,
    duplicate_names: duplicate_name_count(tokens),
    empty_values,
    longest_name,
    longest_value,
  }
}

///|
fn duplicate_name_count(tokens : Array[Token]) -> Int {
  let names : Array[String] = []
  let mut duplicates = 0
  for token in tokens {
    if contains_string(names, token.name) {
      duplicates += 1
    } else {
      names.push(token.name)
    }
  }
  duplicates
}

///|
pub fn tokens_referencing(tokens : Array[Token], name : String) -> Array[Token] {
  let reference = "{" + name + "}"
  [
    for token in tokens if token.value == reference => token
  ]
}

///|
pub fn replace_token_reference(
  tokens : Array[Token],
  old_name : String,
  new_name : String,
) -> Array[Token] {
  let old_reference = "{" + old_name + "}"
  let new_reference = "{" + new_name + "}"
  [
    for token in tokens => {
      if token.value == old_reference {
        { ..token, value: new_reference }
      } else {
        token
      }
    }
  ]
}

///|
pub fn validate_unique_names(tokens : Array[Token]) -> Array[String] {
  let problems : Array[String] = []
  for i in 0.. Array[String] {
  let problems : Array[String] = []
  for token in tokens {
    if normalize_name(token.name) != token.name {
      problems.push("token name `\{token.name}` is not canonical")
    }
    if token.name.has_prefix("-") || token.name.has_suffix("-") {
      problems.push("token name `\{token.name}` has a boundary separator")
    }
  }
  problems
}

///|
pub fn Token::css_name(self : Token) -> String {
  "--\{normalize_name(self.name)}"
}

///|
pub fn Token::css_declaration(self : Token) -> String {
  "\{self.css_name()}: \{self.value};"
}

///|
pub fn to_css_minified(tokens : Array[Token]) -> String {
  let declarations = [
    for token in canonical_tokens(tokens) => "\{token.css_declaration()}"
  ]
  ":root{\{declarations.join("")}}"
}

///|
pub fn to_css_scoped(tokens : Array[Token], selector : String) -> String {
  let rows = [
    for token in canonical_tokens(tokens) => "  \{token.css_declaration()}"
  ]
  "\{selector} {\n\{rows.join("\n")}\n}\n"
}

///|
pub fn to_css_layer(tokens : Array[Token], layer : String) -> String {
  "@layer \{layer} {\n\{to_css(tokens)}"
}

///|
pub fn to_json_lines(tokens : Array[Token]) -> String {
  let rows = [ for token in canonical_tokens(tokens) => token.json_object() ]
  "\{rows.join("\n")}\n"
}

///|
pub fn to_markdown_sorted(tokens : Array[Token]) -> String {
  to_markdown_table(canonical_tokens(tokens))
}

///|
pub fn render_all(tokens : Array[Token]) -> String {
  "CSS\n\{to_css(tokens)}\nJSON\n\{to_json(tokens)}\nMoonBit\n\{to_moonbit_constants(tokens)}\nMarkdown\n\{to_markdown_table(tokens)}"
}