///|
#warnings("-unused_field")
priv enum CandidateModifier {
  NamedModifier(value~ : String, source~ : String)
  ArbitraryModifier(value~ : String, source~ : String)
}

///|
#warnings("-unused_constructor")
priv enum CandidateValue {
  NamedValue(value~ : String, fraction~ : String?)
  ArbitraryValue(data_type~ : String?, value~ : String)
}

///|
#warnings("-unused_field")
priv enum CandidateForm {
  StaticCandidate(root~ : String)
  FunctionalCandidate(
    root~ : String,
    value~ : CandidateValue?,
    source~ : String
  )
  ArbitraryPropertyCandidate(
    property~ : String,
    value~ : String,
    source~ : String
  )
}

///|
#warnings("-unused_field")
priv enum CandidateVariant {
  ArbitraryVariant(selector~ : String, relative~ : Bool, source~ : String)
  StaticVariant(root~ : String, source~ : String)
  FunctionalVariant(
    root~ : String,
    value~ : String?,
    modifier~ : CandidateModifier?,
    source~ : String
  )
  CompoundVariant(
    root~ : String,
    modifier~ : CandidateModifier?,
    variant~ : CandidateVariant,
    source~ : String
  )
}

///|
#warnings("-unused_field")
priv struct ParsedCandidate {
  form : CandidateForm
  negative : Bool
  important : Bool
  modifier : CandidateModifier?
  variants : Array[CandidateVariant]
  raw : String
}

///|
fn split_modifier(input : String) -> (String, String?) {
  let mut depth = 0
  for i in 0.. depth += 1
      ']' | ')' => depth -= 1
      '/' =>
        if depth == 0 {
          return (input[:i].to_owned(), Some(input[i + 1:].to_owned()))
        }
      _ => ()
    }
  }
  (input, None)
}

///|
fn is_named_candidate_part(input : String) -> Bool {
  if input == "" {
    return false
  }
  for character in input {
    if !((character >= 'a' && character <= 'z') ||
      (character >= 'A' && character <= 'Z') ||
      (character >= '0' && character <= '9') ||
      character == '_' ||
      character == '.' ||
      character == '%' ||
      character == '-') {
      return false
    }
  }
  true
}

///|
fn modifier_value(modifier : String) -> String? {
  if modifier.has_prefix("[") && modifier.has_suffix("]") {
    let value = decode_arbitrary(modifier[1:modifier.length() - 1].to_owned())
    if trim(value) == "" || !is_valid_arbitrary(value) {
      None
    } else {
      Some(value)
    }
  } else if modifier.has_prefix("(") && modifier.has_suffix(")") {
    let variable = modifier[1:modifier.length() - 1].to_owned()
    if !variable.has_prefix("--") || !is_valid_arbitrary(variable) {
      None
    } else {
      Some("var(\{decode_arbitrary(variable)})")
    }
  } else if is_named_candidate_part(modifier) {
    Some(modifier)
  } else {
    None
  }
}

///|
fn parse_candidate_modifier(modifier : String) -> CandidateModifier? {
  guard modifier_value(modifier) is Some(value) else { return None }
  if modifier.has_prefix("[") || modifier.has_prefix("(") {
    Some(ArbitraryModifier(value~, source=modifier))
  } else {
    Some(NamedModifier(value~, source=modifier))
  }
}

///|
fn CandidateModifier::source(self : CandidateModifier) -> String {
  match self {
    NamedModifier(source~, ..) | ArbitraryModifier(source~, ..) => source
  }
}

