/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::attr::{AttrSelectorOperation, NamespaceConstraint, ParsedAttrSelectorOperation};
use crate::bloom::{BloomFilter, BLOOM_HASH_MASK};
use crate::nth_index_cache::NthIndexCacheInner;
use crate::parser::{AncestorHashes, Combinator, Component, LocalName, NthType};
use crate::parser::{NonTSPseudoClass, Selector, SelectorImpl, SelectorIter, SelectorList};
use crate::tree::Element;
use smallvec::SmallVec;
use std::borrow::Borrow;
use std::iter;

pub use crate::context::*;

// The bloom filter for descendant CSS selectors will have a <1% false
// positive rate until it has this many selectors in it, then it will
// rapidly increase.
pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;

bitflags! {
    /// Set of flags that are set on either the element or its parent (depending
    /// on the flag) if the element could potentially match a selector.
    pub struct ElementSelectorFlags: usize {
        /// When a child is added or removed from the parent, all the children
        /// must be restyled, because they may match :nth-last-child,
        /// :last-of-type, :nth-last-of-type, or :only-of-type.
        const HAS_SLOW_SELECTOR = 1 << 0;

        /// When a child is added or removed from the parent, any later
        /// children must be restyled, because they may match :nth-child,
        /// :first-of-type, or :nth-of-type.
        const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1;

        /// When a child is added or removed from the parent, the first and
        /// last children must be restyled, because they may match :first-child,
        /// :last-child, or :only-child.
        const HAS_EDGE_CHILD_SELECTOR = 1 << 2;

        /// The element has an empty selector, so when a child is appended we
        /// might need to restyle the parent completely.
        const HAS_EMPTY_SELECTOR = 1 << 3;
    }
}

impl ElementSelectorFlags {
  /// Returns the subset of flags that apply to the element.
  pub fn for_self(self) -> ElementSelectorFlags {
    self & (ElementSelectorFlags::HAS_EMPTY_SELECTOR)
  }

  /// Returns the subset of flags that apply to the parent.
  pub fn for_parent(self) -> ElementSelectorFlags {
    self
      & (ElementSelectorFlags::HAS_SLOW_SELECTOR
        | ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
        | ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR)
  }
}

/// Holds per-compound-selector data.
struct LocalMatchingContext<'a, 'b, 'i, Impl: SelectorImpl<'i>> {
  shared: &'a mut MatchingContext<'b, 'i, Impl>,
  matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk,
}

#[inline(always)]
pub fn matches_selector_list<'i, E>(
  selector_list: &SelectorList<'i, E::Impl>,
  element: &E,
  context: &mut MatchingContext<'_, 'i, E::Impl>,
) -> bool
where
  E: Element<'i>,
{
  // This is pretty much any(..) but manually inlined because the compiler
  // refuses to do so from querySelector / querySelectorAll.
  for selector in &selector_list.0 {
    let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {});

    if matches {
      return true;
    }
  }

  false
}

#[inline(always)]
fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool {
  // Check the first three hashes. Note that we can check for zero before
  // masking off the high bits, since if any of the first three hashes is
  // zero the fourth will be as well. We also take care to avoid the
  // special-case complexity of the fourth hash until we actually reach it,
  // because we usually don't.
  //
  // To be clear: this is all extremely hot.
  for i in 0..3 {
    let packed = hashes.packed_hashes[i];
    if packed == 0 {
      // No more hashes left - unable to fast-reject.
      return true;
    }

    if !bf.might_contain_hash(packed & BLOOM_HASH_MASK) {
      // Hooray! We fast-rejected on this hash.
      return false;
    }
  }

  // Now do the slighty-more-complex work of synthesizing the fourth hash,
  // and check it against the filter if it exists.
  let fourth = hashes.fourth_hash();
  fourth == 0 || bf.might_contain_hash(fourth)
}

