///|
/// CSS Cascade Algorithm
/// Resolves multiple declarations into final cascaded values

///|
/// Cascaded values for an element
/// Maps property names to their winning declaration
pub struct CascadedValues {
  /// Map from property name to winning declaration
  values : Map[String, Declaration]
}

///|
pub fn CascadedValues::new() -> CascadedValues {
  { values: {} }
}

///|
/// Get the cascaded value for a property
pub fn CascadedValues::get(
  self : CascadedValues,
  property : String,
) -> Declaration? {
  self.values.get(property)
}

///|
/// Get the raw value string for a property
pub fn CascadedValues::get_value(
  self : CascadedValues,
  property : String,
) -> String? {
  match self.values.get(property) {
    Some(decl) =>
      match decl.value {
        Value(v) => Some(v)
        _ => None
      }
    None => None
  }
}

///|
/// Check if a property has a cascaded value
pub fn CascadedValues::has(self : CascadedValues, property : String) -> Bool {
  self.values.contains(property)
}

///|
/// Get all property names that have cascaded values
pub fn CascadedValues::properties(self : CascadedValues) -> Array[String] {
  let result : Array[String] = []
  self.values.each(fn(k, _v) { result.push(k) })
  result
}

///|
/// Iterate cascaded declarations without allocating an intermediate key array.
pub fn CascadedValues::each(
  self : CascadedValues,
  f : (String, Declaration) -> Unit,
) -> Unit {
  self.values.each(f)
}

///|
/// Cascade a list of declarations into final values
/// Declarations should be for a single element
pub fn cascade(declarations : Array[Declaration]) -> CascadedValues {
  let result = CascadedValues::new()
  for decl in declarations {
    accumulate_cascaded_declaration(result, decl)
  }
  result
}

///|
fn accumulate_cascaded_declaration(
  result : CascadedValues,
  decl : Declaration,
) -> Unit {
  match result.values.get(decl.property) {
    Some(current) =>
      if compare_declarations(decl, current) > 0 {
        result.values.set(decl.property, decl)
      }
    None => result.values.set(decl.property, decl)
  }
}

///|
fn accumulate_inline_declaration(
  result : CascadedValues,
  decl : Declaration,
) -> Unit {
  accumulate_cascaded_declaration(result, {
    property: decl.property,
    value: decl.value,
    origin: Author,
    importance: decl.importance,
    specificity: { a: 1000, b: 0, c: 0 },
    source_order: decl.source_order,
  })
}

///|
/// Collect declarations from matching rules for an element
/// This is a helper to build the input for cascade()
pub(all) struct RuleMatch {
  /// The matched selector's specificity
  specificity : @selector.Specificity
  /// Declarations from the rule
  declarations : Array[Declaration]
  /// Source order of the rule
  source_order : Int
}

///|
/// A CSS rule: selector + declarations
pub struct CSSRule {
  /// Selector text (for debugging)
  selector_text : String
  /// Parsed selector
  selector : @selector.ComplexSelector
  /// Declarations in this rule
  declarations : Array[Declaration]
  /// Source order
  source_order : Int
  /// Media query (if this rule is inside @media)
  media_query : @media.MediaQueryList?
}

///|
/// A stylesheet: collection of rules
pub struct Stylesheet {
  /// Rules in source order
  rules : Array[CSSRule]
  /// Origin of this stylesheet
  origin : Origin
}

///|
pub fn Stylesheet::new(origin : Origin) -> Stylesheet {
  { rules: [], origin }
}

///|
/// Add a rule to the stylesheet
pub fn Stylesheet::add_rule(
  self : Stylesheet,
  selector_text : String,
  selector : @selector.ComplexSelector,
  declarations : Array[Declaration],
) -> Unit {
  let source_order = self.rules.length()
  let rule : CSSRule = {
    selector_text,
    selector,
    declarations,
    source_order,
    media_query: None,
  }
  self.rules.push(rule)
}

