///|
pub struct TokenQuery {
  kind : TokenKind?
  prefix : String
  text : String
  value : String
} derive(Debug, Eq)

///|
pub fn TokenQuery::empty() -> TokenQuery {
  { kind: None, prefix: "", text: "", value: "" }
}

///|
pub fn TokenQuery::parse(text : String) -> TokenQuery {
  let query = TokenQuery::empty()
  let mut result = query
  for part in text.split(" ") {
    let item = part.trim().to_owned()
    if item == "" {
      continue
    }
    match query_key_value(item) {
      Some((key, value)) =>
        match key {
          "kind" => result = { ..result, kind: token_kind_from_name(value) }
          "prefix" =>
            result = {
              ..result,
              prefix: TokenPath::from_string(value).canonical(),
            }
          "text" | "name" => result = { ..result, text: value }
          "value" => result = { ..result, value, }
          _ => ()
        }
      None => result = { ..result, text: item }
    }
  }
  result
}

///|
pub fn TokenQuery::with_kind(self : TokenQuery, kind : TokenKind) -> TokenQuery {
  { ..self, kind: Some(kind) }
}

///|
pub fn TokenQuery::with_prefix(
  self : TokenQuery,
  prefix : String,
) -> TokenQuery {
  { ..self, prefix: TokenPath::from_string(prefix).canonical() }
}

///|
pub fn TokenQuery::with_text(self : TokenQuery, text : String) -> TokenQuery {
  { ..self, text, }
}

///|
pub fn TokenCatalog::query(
  self : TokenCatalog,
  query : TokenQuery,
) -> Array[CatalogEntry] {
  let result : Array[CatalogEntry] = []
  for entry in self.entries {
    let kind_ok = match query.kind {
      Some(kind) => entry.kind == kind
      None => true
    }
    let prefix_ok = query.prefix == "" ||
      entry.path == query.prefix ||
      entry.path.has_prefix(query.prefix + ".")
    let text_ok = query.text == "" ||
      ascii_lower(entry.path).contains(ascii_lower(query.text)) ||
      ascii_lower(entry.description).contains(ascii_lower(query.text))
    let value_ok = query.value == "" ||
      theme_value_display(entry.value).contains(query.value)
    if kind_ok && prefix_ok && text_ok && value_ok {
      result.push(entry)
    }
  }
  result
}

///|
fn query_key_value(text : String) -> (String, String)? {
  for i, ch in text {
    if ch == '=' {
      return Some(
        (
          substring_owned(text, 0, i),
          substring_owned(text, i + 1, text.length()),
        ),
      )
    }
  }
  None
}

///|
fn token_kind_from_name(text : String) -> TokenKind? {
  match normalize_identifier(text) {
    "color" | "colors" => Some(Color)
    "number" | "size" => Some(Number)
    "text" | "string" => Some(Text)
    "boolean" | "bool" => Some(Boolean)
    _ => None
  }
}