///|
/// CSS Selector types based on Selectors Level 4

///|
/// Attribute matching mode
pub enum AttributeMatch {
  /// [attr] - has attribute
  Exists
  /// [attr=value] - exact match
  Exact(String)
  /// [attr~=value] - whitespace-separated list contains value
  Includes(String)
  /// [attr|=value] - equals or starts with value followed by hyphen
  DashMatch(String)
  /// [attr^=value] - starts with
  Prefix(String)
  /// [attr$=value] - ends with
  Suffix(String)
  /// [attr*=value] - contains
  Substring(String)
}

///|
pub impl Show for AttributeMatch with fn output(self, logger) {
  match self {
    Exists => logger.write_string("Exists")
    Exact(v) => {
      logger.write_string("Exact(")
      logger.write_string(v)
      logger.write_string(")")
    }
    Includes(v) => {
      logger.write_string("Includes(")
      logger.write_string(v)
      logger.write_string(")")
    }
    DashMatch(v) => {
      logger.write_string("DashMatch(")
      logger.write_string(v)
      logger.write_string(")")
    }
    Prefix(v) => {
      logger.write_string("Prefix(")
      logger.write_string(v)
      logger.write_string(")")
    }
    Suffix(v) => {
      logger.write_string("Suffix(")
      logger.write_string(v)
      logger.write_string(")")
    }
    Substring(v) => {
      logger.write_string("Substring(")
      logger.write_string(v)
      logger.write_string(")")
    }
  }
}

///|
/// Attribute selector
pub struct AttributeSelector {
  name : String
  match_type : AttributeMatch
  /// Case-insensitive flag [attr=value i]
  case_insensitive : Bool
  /// True when written with `*|` (any-namespace) prefix, e.g.
  /// `[*|att]`. The implicit (`[att]`) and `[|att]` (explicit
  /// no-namespace) forms both leave this false — they share the same
  /// canonical serialization.
  any_namespace : Bool
}

///|
pub impl Show for AttributeSelector with fn output(self, logger) {
  logger.write_string("[")
  if self.any_namespace {
    logger.write_string("*|")
  }
  logger.write_string(self.name)
  match self.match_type {
    Exists => ()
    _ => {
      logger.write_string(" ")
      self.match_type.output(logger)
    }
  }
  if self.case_insensitive {
    logger.write_string(" i")
  }
  logger.write_string("]")
}

///|
/// Nth expression: An+B
pub struct NthExpr {
  a : Int // coefficient
  b : Int // offset
}

///|
pub impl Show for NthExpr with fn output(self, logger) {
  if self.a == 0 {
    logger.write_string(self.b.to_string())
  } else if self.a == 1 {
    logger.write_string("n")
    if self.b > 0 {
      logger.write_string("+")
      logger.write_string(self.b.to_string())
    } else if self.b < 0 {
      logger.write_string(self.b.to_string())
    }
  } else if self.a == -1 {
    logger.write_string("-n")
    if self.b > 0 {
      logger.write_string("+")
      logger.write_string(self.b.to_string())
    } else if self.b < 0 {
      logger.write_string(self.b.to_string())
    }
  } else {
    logger.write_string(self.a.to_string())
    logger.write_string("n")
    if self.b > 0 {
      logger.write_string("+")
      logger.write_string(self.b.to_string())
    } else if self.b < 0 {
      logger.write_string(self.b.to_string())
    }
  }
}