/// A result of selector matching, includes 3 failure types,
///
///   NotMatchedAndRestartFromClosestLaterSibling
///   NotMatchedAndRestartFromClosestDescendant
///   NotMatchedGlobally
///
/// When NotMatchedGlobally appears, stop selector matching completely since
/// the succeeding selectors never matches.
/// It is raised when
///   Child combinator cannot find the candidate element.
///   Descendant combinator cannot find the candidate element.
///
/// When NotMatchedAndRestartFromClosestDescendant appears, the selector
/// matching does backtracking and restarts from the closest Descendant
/// combinator.
/// It is raised when
///   NextSibling combinator cannot find the candidate element.
///   LaterSibling combinator cannot find the candidate element.
///   Child combinator doesn't match on the found element.
///
/// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector
/// matching does backtracking and restarts from the closest LaterSibling
/// combinator.
/// It is raised when
///   NextSibling combinator doesn't match on the found element.
///
/// For example, when the selector "d1 d2 a" is provided and we cannot *find*
/// an appropriate ancestor element for "d1", this selector matching raises
/// NotMatchedGlobally since even if "d2" is moved to more upper element, the
/// candidates for "d1" becomes less than before and d1 .
///
/// The next example is siblings. When the selector "b1 + b2 ~ d1 a" is
/// provided and we cannot *find* an appropriate brother element for b1,
/// the selector matching raises NotMatchedAndRestartFromClosestDescendant.
/// The selectors ("b1 + b2 ~") doesn't match and matching restart from "d1".
///
/// The additional example is child and sibling. When the selector
/// "b1 + c1 > b2 ~ d1 a" is provided and the selector "b1" doesn't match on
/// the element, this "b1" raises NotMatchedAndRestartFromClosestLaterSibling.
/// However since the selector "c1" raises
/// NotMatchedAndRestartFromClosestDescendant. So the selector
/// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1".
#[derive(Clone, Copy, Eq, PartialEq)]
enum SelectorMatchingResult {
  Matched,
  NotMatchedAndRestartFromClosestLaterSibling,
  NotMatchedAndRestartFromClosestDescendant,
  NotMatchedGlobally,
}

/// Whether the :hover and :active quirk applies.
///
/// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
#[derive(Clone, Copy, Debug, PartialEq)]
enum MatchesHoverAndActiveQuirk {
  Yes,
  No,
}

/// Matches a selector, fast-rejecting against a bloom filter.
///
/// We accept an offset to allow consumers to represent and match against
/// partial selectors (indexed from the right). We use this API design, rather
/// than having the callers pass a SelectorIter, because creating a SelectorIter
/// requires dereferencing the selector to get the length, which adds an
/// unnecessary cache miss for cases when we can fast-reject with AncestorHashes
/// (which the caller can store inline with the selector pointer).
#[inline(always)]
pub fn matches_selector<'i, E, F>(
  selector: &Selector<'i, E::Impl>,
  offset: usize,
  hashes: Option<&AncestorHashes>,
  element: &E,
  context: &mut MatchingContext<'_, 'i, E::Impl>,
  flags_setter: &mut F,
) -> bool
where
  E: Element<'i>,
  F: FnMut(&E, ElementSelectorFlags),
{
  // Use the bloom filter to fast-reject.
  if let Some(hashes) = hashes {
    if let Some(filter) = context.bloom_filter {
      if !may_match(hashes, filter) {
        return false;
      }
    }
  }

  matches_complex_selector(selector.iter_from(offset), element, context, flags_setter)
}

/// Whether a compound selector matched, and whether it was the rightmost
/// selector inside the complex selector.
pub enum CompoundSelectorMatchingResult {
  /// The selector was fully matched.
  FullyMatched,
  /// The compound selector matched, and the next combinator offset is
  /// `next_combinator_offset`.
  Matched { next_combinator_offset: usize },
  /// The selector didn't match.
  NotMatched,
}