///|
fn parse_candidate_variant(source : String) -> CandidateVariant? {
  if source.has_prefix("[") {
    if !source.has_suffix("]") || source.length() < 3 {
      return None
    }
    let selector = decode_arbitrary(source[1:source.length() - 1].to_owned())
    if trim(selector) == "" || !is_valid_arbitrary(selector) {
      return None
    }
    let relative = selector.has_prefix(">") ||
      selector.has_prefix("+") ||
      selector.has_prefix("~")
    return Some(ArbitraryVariant(selector~, relative~, source~))
  }
  let (without_modifier, raw_modifier) = split_modifier(source)
  let modifier = match raw_modifier {
    Some(value) =>
      match parse_candidate_modifier(value) {
        Some(value) => Some(value)
        None => return None
      }
    None => None
  }
  let compound_roots = ["group", "peer", "has", "not", "in"]
  for root in compound_roots {
    let marker = "\{root}-"
    if without_modifier.has_prefix(marker) {
      let inner = without_modifier[marker.length():].to_owned()
      guard parse_candidate_variant(inner) is Some(variant) else { return None }
      let (modifier, variant) = if (
          root == "not" || root == "has" || root == "in"
        ) &&
        modifier is Some(forwarded) {
        match variant {
          CompoundVariant(
            root=inner_root,
            modifier=None,
            variant=inner_variant,
            source=inner_source
          ) =>
            (
              None,
              CompoundVariant(
                root=inner_root,
                modifier=Some(forwarded),
                variant=inner_variant,
                source=inner_source,
              ),
            )
          _ => (modifier, variant)
        }
      } else {
        (modifier, variant)
      }
      return Some(CompoundVariant(root~, modifier~, variant~, source~))
    }
  }
  let functional_roots = [
    "aria", "data", "nth", "nth-last", "supports", "min", "max",
  ]
  for root in functional_roots {
    let marker = "\{root}-"
    if without_modifier.has_prefix(marker) {
      let value = without_modifier[marker.length():].to_owned()
      if value == "" {
        return None
      }
      return Some(
        FunctionalVariant(root~, value=Some(value), modifier~, source~),
      )
    }
  }
  if without_modifier.has_prefix("@") {
    let root = if without_modifier.has_prefix("@min-") {
      "@min"
    } else if without_modifier.has_prefix("@max-") {
      "@max"
    } else {
      "@"
    }
    let value = if root == "@" {
      without_modifier[1:].to_owned()
    } else {
      without_modifier[root.length() + 1:].to_owned()
    }
    return Some(FunctionalVariant(root~, value=Some(value), modifier~, source~))
  }
  if modifier is Some(_) {
    return None
  }
  Some(StaticVariant(root=without_modifier, source~))
}

///|
fn CandidateVariant::source(self : CandidateVariant) -> String {
  match self {
    ArbitraryVariant(source~, ..)
    | StaticVariant(source~, ..)
    | FunctionalVariant(source~, ..)
    | CompoundVariant(source~, ..) => source
  }
}

///|
fn encode_arbitrary_for_candidate(value : String) -> String {
  let output = StringBuilder()
  for character in trim(value) {
    if character == ' ' {
      output.write_char('_')
    } else if character == '_' {
      output.write_string("\\_")
    } else {
      output.write_char(character)
    }
  }
  output.to_string()
}

///|
fn CandidateVariant::canonical(self : CandidateVariant) -> String {
  match self {
    ArbitraryVariant(selector~, ..) => {
      let selector = trim(selector)
      let simplified = if selector.has_prefix("&:is(") &&
        selector.has_suffix(")") {
        selector[5:selector.length() - 1].to_owned()
      } else {
        selector
      }
      "[\{encode_arbitrary_for_candidate(simplified)}]"
    }
    StaticVariant(root~, ..) => root
    FunctionalVariant(source~, ..) => source
    CompoundVariant(root~, modifier~, variant~, ..) => {
      let output = StringBuilder()
      output.write_string("\{root}-\{variant.canonical()}")
      match modifier {
        Some(modifier) => output.write_string("/\{modifier.source()}")
        None => ()
      }
      output.to_string()
    }
  }
}

///|
fn validate_arbitrary_candidate_base(base : String) -> Bool {
  if base.has_prefix("[") {
    if !base.has_suffix("]") || base.length() < 4 {
      return false
    }
    let inner = base[1:base.length() - 1].to_owned()
    guard inner.split_once(":") is Some((property, raw_value)) else {
      return false
    }
    if property == "" || raw_value == "" {
      return false
    }
    let first = property[0]
    if first != '-' && !(first >= 'a' && first <= 'z') {
      return false
    }
    let value = decode_arbitrary(raw_value.to_owned())
    return trim(value) != "" && is_valid_arbitrary(value)
  }
  let arbitrary_start = match base.find("-[") {
    Some(index) => Some((index, true))
    None =>
      match base.find("-(") {
        Some(index) => Some((index, false))
        None => None
      }
  }
  match arbitrary_start {
    None => true
    Some((index, brackets)) => {
      if index == 0 ||
        (brackets && base[base.length() - 1] != ']') ||
        (!brackets && base[base.length() - 1] != ')') {
        return false
      }
      let raw_value = base[index + 2:base.length() - 1].to_owned()
      if !brackets {
        let variable = match raw_value.split_once(":") {
          Some((_, value)) => value.to_owned()
          None => raw_value
        }
        if !variable.has_prefix("--") {
          return false
        }
      }
      let value = decode_arbitrary(raw_value)
      trim(value) != "" && is_valid_arbitrary(value)
    }
  }
}

