///|
pub struct CatalogEntry {
  path : String
  name : String
  kind : TokenKind
  value : ThemeValue
  description : String
} derive(Debug, Eq)

///|
pub struct CatalogStats {
  total : Int
  color_count : Int
  number_count : Int
  text_count : Int
  boolean_count : Int
  described_count : Int
} derive(Debug, Eq)

///|
pub struct TokenCatalog {
  entries : Array[CatalogEntry]
} derive(Debug, Eq)

///|
pub fn CatalogEntry::from_theme_token(token : ThemeToken) -> CatalogEntry {
  {
    path: token.path.canonical(),
    name: last_path_segment(token.path),
    kind: token.kind,
    value: token.value,
    description: token.description,
  }
}

///|
pub fn TokenCatalog::empty() -> TokenCatalog {
  { entries: [] }
}

///|
pub fn TokenCatalog::from_theme_document(
  document : ThemeDocument,
) -> TokenCatalog {
  let entries : Array[CatalogEntry] = []
  for token in document.sorted().tokens {
    entries.push(CatalogEntry::from_theme_token(token))
  }
  { entries, }
}

///|
pub fn TokenCatalog::from_brand_book(book : BrandBook) -> TokenCatalog {
  TokenCatalog::from_theme_document(book.theme_document())
}

///|
pub fn TokenCatalog::add(
  self : TokenCatalog,
  entry : CatalogEntry,
) -> TokenCatalog {
  let entries : Array[CatalogEntry] = []
  for item in self.entries {
    entries.push(item)
  }
  entries.push(entry)
  { entries, }
}

///|
pub fn TokenCatalog::len(self : TokenCatalog) -> Int {
  self.entries.length()
}

///|
pub fn TokenCatalog::is_empty(self : TokenCatalog) -> Bool {
  self.entries.length() == 0
}

///|
pub fn TokenCatalog::entries(self : TokenCatalog) -> Array[CatalogEntry] {
  let result : Array[CatalogEntry] = []
  for entry in self.entries {
    result.push(entry)
  }
  result
}

///|
pub fn TokenCatalog::names(self : TokenCatalog) -> Array[String] {
  let result : Array[String] = []
  for entry in self.entries {
    result.push(entry.name)
  }
  result
}

///|
pub fn TokenCatalog::paths(self : TokenCatalog) -> Array[String] {
  let result : Array[String] = []
  for entry in self.entries {
    result.push(entry.path)
  }
  result
}

///|
pub fn TokenCatalog::find(self : TokenCatalog, path : String) -> CatalogEntry? {
  let wanted = TokenPath::from_string(path).canonical()
  for entry in self.entries {
    if entry.path == wanted {
      return Some(entry)
    }
  }
  None
}

///|
pub fn TokenCatalog::contains(self : TokenCatalog, path : String) -> Bool {
  self.find(path) is Some(_)
}

///|
pub fn TokenCatalog::by_kind(
  self : TokenCatalog,
  kind : TokenKind,
) -> Array[CatalogEntry] {
  let result : Array[CatalogEntry] = []
  for entry in self.entries {
    if entry.kind == kind {
      result.push(entry)
    }
  }
  result
}

///|
pub fn TokenCatalog::by_prefix(
  self : TokenCatalog,
  prefix : String,
) -> Array[CatalogEntry] {
  let normalized = TokenPath::from_string(prefix).canonical()
  let result : Array[CatalogEntry] = []
  for entry in self.entries {
    if entry.path == normalized || entry.path.has_prefix(normalized + ".") {
      result.push(entry)
    }
  }
  result
}

///|
pub fn TokenCatalog::search(
  self : TokenCatalog,
  query : String,
) -> Array[CatalogEntry] {
  let wanted = ascii_lower(query)
  let result : Array[CatalogEntry] = []
  for entry in self.entries {
    if ascii_lower(entry.path).contains(wanted) ||
      ascii_lower(entry.name).contains(wanted) ||
      ascii_lower(entry.description).contains(wanted) {
      result.push(entry)
    }
  }
  result
}

///|
pub fn TokenCatalog::stats(self : TokenCatalog) -> CatalogStats {
  let mut colors = 0
  let mut numbers = 0
  let mut texts = 0
  let mut booleans = 0
  let mut described = 0
  for entry in self.entries {
    match entry.kind {
      Color => colors += 1
      Number => numbers += 1
      Text => texts += 1
      Boolean => booleans += 1
    }
    if entry.description.trim().to_owned() != "" {
      described += 1
    }
  }
  {
    total: self.entries.length(),
    color_count: colors,
    number_count: numbers,
    text_count: texts,
    boolean_count: booleans,
    described_count: described,
  }
}

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

///|
pub fn TokenCatalog::to_markdown(self : TokenCatalog) -> String {
  let lines : Array[String] = [
    "## Token catalog", "", "| Path | Kind | Value | Description |", "| --- | --- | --- | --- |",
  ]
  for entry in self.entries {
    lines.push(
      "| `" +
      entry.path +
      "` | " +
      token_kind_name(entry.kind) +
      " | " +
      theme_value_display(entry.value) +
      " | " +
      entry.description +
      " |",
    )
  }
  lines.join("\n")
}

///|
fn last_path_segment(path : TokenPath) -> String {
  if path.segments.length() == 0 {
    ""
  } else {
    path.segments[path.segments.length() - 1]
  }
}

///|
fn theme_value_display(value : ThemeValue) -> String {
  match value {
    ColorValue(text) => "`" + text + "`"
    NumberValue(number) => number.to_string()
    TextValue(text) => "`" + text + "`"
    BooleanValue(flag) => if flag { "true" } else { "false" }
  }
}