///|
/// Pseudo-class selector
pub enum PseudoClass {
  // Tree-structural
  FirstChild
  LastChild
  OnlyChild
  NthChild(NthExpr)
  NthLastChild(NthExpr)
  FirstOfType
  LastOfType
  OnlyOfType
  NthOfType(NthExpr)
  NthLastOfType(NthExpr)
  Root
  Empty
  // Negation
  // CSS Selectors 4: `` argument.
  Not(Array[ComplexSelector])
  // Matches-any
  Is(Array[ComplexSelector])
  Where(Array[ComplexSelector])
  Has(Array[RelativeSelector])
  State(String)
  /// `:host` — Shadow DOM host element with no further constraints.
  /// Spec: https://drafts.csswg.org/css-scoping-1/#host-selector
  Host
  /// `:host()` — Shadow DOM host matching a selector.
  HostFunc(CompoundSelector)
  /// `:host-context()` — Host that has a matching ancestor.
  HostContextFunc(CompoundSelector)
  /// `:heading` — any HTML heading element (h1-h6).
  /// `:heading(N, ...)` — headings of the specified levels.
  /// Spec: CSS Selectors 4 (draft).
  Heading(Array[Int])
  /// `:dir(ltr|rtl)` — directionality. Spec: CSS Selectors 4.
  Dir(String)
  /// `:lang(+)` — language-tag match. Spec: Selectors 4.
  Lang(Array[String])
  // User-action
  Hover
  Active
  Focus
  FocusVisible
  FocusWithin
  // Link
  Link
  Visited
  AnyLink
  // Input
  Enabled
  Disabled
  Checked
  Indeterminate
  Required
  Optional
  Valid
  Invalid
  ReadOnly
  ReadWrite
}

///|
pub impl Show for PseudoClass with fn output(self, logger) {
  match self {
    FirstChild => logger.write_string(":first-child")
    LastChild => logger.write_string(":last-child")
    OnlyChild => logger.write_string(":only-child")
    NthChild(expr) => {
      logger.write_string(":nth-child(")
      expr.output(logger)
      logger.write_string(")")
    }
    NthLastChild(expr) => {
      logger.write_string(":nth-last-child(")
      expr.output(logger)
      logger.write_string(")")
    }
    FirstOfType => logger.write_string(":first-of-type")
    LastOfType => logger.write_string(":last-of-type")
    OnlyOfType => logger.write_string(":only-of-type")
    NthOfType(expr) => {
      logger.write_string(":nth-of-type(")
      expr.output(logger)
      logger.write_string(")")
    }
    NthLastOfType(expr) => {
      logger.write_string(":nth-last-of-type(")
      expr.output(logger)
      logger.write_string(")")
    }
    Root => logger.write_string(":root")
    Empty => logger.write_string(":empty")
    Not(_) => logger.write_string(":not(...)")
    Is(_) => logger.write_string(":is(...)")
    Where(_) => logger.write_string(":where(...)")
    Has(_) => logger.write_string(":has(...)")
    State(state) => {
      logger.write_string(":state(")
      logger.write_string(state)
      logger.write_string(")")
    }
    Host => logger.write_string(":host")
    HostFunc(_) => logger.write_string(":host(...)")
    HostContextFunc(_) => logger.write_string(":host-context(...)")
    Heading(levels) =>
      if levels.is_empty() {
        logger.write_string(":heading")
      } else {
        logger.write_string(":heading(")
        for i, n in levels {
          if i > 0 {
            logger.write_string(", ")
          }
          logger.write_string(n.to_string())
        }
        logger.write_string(")")
      }
    Dir(dir) => {
      logger.write_string(":dir(")
      logger.write_string(dir)
      logger.write_string(")")
    }
    Lang(tags) => {
      logger.write_string(":lang(")
      for i, t in tags {
        if i > 0 {
          logger.write_string(", ")
        }
        logger.write_string(t)
      }
      logger.write_string(")")
    }
    Hover => logger.write_string(":hover")
    Active => logger.write_string(":active")
    Focus => logger.write_string(":focus")
    FocusVisible => logger.write_string(":focus-visible")
    FocusWithin => logger.write_string(":focus-within")
    Link => logger.write_string(":link")
    Visited => logger.write_string(":visited")
    AnyLink => logger.write_string(":any-link")
    Enabled => logger.write_string(":enabled")
    Disabled => logger.write_string(":disabled")
    Checked => logger.write_string(":checked")
    Indeterminate => logger.write_string(":indeterminate")
    Required => logger.write_string(":required")
    Optional => logger.write_string(":optional")
    Valid => logger.write_string(":valid")
    Invalid => logger.write_string(":invalid")
    ReadOnly => logger.write_string(":read-only")
    ReadWrite => logger.write_string(":read-write")
  }
}