/// Matches a compound selector belonging to `selector`, starting at offset
/// `from_offset`, matching left to right.
///
/// Requires that `from_offset` points to a `Combinator`.
///
/// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the
/// complex selector, but it happens to be the case we don't need it.
pub fn matches_compound_selector_from<'i, E>(
  selector: &Selector<'i, E::Impl>,
  mut from_offset: usize,
  context: &mut MatchingContext<'_, 'i, E::Impl>,
  element: &E,
) -> CompoundSelectorMatchingResult
where
  E: Element<'i>,
{
  if cfg!(debug_assertions) && from_offset != 0 {
    selector.combinator_at_parse_order(from_offset - 1); // This asserts.
  }

  let mut local_context = LocalMatchingContext {
    shared: context,
    matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk::No,
  };

  // Find the end of the selector or the next combinator, then match
  // backwards, so that we match in the same order as
  // matches_complex_selector, which is usually faster.
  let start_offset = from_offset;
  for component in selector.iter_raw_parse_order_from(from_offset) {
    if matches!(*component, Component::Combinator(..)) {
      debug_assert_ne!(from_offset, 0, "Selector started with a combinator?");
      break;
    }

    from_offset += 1;
  }

  debug_assert!(from_offset >= 1);
  debug_assert!(from_offset <= selector.len());

  let iter = selector.iter_from(selector.len() - from_offset);
  debug_assert!(
    iter.clone().next().is_some()
      || (from_offset != selector.len()
        && matches!(
          selector.combinator_at_parse_order(from_offset),
          Combinator::SlotAssignment | Combinator::PseudoElement
        )),
    "Got the math wrong: {:?} | {:?} | {} {}",
    selector,
    selector.iter_raw_match_order().as_slice(),
    from_offset,
    start_offset
  );

  for component in iter {
    if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) {
      return CompoundSelectorMatchingResult::NotMatched;
    }
  }

  if from_offset != selector.len() {
    return CompoundSelectorMatchingResult::Matched {
      next_combinator_offset: from_offset,
    };
  }

  CompoundSelectorMatchingResult::FullyMatched
}

/// Matches a complex selector.
#[inline(always)]
pub fn matches_complex_selector<'i, E, F>(
  mut iter: SelectorIter<'_, 'i, E::Impl>,
  element: &E,
  context: &mut MatchingContext<'_, 'i, E::Impl>,
  flags_setter: &mut F,
) -> bool
where
  E: Element<'i>,
  F: FnMut(&E, ElementSelectorFlags),
{
  // If this is the special pseudo-element mode, consume the ::pseudo-element
  // before proceeding, since the caller has already handled that part.
  if context.matching_mode() == MatchingMode::ForStatelessPseudoElement && !context.is_nested() {
    // Consume the pseudo.
    match *iter.next().unwrap() {
      Component::PseudoElement(ref pseudo) => {
        if let Some(ref f) = context.pseudo_element_matching_fn {
          if !f(pseudo) {
            return false;
          }
        }
      }
      _ => {
        debug_assert!(
          false,
          "Used MatchingMode::ForStatelessPseudoElement \
                     in a non-pseudo selector"
        );
      }
    }

    if !iter.matches_for_stateless_pseudo_element() {
      return false;
    }

    // Advance to the non-pseudo-element part of the selector.
    let next_sequence = iter.next_sequence().unwrap();
    debug_assert_eq!(next_sequence, Combinator::PseudoElement);
  }

  let result = matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes);

  matches!(result, SelectorMatchingResult::Matched)
}

#[inline]
fn matches_hover_and_active_quirk<'i, Impl: SelectorImpl<'i>>(
  selector_iter: &SelectorIter<'_, 'i, Impl>,
  context: &MatchingContext<'_, 'i, Impl>,
  rightmost: Rightmost,
) -> MatchesHoverAndActiveQuirk {
  if context.quirks_mode() != QuirksMode::Quirks {
    return MatchesHoverAndActiveQuirk::No;
  }

  if context.is_nested() {
    return MatchesHoverAndActiveQuirk::No;
  }

  // This compound selector had a pseudo-element to the right that we
  // intentionally skipped.
  if rightmost == Rightmost::Yes && context.matching_mode() == MatchingMode::ForStatelessPseudoElement {
    return MatchesHoverAndActiveQuirk::No;
  }

  let all_match = selector_iter.clone().all(|simple| match *simple {
    Component::LocalName(_)
    | Component::AttributeInNoNamespaceExists { .. }
    | Component::AttributeInNoNamespace { .. }
    | Component::AttributeOther(_)
    | Component::ID(_)
    | Component::Class(_)
    | Component::PseudoElement(_)
    | Component::Negation(_)
    | Component::Nth(_)
    | Component::NthOf(..)
    | Component::Empty => false,
    Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(),
    _ => true,
  });

  if all_match {
    MatchesHoverAndActiveQuirk::Yes
  } else {
    MatchesHoverAndActiveQuirk::No
  }
}

#[derive(Clone, Copy, PartialEq)]
enum Rightmost {
  Yes,
  No,
}