///|
/// Add a rule with a media query to the stylesheet
pub fn Stylesheet::add_rule_with_media(
  self : Stylesheet,
  selector_text : String,
  selector : @selector.ComplexSelector,
  declarations : Array[Declaration],
  media_query : @media.MediaQueryList,
) -> Unit {
  let source_order = self.rules.length()
  let rule : CSSRule = {
    selector_text,
    selector,
    declarations,
    source_order,
    media_query: Some(media_query),
  }
  self.rules.push(rule)
}

///|
/// Match all rules against an element and return matched rules
pub fn Stylesheet::match_element(
  self : Stylesheet,
  element : @selector.Element,
) -> Array[RuleMatch] {
  self.match_element_with_media(element, None)
}

///|
/// Match all rules against an element with media query evaluation
pub fn Stylesheet::match_element_with_media(
  self : Stylesheet,
  element : @selector.Element,
  media_env : @media.MediaEnvironment?,
) -> Array[RuleMatch] {
  let matches : Array[RuleMatch] = []
  for rule in self.rules {
    // Check media query if present and environment provided
    let media_matches = match (rule.media_query, media_env) {
      (Some(mq), Some(env)) => mq.evaluate(env)
      (Some(_), None) => true // No env = match all media queries
      (None, _) => true // No media query = always matches
    }
    if !media_matches {
      continue
    }
    if @selector.matches_complex(element, rule.selector) {
      let specificity = @selector.complex_specificity(rule.selector)
      // Update declarations with stylesheet origin
      // Combine rule source_order with declaration source_order to preserve
      // declaration order within rules (important for vendor prefix fallbacks like
      // "display: -webkit-flex; display: flex;")
      let decls : Array[Declaration] = []
      for decl in rule.declarations {
        decls.push({
          property: decl.property,
          value: decl.value,
          origin: self.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
}

///|
fn cascade_stylesheet_matches_into(
  result : CascadedValues,
  stylesheet : Stylesheet,
  element : @selector.Element,
  media_env : @media.MediaEnvironment?,
  source_order_offset : Int,
) -> Int {
  let mut max_source_order_in_sheet = source_order_offset - 1
  for rule in stylesheet.rules {
    let media_matches = match (rule.media_query, media_env) {
      (Some(mq), Some(env)) => mq.evaluate(env)
      (Some(_), None) => true
      (None, _) => true
    }
    if !media_matches {
      continue
    }
    if @selector.matches_complex(element, rule.selector) {
      let specificity = @selector.complex_specificity(rule.selector)
      for decl in rule.declarations {
        let adjusted_source_order = source_order_offset +
          rule.source_order * 10000 +
          decl.source_order
        if adjusted_source_order > max_source_order_in_sheet {
          max_source_order_in_sheet = adjusted_source_order
        }
        accumulate_cascaded_declaration(result, {
          property: decl.property,
          value: decl.value,
          origin: stylesheet.origin,
          importance: decl.importance,
          specificity,
          source_order: adjusted_source_order,
        })
      }
    }
  }
  max_source_order_in_sheet
}

///|
/// Cascade styles for an element from multiple stylesheets
pub fn cascade_element(
  element : @selector.Element,
  stylesheets : Array[Stylesheet],
  inline_style : Array[Declaration],
) -> CascadedValues {
  cascade_element_with_media(element, stylesheets, inline_style, None)
}

///|
/// Cascade styles for an element with media query evaluation
pub fn cascade_element_with_media(
  element : @selector.Element,
  stylesheets : Array[Stylesheet],
  inline_style : Array[Declaration],
  media_env : @media.MediaEnvironment?,
) -> CascadedValues {
  let result = CascadedValues::new()
  let mut source_order_offset = 0

  // Collect matches from all stylesheets
  for stylesheet in stylesheets {
    let max_source_order_in_sheet = cascade_stylesheet_matches_into(
      result, stylesheet, element, media_env, source_order_offset,
    )
    if max_source_order_in_sheet >= source_order_offset {
      source_order_offset = max_source_order_in_sheet + 1
    }
  }

  for decl in inline_style {
    accumulate_inline_declaration(result, decl)
  }
  result
}