///|
/// Pseudo-element selector
pub enum PseudoElement {
  Before
  After
  FirstLine
  FirstLetter
  Marker
  Placeholder
  Selection
  Backdrop
  FileSelectorButton
  PlaceholderShown
  TargetText
  SpellingError
  GrammarError
  Cue
  /// `::part(+)` — Shadow DOM exposed-part targeting.
  /// Spec: https://drafts.csswg.org/css-shadow-parts-1/#part
  Part(Array[String])
  /// `::slotted()` — Shadow DOM slot targeting.
  /// Spec: https://drafts.csswg.org/css-scoping-1/#slotted-pseudo
  Slotted(Array[CompoundSelector])
}

///|
pub impl Show for PseudoElement with fn output(self, logger) {
  match self {
    Before => logger.write_string("::before")
    After => logger.write_string("::after")
    FirstLine => logger.write_string("::first-line")
    FirstLetter => logger.write_string("::first-letter")
    Marker => logger.write_string("::marker")
    Placeholder => logger.write_string("::placeholder")
    Selection => logger.write_string("::selection")
    Backdrop => logger.write_string("::backdrop")
    FileSelectorButton => logger.write_string("::file-selector-button")
    PlaceholderShown => logger.write_string("::placeholder-shown")
    TargetText => logger.write_string("::target-text")
    SpellingError => logger.write_string("::spelling-error")
    GrammarError => logger.write_string("::grammar-error")
    Cue => logger.write_string("::cue")
    Part(names) => {
      logger.write_string("::part(")
      for i, name in names {
        if i > 0 {
          logger.write_string(" ")
        }
        logger.write_string(name)
      }
      logger.write_string(")")
    }
    Slotted(_) => logger.write_string("::slotted(...)")
  }
}

///|
/// Simple selector - a single component
pub enum SimpleSelector {
  /// Type selector: div, span
  Type(String)
  /// Universal selector: *
  Universal
  /// ID selector: #id
  Id(String)
  /// Class selector: .class
  Class(String)
  /// Attribute selector: [attr], [attr=value]
  Attribute(AttributeSelector)
  /// Pseudo-class: :hover, :first-child
  PseudoClass(PseudoClass)
  /// Pseudo-element: ::before, ::after
  PseudoElement(PseudoElement)
}

///|
pub impl Show for SimpleSelector with fn output(self, logger) {
  match self {
    Type(name) => logger.write_string(name)
    Universal => logger.write_string("*")
    Id(id) => {
      logger.write_string("#")
      logger.write_string(id)
    }
    Class(cls) => {
      logger.write_string(".")
      logger.write_string(cls)
    }
    Attribute(attr) => attr.output(logger)
    PseudoClass(pc) => pc.output(logger)
    PseudoElement(pe) => pe.output(logger)
  }
}

///|
/// Compound selector - a sequence of simple selectors without combinator
/// e.g., div.foo#bar:hover
pub struct CompoundSelector {
  /// Type or universal selector (at most one, must be first if present)
  type_selector : SimpleSelector?
  /// Other simple selectors (id, class, attribute, pseudo)
  subclasses : Array[SimpleSelector]
  /// True when the type selector was written with the `*|` (any-
  /// namespace) prefix as in `*|*` or `*|name`. CSS canonical form
  /// drops the `*|*` universal when subclasses are present.
  any_ns_universal : Bool
}

///|
pub impl Show for CompoundSelector with fn output(self, logger) {
  match self.type_selector {
    Some(Universal) =>
      // Canonical form drops `*|*` when combined with other subclasses.
      if self.any_ns_universal && self.subclasses.length() > 0 {
        ()
      } else {
        logger.write_string("*")
      }
    Some(sel) => sel.output(logger)
    None => ()
  }
  for sub in self.subclasses {
    sub.output(logger)
  }
}

///|
/// Combinator between compound selectors
pub enum Combinator {
  /// Descendant combinator (whitespace)
  Descendant
  /// Child combinator (>)
  Child
  /// Next-sibling combinator (+)
  NextSibling
  /// Subsequent-sibling combinator (~)
  SubsequentSibling
}

///|
pub impl Show for Combinator with fn output(self, logger) {
  match self {
    Descendant => logger.write_string(" ")
    Child => logger.write_string(" > ")
    NextSibling => logger.write_string(" + ")
    SubsequentSibling => logger.write_string(" ~ ")
  }
}