#[inline(always)]
fn next_element_for_combinator<'i, E>(
  element: &E,
  combinator: Combinator,
  selector: &SelectorIter<'_, 'i, E::Impl>,
  context: &MatchingContext<'_, 'i, E::Impl>,
) -> Option<E>
where
  E: Element<'i>,
{
  match combinator {
    Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(),
    Combinator::Child | Combinator::Descendant | Combinator::Deep | Combinator::DeepDescendant => {
      match element.parent_element() {
        Some(e) => return Some(e),
        None => {}
      }

      if !element.parent_node_is_shadow_root() {
        return None;
      }

      // https://drafts.csswg.org/css-scoping/#host-element-in-tree:
      //
      //   For the purpose of Selectors, a shadow host also appears in
      //   its shadow tree, with the contents of the shadow tree treated
      //   as its children. (In other words, the shadow host is treated as
      //   replacing the shadow root node.)
      //
      // and also:
      //
      //   When considered within its own shadow trees, the shadow host is
      //   featureless. Only the :host, :host(), and :host-context()
      //   pseudo-classes are allowed to match it.
      //
      // Since we know that the parent is a shadow root, we necessarily
      // are in a shadow tree of the host, and the next selector will only
      // match if the selector is a featureless :host selector.
      if !selector.clone().is_featureless_host_selector() {
        return None;
      }

      element.containing_shadow_host()
    }
    Combinator::Part => element.containing_shadow_host(),
    Combinator::SlotAssignment => {
      debug_assert!(element.assigned_slot().map_or(true, |s| s.is_html_slot_element()));
      let scope = context.current_host?;
      let mut current_slot = element.assigned_slot()?;
      while current_slot.containing_shadow_host().unwrap().opaque() != scope {
        current_slot = current_slot.assigned_slot()?;
      }
      Some(current_slot)
    }
    Combinator::PseudoElement => element.pseudo_element_originating_element(),
  }
}

fn matches_complex_selector_internal<'i, E, F>(
  mut selector_iter: SelectorIter<'_, 'i, E::Impl>,
  element: &E,
  context: &mut MatchingContext<'_, 'i, E::Impl>,
  flags_setter: &mut F,
  rightmost: Rightmost,
) -> SelectorMatchingResult
where
  E: Element<'i>,
  F: FnMut(&E, ElementSelectorFlags),
{
  debug!("Matching complex selector {:?} for {:?}", selector_iter, element);

  let matches_compound_selector =
    matches_compound_selector(&mut selector_iter, element, context, flags_setter, rightmost);

  let combinator = selector_iter.next_sequence();
  if combinator.map_or(false, |c| c.is_sibling()) {
    flags_setter(element, ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS);
  }

  if !matches_compound_selector {
    return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling;
  }

  let combinator = match combinator {
    None => return SelectorMatchingResult::Matched,
    Some(c) => c,
  };

  let candidate_not_found = match combinator {
    Combinator::NextSibling | Combinator::LaterSibling => {
      SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant
    }
    Combinator::Child
    | Combinator::Deep
    | Combinator::DeepDescendant
    | Combinator::Descendant
    | Combinator::SlotAssignment
    | Combinator::Part
    | Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally,
  };

  let mut next_element = next_element_for_combinator(element, combinator, &selector_iter, &context);

  // Stop matching :visited as soon as we find a link, or a combinator for
  // something that isn't an ancestor.
  let mut visited_handling = if element.is_link() || combinator.is_sibling() {
    VisitedHandlingMode::AllLinksUnvisited
  } else {
    context.visited_handling()
  };

  loop {
    let element = match next_element {
      None => return candidate_not_found,
      Some(next_element) => next_element,
    };

    let result = context.with_visited_handling_mode(visited_handling, |context| {
      matches_complex_selector_internal(selector_iter.clone(), &element, context, flags_setter, Rightmost::No)
    });

    match (result, combinator) {
      // Return the status immediately.
      (SelectorMatchingResult::Matched, _)
      | (SelectorMatchingResult::NotMatchedGlobally, _)
      | (_, Combinator::NextSibling) => {
        return result;
      }

      // Upgrade the failure status to
      // NotMatchedAndRestartFromClosestDescendant.
      (_, Combinator::PseudoElement) | (_, Combinator::Child) => {
        return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant;
      }

      // If the failure status is
      // NotMatchedAndRestartFromClosestDescendant and combinator is
      // Combinator::LaterSibling, give up this Combinator::LaterSibling
      // matching and restart from the closest descendant combinator.
      (SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, Combinator::LaterSibling) => {
        return result;
      }

      // The Combinator::Descendant combinator and the status is
      // NotMatchedAndRestartFromClosestLaterSibling or
      // NotMatchedAndRestartFromClosestDescendant, or the
      // Combinator::LaterSibling combinator and the status is
      // NotMatchedAndRestartFromClosestDescendant, we can continue to
      // matching on the next candidate element.
      _ => {}
    }

    if element.is_link() {
      visited_handling = VisitedHandlingMode::AllLinksUnvisited;
    }

    next_element = next_element_for_combinator(&element, combinator, &selector_iter, &context);
  }
}

