// =============================================================================
// Selector Index for Fast CSS Matching
// =============================================================================
// Indexes rules by their rightmost selector (head) for O(1) candidate lookup

///|
/// Index for fast rule lookup
/// Rules are indexed by the rightmost compound selector's:
/// - ID selectors
/// - Class selectors
/// - Tag name
/// - Universal selectors (matched against all elements)
pub(all) struct SelectorIndex {
  /// Rules indexed by ID (rightmost ID in head)
  by_id : Map[String, Array[Int]]
  /// Rules indexed by class (rightmost class in head)
  by_class : Map[String, Array[Int]]
  /// Rules indexed by tag name (rightmost type in head)
  by_tag : Map[String, Array[Int]]
  /// Rules with universal selector (*) or no specific key
  universal : Array[Int]
}

///|
pub fn SelectorIndex::new() -> SelectorIndex {
  { by_id: {}, by_class: {}, by_tag: {}, universal: [] }
}

///|
/// Extract indexing key from a CompoundSelector's head
/// Returns (id, classes, tag) where any can be None/empty
fn extract_index_keys(
  selector : @selector.CompoundSelector,
) -> (String?, Array[String], String?) {
  let mut id : String? = None
  let classes : Array[String] = []
  let mut tag : String? = None
  // Check type selector
  match selector.type_selector {
    Some(@selector.Type(name)) => tag = Some(name.to_lower())
    Some(@selector.Universal) => () // Universal doesn't narrow down
    Some(_) => () // Other selector types (shouldn't be in type_selector position)
    None => ()
  }
  // Check subclasses
  for sub in selector.subclasses {
    match sub {
      @selector.Id(name) => if id is None { id = Some(name) }
      @selector.Class(name) => classes.push(name)
      _ => ()
    }
  }
  (id, classes, tag)
}

///|
/// Build index for a stylesheet
pub fn SelectorIndex::from_stylesheet(stylesheet : Stylesheet) -> SelectorIndex {
  let index = SelectorIndex::new()
  for i, rule in stylesheet.rules {
    // Extract keys from the head (rightmost compound selector)
    let (id, classes, tag) = extract_index_keys(rule.selector.head)
    // Index by the most specific key (ID > class > tag > universal)
    // We add to ALL matching indices for better recall
    let mut indexed = false
    // Add to ID index if present
    match id {
      Some(id_val) => {
        match index.by_id.get(id_val) {
          Some(arr) => arr.push(i)
          None => index.by_id.set(id_val, [i])
        }
        indexed = true
      }
      None => ()
    }
    // Add to class indices
    for cls in classes {
      match index.by_class.get(cls) {
        Some(arr) => arr.push(i)
        None => index.by_class.set(cls, [i])
      }
      indexed = true
    }
    // Add to tag index
    match tag {
      Some(tag_val) => {
        match index.by_tag.get(tag_val) {
          Some(arr) => arr.push(i)
          None => index.by_tag.set(tag_val, [i])
        }
        indexed = true
      }
      None => ()
    }
    // If no specific key, add to universal
    if not(indexed) {
      index.universal.push(i)
    }
  }
  index
}

///|
/// Get candidate rule indices for an element
/// Returns indices of rules that might match (need full selector check)
pub fn SelectorIndex::get_candidates(
  self : SelectorIndex,
  element : @selector.Element,
) -> Array[Int] {
  // Use a set to avoid duplicates
  let seen : Map[Int, Bool] = {}
  let candidates : Array[Int] = []
  // Check ID index
  match element.id {
    Some(id) =>
      match self.by_id.get(id) {
        Some(arr) =>
          for i in arr {
            if not(seen.contains(i)) {
              seen.set(i, true)
              candidates.push(i)
            }
          }
        None => ()
      }
    None => ()
  }
  // Check class indices
  for cls in element.classes {
    match self.by_class.get(cls) {
      Some(arr) =>
        for i in arr {
          if not(seen.contains(i)) {
            seen.set(i, true)
            candidates.push(i)
          }
        }
      None => ()
    }
  }
  // Check tag index
  let tag = element.tag_name.to_lower()
  match self.by_tag.get(tag) {
    Some(arr) =>
      for i in arr {
        if not(seen.contains(i)) {
          seen.set(i, true)
          candidates.push(i)
        }
      }
    None => ()
  }
  // Always include universal rules
  for i in self.universal {
    if not(seen.contains(i)) {
      seen.set(i, true)
      candidates.push(i)
    }
  }
  candidates
}

///|
/// Indexed stylesheet for fast matching
pub struct IndexedStylesheet {
  /// The underlying stylesheet
  stylesheet : Stylesheet
  /// Selector index for fast lookup
  index : SelectorIndex
}

///|
pub fn IndexedStylesheet::new(stylesheet : Stylesheet) -> IndexedStylesheet {
  let index = SelectorIndex::from_stylesheet(stylesheet)
  { stylesheet, index }
}

///|
/// Match all rules against an element using the index
pub fn IndexedStylesheet::match_element(
  self : IndexedStylesheet,
  element : @selector.Element,
) -> Array[RuleMatch] {
  self.match_element_with_media(element, None)
}

///|
/// Match all rules against an element with media query evaluation using the index
pub fn IndexedStylesheet::match_element_with_media(
  self : IndexedStylesheet,
  element : @selector.Element,
  media_env : @media.MediaEnvironment?,
) -> Array[RuleMatch] {
  let matches : Array[RuleMatch] = []
  // Get candidate rule indices from index
  let candidates = self.index.get_candidates(element)
  // Only check candidate rules (not all rules)
  for rule_idx in candidates {
    let rule = self.stylesheet.rules[rule_idx]
    // Check media query if present
    let media_matches = match (rule.media_query, media_env) {
      (Some(mq), Some(env)) => mq.evaluate(env)
      (Some(_), None) => true
      (None, _) => true
    }
    if not(media_matches) {
      continue
    }
    // Full selector match
    if @selector.matches_complex(element, rule.selector) {
      let specificity = @selector.complex_specificity(rule.selector)
      let decls : Array[Declaration] = []
      for decl in rule.declarations {
        decls.push({
          property: decl.property,
          value: decl.value,
          origin: self.stylesheet.origin,
          importance: decl.importance,
          specificity,
          source_order: rule.source_order * 10000 + decl.source_order,
        })
      }
      matches.push({
        specificity,
        declarations: decls,
        source_order: rule.source_order,
      })
    }
  }
  matches
}

///|
/// Cascade styles using indexed stylesheets for better performance
pub fn cascade_element_indexed(
  element : @selector.Element,
  stylesheets : Array[IndexedStylesheet],
  inline_style : Array[Declaration],
  media_env : @media.MediaEnvironment?,
) -> CascadedValues {
  let all_matches : Array[RuleMatch] = []
  for stylesheet in stylesheets {
    let matches = stylesheet.match_element_with_media(element, media_env)
    for m in matches {
      all_matches.push(m)
    }
  }
  let declarations = collect_declarations(all_matches, inline_style)
  cascade(declarations)
}