///|
fn parse_candidate_form(
  base : String,
  modifier : CandidateModifier?,
) -> CandidateForm? {
  if base.has_prefix("[") {
    let inner = base[1:base.length() - 1].to_owned()
    guard inner.split_once(":") is Some((property, raw_value)) else {
      return None
    }
    return Some(
      ArbitraryPropertyCandidate(
        property=property.to_owned(),
        value=decode_arbitrary(raw_value.to_owned()),
        source=base,
      ),
    )
  }
  let arbitrary_index = match base.find("-[") {
    Some(index) => Some((index, true))
    None =>
      match base.find("-(") {
        Some(index) => Some((index, false))
        None => None
      }
  }
  match arbitrary_index {
    Some((index, brackets)) => {
      let root = base[:index].to_owned()
      let raw_value = base[index + 2:base.length() - 1].to_owned()
      let (data_type, value) = if !brackets {
        match raw_value.split_once(":") {
          Some((hint, variable)) =>
            (
              Some(hint.to_owned()),
              "var(\{decode_arbitrary(variable.to_owned())})",
            )
          None => (None, "var(\{decode_arbitrary(raw_value)})")
        }
      } else {
        let decoded = decode_arbitrary(raw_value)
        match decoded.split_once(":") {
          Some((hint, value)) if is_named_candidate_part(hint.to_owned()) =>
            (Some(hint.to_owned()), value.to_owned())
          _ => (None, decoded)
        }
      }
      Some(
        FunctionalCandidate(
          root~,
          value=Some(ArbitraryValue(data_type~, value~)),
          source=base,
        ),
      )
    }
    None =>
      if static_utility(base) is Some(_) && modifier is None {
        Some(StaticCandidate(root=base))
      } else {
        match base.rev_split_once("-") {
          Some((root, value)) if root != "" && value != "" => {
            let fraction = match modifier {
              Some(NamedModifier(value=modifier, ..)) =>
                Some("\{value}/\{modifier}")
              _ => None
            }
            Some(
              FunctionalCandidate(
                root=root.to_owned(),
                value=Some(NamedValue(value=value.to_owned(), fraction~)),
                source=base,
              ),
            )
          }
          _ => Some(FunctionalCandidate(root=base, value=None, source=base))
        }
      }
  }
}

///|
fn parse_candidate(raw : String) -> ParsedCandidate? {
  if raw == "" {
    return None
  }
  let parts = split_variants(raw)
  guard parts.pop() is Some(raw_base) && raw_base != "" else { return None }
  let important = raw_base.has_suffix("!") || raw_base.has_prefix("!")
  let without_important = if important {
    if raw_base.has_suffix("!") {
      raw_base[:raw_base.length() - 1].to_owned()
    } else {
      raw_base[1:].to_owned()
    }
  } else {
    raw_base
  }
  let negative = without_important.has_prefix("-")
  let positive = if negative {
    without_important[1:].to_owned()
  } else {
    without_important
  }
  let (base, modifier) = split_modifier(positive)
  let parsed_modifier = match modifier {
    Some(value) =>
      match parse_candidate_modifier(value) {
        Some(value) => Some(value)
        None => return None
      }
    None => None
  }
  if base == "" ||
    base.has_suffix("-") ||
    modifier == Some("") ||
    !validate_arbitrary_candidate_base(base) ||
    (negative && base.has_prefix("[")) ||
    parts.any(fn(variant) { variant == "" }) {
    return None
  }
  let variants : Array[CandidateVariant] = []
  for part in parts {
    guard parse_candidate_variant(part) is Some(variant) else { return None }
    variants.push(variant)
  }
  guard parse_candidate_form(base, parsed_modifier) is Some(form) else {
    return None
  }
  Some({ form, negative, important, modifier: parsed_modifier, variants, raw })
}

///|
fn ParsedCandidate::base(self : ParsedCandidate) -> String {
  match self.form {
    StaticCandidate(root~) => root
    FunctionalCandidate(source~, ..)
    | ArbitraryPropertyCandidate(source~, ..) => source
  }
}

///|
fn ParsedCandidate::modifier_source(self : ParsedCandidate) -> String? {
  self.modifier.map(fn(modifier) { modifier.source() })
}

///|
#warnings("-unused_value")
fn print_candidate(candidate : ParsedCandidate) -> String {
  let output = StringBuilder()
  for variant in candidate.variants {
    output.write_string(variant.canonical())
    output.write_char(':')
  }
  if candidate.negative {
    output.write_char('-')
  }
  output.write_string(candidate.base())
  match candidate.modifier {
    Some(modifier) => output.write_string("/\{modifier.source()}")
    None => ()
  }
  if candidate.important {
    output.write_char('!')
  }
  output.to_string()
}