#[inline]
fn matches_local_name<'i, E>(element: &E, local_name: &LocalName<'i, E::Impl>) -> bool
where
  E: Element<'i>,
{
  let name = select_name(
    element.is_html_element_in_html_document(),
    &local_name.name,
    &local_name.lower_name,
  )
  .borrow();
  element.has_local_name(name)
}

/// Determines whether the given element matches the given compound selector.
#[inline]
fn matches_compound_selector<'i, E, F>(
  selector_iter: &mut SelectorIter<'_, 'i, E::Impl>,
  element: &E,
  context: &mut MatchingContext<'_, 'i, E::Impl>,
  flags_setter: &mut F,
  rightmost: Rightmost,
) -> bool
where
  E: Element<'i>,
  F: FnMut(&E, ElementSelectorFlags),
{
  let matches_hover_and_active_quirk = matches_hover_and_active_quirk(&selector_iter, context, rightmost);

  // Handle some common cases first.
  // We may want to get rid of this at some point if we can make the
  // generic case fast enough.
  let mut selector = selector_iter.next();
  if let Some(&Component::LocalName(ref local_name)) = selector {
    if !matches_local_name(element, local_name) {
      return false;
    }
    selector = selector_iter.next();
  }
  let class_and_id_case_sensitivity = context.classes_and_ids_case_sensitivity();
  if let Some(&Component::ID(ref id)) = selector {
    if !element.has_id(id, class_and_id_case_sensitivity) {
      return false;
    }
    selector = selector_iter.next();
  }
  while let Some(&Component::Class(ref class)) = selector {
    if !element.has_class(class, class_and_id_case_sensitivity) {
      return false;
    }
    selector = selector_iter.next();
  }
  let selector = match selector {
    Some(s) => s,
    None => return true,
  };

  let mut local_context = LocalMatchingContext {
    shared: context,
    matches_hover_and_active_quirk,
  };
  iter::once(selector)
    .chain(selector_iter)
    .all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter))
}