///|
/// A step in a complex selector: combinator + compound selector
pub struct ComplexSelectorStep {
  combinator : Combinator
  selector : CompoundSelector
}

///|
/// Complex selector - compound selectors joined by combinators
/// e.g., div > p.intro + span
pub struct ComplexSelector {
  /// The rightmost compound selector (the subject by default)
  head : CompoundSelector
  /// Chain of combinator + compound pairs going left
  /// e.g., for "div > p.intro", head = "p.intro", tail = [(Child, "div")]
  tail : Array[ComplexSelectorStep]
}

///|
pub impl Show for ComplexSelector with fn output(self, logger) {
  // Print from left to right (tail is reversed order)
  let parts : Array[String] = []
  for i = self.tail.length() - 1; i >= 0; i = i - 1 {
    let step = self.tail[i]
    let buf = StringBuilder::new()
    step.selector.output(buf)
    parts.push(buf.to_string())
    match step.combinator {
      Descendant => parts.push(" ")
      Child => parts.push(" > ")
      NextSibling => parts.push(" + ")
      SubsequentSibling => parts.push(" ~ ")
    }
  }
  for part in parts {
    logger.write_string(part)
  }
  self.head.output(logger)
}

///|
/// Relative selector for :has()
pub struct RelativeSelector {
  combinator : Combinator?
  selector : ComplexSelector
}

///|
/// Selector list - comma-separated selectors
pub struct SelectorList {
  selectors : Array[ComplexSelector]
}

///|
pub impl Show for SelectorList with fn output(self, logger) {
  for i, sel in self.selectors {
    if i > 0 {
      logger.write_string(", ")
    }
    sel.output(logger)
  }
}

///|
/// Specificity as (a, b, c) tuple
/// a: ID selectors count
/// b: class selectors, attribute selectors, pseudo-classes count
/// c: type selectors, pseudo-elements count
pub(all) struct Specificity {
  a : Int
  b : Int
  c : Int
} derive(Eq, Compare)

///|
pub impl Show for Specificity with fn output(self, logger) {
  logger.write_string("(")
  logger.write_string(self.a.to_string())
  logger.write_string(",")
  logger.write_string(self.b.to_string())
  logger.write_string(",")
  logger.write_string(self.c.to_string())
  logger.write_string(")")
}

///|
pub fn Specificity::zero() -> Specificity {
  { a: 0, b: 0, c: 0 }
}

///|
pub fn Specificity::add(self : Specificity, other : Specificity) -> Specificity {
  { a: self.a + other.a, b: self.b + other.b, c: self.c + other.c }
}

///|
/// Compare specificities for cascade ordering
/// Returns positive if self > other, negative if self < other, 0 if equal
pub fn Specificity::compare_to(self : Specificity, other : Specificity) -> Int {
  if self.a != other.a {
    return self.a - other.a
  }
  if self.b != other.b {
    return self.b - other.b
  }
  self.c - other.c
}

///|
/// Create a simple compound selector with just a type selector
pub fn CompoundSelector::from_type(tag_name : String) -> CompoundSelector {
  {
    type_selector: Some(Type(tag_name)),
    subclasses: [],
    any_ns_universal: false,
  }
}

///|
/// Create a simple compound selector with just a class selector
pub fn CompoundSelector::from_class(class_name : String) -> CompoundSelector {
  {
    type_selector: None,
    subclasses: [Class(class_name)],
    any_ns_universal: false,
  }
}

///|
/// Create a simple compound selector with just an ID selector
pub fn CompoundSelector::from_id(id : String) -> CompoundSelector {
  { type_selector: None, subclasses: [Id(id)], any_ns_universal: false }
}

///|
/// Create a simple compound selector with universal selector
pub fn CompoundSelector::universal() -> CompoundSelector {
  { type_selector: Some(Universal), subclasses: [], any_ns_universal: false }
}

///|
/// Create a complex selector from a single compound
pub fn ComplexSelector::simple(compound : CompoundSelector) -> ComplexSelector {
  { head: compound, tail: [] }
}