/// Determines whether the given element matches the given single selector.
fn matches_simple_selector<'i, E, F>(
  selector: &Component<'i, E::Impl>,
  element: &E,
  context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>,
  flags_setter: &mut F,
) -> bool
where
  E: Element<'i>,
  F: FnMut(&E, ElementSelectorFlags),
{
  debug_assert!(context.shared.is_nested() || !context.shared.in_negation());

  match *selector {
    Component::Combinator(_) => unreachable!(),
    Component::Part(ref parts) => {
      let mut hosts = SmallVec::<[E; 4]>::new();

      let mut host = match element.containing_shadow_host() {
        Some(h) => h,
        None => return false,
      };

      let current_host = context.shared.current_host;
      if current_host != Some(host.opaque()) {
        loop {
          let outer_host = host.containing_shadow_host();
          if outer_host.as_ref().map(|h| h.opaque()) == current_host {
            break;
          }
          let outer_host = match outer_host {
            Some(h) => h,
            None => return false,
          };
          // TODO(emilio): if worth it, we could early return if
          // host doesn't have the exportparts attribute.
          hosts.push(host);
          host = outer_host;
        }
      }

      // Translate the part into the right scope.
      parts.iter().all(|part| {
        let mut part = part.clone();
        for host in hosts.iter().rev() {
          part = match host.imported_part(&part) {
            Some(p) => p,
            None => return false,
          };
        }
        element.is_part(&part)
      })
    }
    Component::Slotted(ref selector) => {
      // <slots> are never flattened tree slottables.
      !element.is_html_slot_element()
        && context
          .shared
          .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter))
    }
    Component::PseudoElement(ref pseudo) => element.match_pseudo_element(pseudo, context.shared),
    Component::LocalName(ref local_name) => matches_local_name(element, local_name),
    Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => true,
    Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => {
      element.has_namespace(&url.borrow())
    }
    Component::ExplicitNoNamespace => {
      let ns = crate::parser::namespace_empty_string::<E::Impl>();
      element.has_namespace(&ns.borrow())
    }
    Component::ID(ref id) => element.has_id(id, context.shared.classes_and_ids_case_sensitivity()),
    Component::Class(ref class) => element.has_class(class, context.shared.classes_and_ids_case_sensitivity()),
    Component::AttributeInNoNamespaceExists {
      ref local_name,
      ref local_name_lower,
    } => {
      let is_html = element.is_html_element_in_html_document();
      element.attr_matches(
        &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<E::Impl>()),
        select_name(is_html, local_name, local_name_lower),
        &AttrSelectorOperation::Exists,
      )
    }
    Component::AttributeInNoNamespace {
      ref local_name,
      ref value,
      operator,
      case_sensitivity,
      never_matches,
    } => {
      if never_matches {
        return false;
      }
      let is_html = element.is_html_element_in_html_document();
      element.attr_matches(
        &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<E::Impl>()),
        local_name,
        &AttrSelectorOperation::WithValue {
          operator,
          case_sensitivity: case_sensitivity.to_unconditional(is_html),
          expected_value: value,
        },
      )
    }
    Component::AttributeOther(ref attr_sel) => {
      if attr_sel.never_matches {
        return false;
      }
      let is_html = element.is_html_element_in_html_document();
      let empty_string;
      let namespace = match attr_sel.namespace() {
        Some(ns) => ns,
        None => {
          empty_string = crate::parser::namespace_empty_string::<E::Impl>();
          NamespaceConstraint::Specific(&empty_string)
        }
      };
      element.attr_matches(
        &namespace,
        select_name(is_html, &attr_sel.local_name, &attr_sel.local_name_lower),
        &match attr_sel.operation {
          ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists,
          ParsedAttrSelectorOperation::WithValue {
            operator,
            case_sensitivity,
            ref expected_value,
          } => AttrSelectorOperation::WithValue {
            operator,
            case_sensitivity: case_sensitivity.to_unconditional(is_html),
            expected_value,
          },
        },
      )
    }
    Component::NonTSPseudoClass(ref pc) => {
      if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes
        && !context.shared.is_nested()
        && pc.is_active_or_hover()
        && !element.is_link()
      {
        return false;
      }

      element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter)
    }
    Component::Root => element.is_root(),
    Component::Empty => {
      flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
      element.is_empty()
    }
    Component::Host(ref selector) => {
      context.shared.shadow_host().map_or(false, |host| host == element.opaque())
        && selector.as_ref().map_or(true, |selector| {
          context
            .shared
            .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter))
        })
    }
    Component::Scope => match context.shared.scope_element {
      Some(ref scope_element) => element.opaque() == *scope_element,
      None => element.is_root(),
    },
    Component::Nth(data) => match data.ty {
      NthType::Child => match (data.a, data.b) {
        (0, 1) => matches_first_child(element, flags_setter),
        (a, b) => matches_generic_nth_child(element, context, a, b, false, false, flags_setter),
      },
      NthType::LastChild => match (data.a, data.b) {
        (0, 1) => matches_last_child(element, flags_setter),
        (a, b) => matches_generic_nth_child(element, context, a, b, false, true, flags_setter),
      },
      NthType::OnlyChild => {
        matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter)
      }
      NthType::OfType => matches_generic_nth_child(element, context, data.a, data.b, true, false, flags_setter),
      NthType::LastOfType => matches_generic_nth_child(element, context, data.a, data.b, true, true, flags_setter),
      NthType::OnlyOfType => {
        matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter)
          && matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter)
      }
      _ => todo!(),
    },
    Component::NthOf(..) => todo!(),
    Component::Is(ref list) | Component::Where(ref list) | Component::Any(_, ref list) => {
      context.shared.nest(|context| {
        for selector in &**list {
          if matches_complex_selector(selector.iter(), element, context, flags_setter) {
            return true;
          }
        }
        false
      })
    }
    Component::Negation(ref list) => context.shared.nest_for_negation(|context| {
      for selector in &**list {
        if matches_complex_selector(selector.iter(), element, context, flags_setter) {
          return false;
        }
      }
      true
    }),
    Component::Nesting | Component::Has(..) => unreachable!(),
  }
}

#[inline(always)]
fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T {
  if is_html {
    local_name_lower
  } else {
    local_name
  }
}

#[inline]
fn matches_generic_nth_child<'i, E, F>(
  element: &E,
  context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>,
  a: i32,
  b: i32,
  is_of_type: bool,
  is_from_end: bool,
  flags_setter: &mut F,
) -> bool
where
  E: Element<'i>,
  F: FnMut(&E, ElementSelectorFlags),
{
  if element.ignores_nth_child_selectors() {
    return false;
  }

  flags_setter(
    element,
    if is_from_end {
      ElementSelectorFlags::HAS_SLOW_SELECTOR
    } else {
      ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
    },
  );

  // Grab a reference to the appropriate cache.
  let mut cache = context.shared.nth_index_cache.as_mut().map(|c| c.get(is_of_type, is_from_end));

  // Lookup or compute the index.
  let index = if let Some(i) = cache.as_mut().and_then(|c| c.lookup(element.opaque())) {
    i
  } else {
    let i = nth_child_index(element, is_of_type, is_from_end, cache.as_deref_mut());
    if let Some(c) = cache.as_mut() {
      c.insert(element.opaque(), i)
    }
    i
  };
  debug_assert_eq!(
    index,
    nth_child_index(element, is_of_type, is_from_end, None),
    "invalid cache"
  );

  // Is there a non-negative integer n such that An+B=index?
  match index.checked_sub(b) {
    None => false,
    Some(an) => match an.checked_div(a) {
            Some(n) => n >= 0 && a * n == an,
            None /* a == 0 */ => an == 0,
        },
  }
}

#[inline]
fn nth_child_index<'i, E>(
  element: &E,
  is_of_type: bool,
  is_from_end: bool,
  mut cache: Option<&mut NthIndexCacheInner>,
) -> i32
where
  E: Element<'i>,
{
  // The traversal mostly processes siblings left to right. So when we walk
  // siblings to the right when computing NthLast/NthLastOfType we're unlikely
  // to get cache hits along the way. As such, we take the hit of walking the
  // siblings to the left checking the cache in the is_from_end case (this
  // matches what Gecko does). The indices-from-the-left is handled during the
  // regular look further below.
  if let Some(ref mut c) = cache {
    if is_from_end && !c.is_empty() {
      let mut index: i32 = 1;
      let mut curr = element.clone();
      while let Some(e) = curr.prev_sibling_element() {
        curr = e;
        if !is_of_type || element.is_same_type(&curr) {
          if let Some(i) = c.lookup(curr.opaque()) {
            return i - index;
          }
          index += 1;
        }
      }
    }
  }

  let mut index: i32 = 1;
  let mut curr = element.clone();
  let next = |e: E| {
    if is_from_end {
      e.next_sibling_element()
    } else {
      e.prev_sibling_element()
    }
  };
  while let Some(e) = next(curr) {
    curr = e;
    if !is_of_type || element.is_same_type(&curr) {
      // If we're computing indices from the left, check each element in the
      // cache. We handle the indices-from-the-right case at the top of this
      // function.
      if !is_from_end {
        if let Some(i) = cache.as_mut().and_then(|c| c.lookup(curr.opaque())) {
          return i + index;
        }
      }
      index += 1;
    }
  }

  index
}

#[inline]
fn matches_first_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool
where
  E: Element<'i>,
  F: FnMut(&E, ElementSelectorFlags),
{
  flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
  element.prev_sibling_element().is_none()
}

#[inline]
fn matches_last_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool
where
  E: Element<'i>,
  F: FnMut(&E, ElementSelectorFlags),
{
  flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
  element.next_sibling_element().is_none()
}
