///|
/// CSS Media Query Parser
/// Parses media query strings like "@media screen and (min-width: 768px)"

///|
/// Parse a media query list from a string
/// e.g., "screen and (min-width: 768px), print"
pub fn parse_media_query_list(input : String) -> MediaQueryList {
  let queries : Array[MediaQuery] = []
  let parts = split_media_queries(input)
  for part in parts {
    let or_parts = split_media_query_or(part)
    for or_part in or_parts {
      let trimmed = or_part.trim().to_owned()
      if !trimmed.is_empty() {
        match parse_single_media_query(trimmed) {
          Some(query) => queries.push(query)
          None => ()
        }
      }
    }
  }
  { queries, }
}

///|
/// Split media queries by comma (top-level only, not inside parentheses)
fn split_media_queries(input : String) -> Array[String] {
  let result : Array[String] = []
  let current = StringBuilder::new()
  let mut paren_depth = 0
  for i = 0; i < input.length(); i = i + 1 {
    let c = input[i].to_int().unsafe_to_char()
    if c == '(' {
      paren_depth += 1
      current.write_char(c)
    } else if c == ')' {
      paren_depth -= 1
      current.write_char(c)
    } else if c == ',' && paren_depth == 0 {
      result.push(current.to_string())
      current.reset()
    } else {
      current.write_char(c)
    }
  }
  let final_str = current.to_string()
  if !final_str.is_empty() {
    result.push(final_str)
  }
  result
}

///|
fn join_media_tokens(tokens : Array[String]) -> String {
  let out = StringBuilder::new()
  for i = 0; i < tokens.length(); i = i + 1 {
    if i > 0 {
      out.write_char(' ')
    }
    out.write_string(tokens[i])
  }
  out.to_string()
}

///|
fn split_media_query_or(input : String) -> Array[String] {
  let result : Array[String] = []
  let current : Array[String] = []
  let tokens = tokenize_media_query(input)
  for token in tokens {
    if token.to_lower() == "or" {
      if current.length() > 0 {
        result.push(join_media_tokens(current))
        current.clear()
      }
    } else {
      current.push(token)
    }
  }
  if current.length() > 0 {
    result.push(join_media_tokens(current))
  }
  result
}

///|
/// Parse a single media query
/// e.g., "screen and (min-width: 768px)"
fn parse_single_media_query(input : String) -> MediaQuery? {
  let input = input.trim().to_owned()
  if input.is_empty() {
    return None
  }
  let tokens = tokenize_media_query(input)
  if tokens.is_empty() {
    return None
  }
  let mut pos = 0
  let mut negated = false
  let mut only = false
  let mut media_type : MediaType? = None
  let conditions : Array[MediaFeature] = []

  // Check for "not" or "only" prefix
  if pos < tokens.length() {
    let token = tokens[pos].to_lower()
    if token == "not" {
      negated = true
      pos += 1
    } else if token == "only" {
      only = true
      pos += 1
    }
  }

  // Check for media type
  if pos < tokens.length() {
    let token = tokens[pos].to_lower()
    match parse_media_type(token) {
      Some(mt) => {
        media_type = Some(mt)
        pos += 1
      }
      None => ()
    }
  }

  // Parse remaining conditions (and (...))
  while pos < tokens.length() {
    let token = tokens[pos].to_lower()
    if token == "and" {
      pos += 1
      continue
    }
    if token.has_prefix("(") && token.has_suffix(")") {
      // Extract content inside parentheses
      let content = token.unsafe_substring(start=1, end=token.length() - 1)
      match parse_media_feature(content) {
        Some(feature) => conditions.push(feature)
        None => ()
      }
    }
    pos += 1
  }
  Some({ negated, only, media_type, conditions })
}

///|
/// Tokenize media query string into tokens
fn tokenize_media_query(input : String) -> Array[String] {
  let tokens : Array[String] = []
  let current = StringBuilder::new()
  let mut in_paren = false
  let mut paren_depth = 0
  for i = 0; i < input.length(); i = i + 1 {
    let c = input[i].to_int().unsafe_to_char()
    if c == '(' {
      if paren_depth == 0 {
        // Push any preceding token
        let s = current.to_string().trim().to_owned()
        if !s.is_empty() {
          tokens.push(s)
        }
        current.reset()
        in_paren = true
      }
      paren_depth += 1
      current.write_char(c)
    } else if c == ')' {
      paren_depth -= 1
      current.write_char(c)
      if paren_depth == 0 {
        in_paren = false
        let s = current.to_string().trim().to_owned()
        if !s.is_empty() {
          tokens.push(s)
        }
        current.reset()
      }
    } else if c == ' ' || c == '\t' || c == '\n' {
      if in_paren {
        current.write_char(c)
      } else {
        let s = current.to_string().trim().to_owned()
        if !s.is_empty() {
          tokens.push(s)
        }
        current.reset()
      }
    } else {
      current.write_char(c)
    }
  }
  let final_str = current.to_string().trim().to_owned()
  if !final_str.is_empty() {
    tokens.push(final_str)
  }
  tokens
}

///|
/// Parse media type from string
fn parse_media_type(s : String) -> MediaType? {
  match s {
    "all" => Some(All)
    "screen" => Some(Screen)
    "print" => Some(Print)
    "speech" => Some(Speech)
    _ => None
  }
}

///|
/// Parse a media feature from the content inside parentheses
/// e.g., "min-width: 768px" or "color" or "width >= 768px"
fn parse_media_feature(content : String) -> MediaFeature? {
  let content = content.trim().to_owned()
  if content.is_empty() {
    return None
  }

  // Check for range syntax (e.g., "width >= 768px")
  match parse_range_feature(content) {
    Some(feature) => return Some(feature)
    None => ()
  }

  // Check for colon (property: value)
  let colon_pos = find_char(content, ':')
  if colon_pos >= 0 {
    let name = content.unsafe_substring(start=0, end=colon_pos)
    let value = content.unsafe_substring(
      start=colon_pos + 1,
      end=content.length(),
    )
    return parse_feature_with_value(
      name.trim().to_owned(),
      value.trim().to_owned(),
    )
  }

  // Boolean feature (no value)
  parse_boolean_feature(content)
}

///|
/// Parse range syntax feature (CSS Media Queries Level 4)
fn parse_range_feature(content : String) -> MediaFeature? {
  // Check for comparison operators
  let ops = [">=", "<=", ">", "<", "="]
  for op in ops {
    let op_pos = find_substring(content, op)
    if op_pos >= 0 {
      let name = content.unsafe_substring(start=0, end=op_pos)
      let value_str = content.unsafe_substring(
        start=op_pos + op.length(),
        end=content.length(),
      )
      match parse_media_value(value_str.trim().to_owned()) {
        Some(value) => {
          let range_op = match op {
            ">=" => Ge
            "<=" => Le
            ">" => Gt
            "<" => Lt
            "=" => Eq
            _ => return None
          }
          match name.trim().to_owned().to_lower() {
            "width" => return Some(WidthRange(range_op, value))
            "height" => return Some(HeightRange(range_op, value))
            _ => return Some(Custom(name.trim().to_owned(), Some(value)))
          }
        }
        None => ()
      }
    }
  }
  None
}

///|
/// Parse a feature with a value (name: value)
fn parse_feature_with_value(name : String, value_str : String) -> MediaFeature? {
  let name_lower = name.to_lower()
  match parse_media_value(value_str) {
    Some(value) =>
      match name_lower {
        "min-width" => Some(MinWidth(value))
        "max-width" => Some(MaxWidth(value))
        "width" => Some(Width(Some(value)))
        "min-height" => Some(MinHeight(value))
        "max-height" => Some(MaxHeight(value))
        "height" => Some(Height(Some(value)))
        "min-aspect-ratio" => Some(MinAspectRatio(value))
        "max-aspect-ratio" => Some(MaxAspectRatio(value))
        "aspect-ratio" => Some(AspectRatio(Some(value)))
        "min-resolution" => Some(MinResolution(value))
        "max-resolution" => Some(MaxResolution(value))
        "resolution" => Some(Resolution(Some(value)))
        "orientation" =>
          match value {
            Ident(s) => Some(Orientation(s))
            _ => Some(Custom(name, Some(value)))
          }
        "display-mode" =>
          match value {
            Ident(s) => Some(DisplayMode(s))
            _ => Some(Custom(name, Some(value)))
          }
        "prefers-color-scheme" =>
          match value {
            Ident(s) => Some(PrefersColorScheme(s))
            _ => Some(Custom(name, Some(value)))
          }
        "prefers-reduced-motion" =>
          match value {
            Ident(s) => Some(PrefersReducedMotion(s))
            _ => Some(Custom(name, Some(value)))
          }
        _ => Some(Custom(name, Some(value)))
      }
    None => Some(Custom(name, None))
  }
}

///|
/// Parse a boolean feature (no value)
fn parse_boolean_feature(name : String) -> MediaFeature? {
  let name_lower = name.to_lower()
  match name_lower {
    "color" => Some(Color)
    "color-gamut" => Some(ColorGamut)
    "color-index" => Some(ColorIndex)
    "grid" => Some(Grid)
    "hover" => Some(Hover)
    "monochrome" => Some(Monochrome)
    "pointer" => Some(Pointer)
    "scripting" => Some(Scripting)
    "update" => Some(Update)
    "width" => Some(Width(None))
    "height" => Some(Height(None))
    "aspect-ratio" => Some(AspectRatio(None))
    "resolution" => Some(Resolution(None))
    _ => Some(Custom(name, None))
  }
}

///|
/// Parse a media value
fn parse_media_value(s : String) -> MediaValue? {
  let s = s.trim().to_owned()
  if s.is_empty() {
    return None
  }

  match parse_calc_media_value(s) {
    Some(value) => return Some(value)
    None => ()
  }

  // Check for ratio (e.g., "16/9")
  let slash_pos = find_char(s, '/')
  if slash_pos > 0 {
    let left = s.unsafe_substring(start=0, end=slash_pos)
    let right = s.unsafe_substring(start=slash_pos + 1, end=s.length())
    match
      (parse_int(left.trim().to_owned()), parse_int(right.trim().to_owned())) {
      (Some(l), Some(r)) => return Some(Ratio(l, r))
      _ => ()
    }
  }

  // Check for dimension (e.g., "768px", "2dppx")
  match parse_dimension_value(s) {
    Some((value, unit)) => return Some(Dimension(value, unit))
    None => ()
  }

  // Check for number
  match parse_number_value(s) {
    Some(n) => return Some(Number(n))
    None => ()
  }

  // Otherwise, it's an identifier
  Some(Ident(s))
}

///|
fn parse_calc_media_value(s : String) -> MediaValue? {
  let lower = s.to_lower()
  if !lower.has_prefix("calc(") || !s.has_suffix(")") {
    return None
  }
  let inner = s.unsafe_substring(start=5, end=s.length() - 1)
  match parse_calc_expr_at(inner, 0) {
    Some((value, pos)) => {
      let pos = skip_calc_ws(inner, pos)
      if pos == inner.length() {
        Some(Dimension(value, "px"))
      } else {
        None
      }
    }
    None => None
  }
}

///|
fn skip_calc_ws(s : String, pos : Int) -> Int {
  let mut i = pos
  while i < s.length() {
    let c = s[i].to_int().unsafe_to_char()
    if c == ' ' || c == '\t' || c == '\n' {
      i = i + 1
    } else {
      break
    }
  }
  i
}

///|
fn parse_calc_expr_at(s : String, pos : Int) -> (Double, Int)? {
  match parse_calc_term_at(s, pos) {
    Some((first, next)) => {
      let mut value = first
      let mut i = skip_calc_ws(s, next)
      while i < s.length() {
        let op = s[i].to_int().unsafe_to_char()
        if op != '+' && op != '-' {
          break
        }
        match parse_calc_term_at(s, i + 1) {
          Some((rhs, after_rhs)) => {
            value = if op == '+' { value + rhs } else { value - rhs }
            i = skip_calc_ws(s, after_rhs)
          }
          None => return None
        }
      }
      Some((value, i))
    }
    None => None
  }
}

///|
fn parse_calc_term_at(s : String, pos : Int) -> (Double, Int)? {
  match parse_calc_factor_at(s, pos) {
    Some((first, next)) => {
      let mut value = first
      let mut i = skip_calc_ws(s, next)
      while i < s.length() {
        let op = s[i].to_int().unsafe_to_char()
        if op != '*' && op != '/' {
          break
        }
        match parse_calc_factor_at(s, i + 1) {
          Some((rhs, after_rhs)) => {
            if op == '*' {
              value = value * rhs
            } else if rhs != 0.0 {
              value = value / rhs
            } else {
              return None
            }
            i = skip_calc_ws(s, after_rhs)
          }
          None => return None
        }
      }
      Some((value, i))
    }
    None => None
  }
}

///|
fn parse_calc_factor_at(s : String, pos : Int) -> (Double, Int)? {
  let pos = skip_calc_ws(s, pos)
  if pos >= s.length() {
    return None
  }
  let c = s[pos].to_int().unsafe_to_char()
  if c == '+' {
    return parse_calc_factor_at(s, pos + 1)
  }
  if c == '-' {
    match parse_calc_factor_at(s, pos + 1) {
      Some((value, next)) => return Some((-value, next))
      None => return None
    }
  }
  if c == '(' {
    match parse_calc_expr_at(s, pos + 1) {
      Some((value, next)) => {
        let next = skip_calc_ws(s, next)
        if next < s.length() && s[next].to_int().unsafe_to_char() == ')' {
          return Some((value, next + 1))
        }
        return None
      }
      None => return None
    }
  }
  parse_calc_numeric_value_at(s, pos)
}

///|
fn parse_calc_numeric_value_at(s : String, pos : Int) -> (Double, Int)? {
  let mut i = pos
  let mut saw_digit = false
  while i < s.length() {
    let c = s[i].to_int().unsafe_to_char()
    if c >= '0' && c <= '9' {
      saw_digit = true
      i = i + 1
    } else if c == '.' {
      i = i + 1
    } else {
      break
    }
  }
  if !saw_digit {
    return None
  }
  let num_str = s.unsafe_substring(start=pos, end=i)
  let value = @string.parse_double(num_str) catch { _ => return None }
  let unit_start = i
  while i < s.length() {
    let c = s[i].to_int().unsafe_to_char()
    if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '%' {
      i = i + 1
    } else {
      break
    }
  }
  let unit = if i > unit_start {
    s.unsafe_substring(start=unit_start, end=i).to_lower()
  } else {
    ""
  }
  let px_value = match unit {
    "px" | "" => value
    "em" | "rem" => value * 16.0
    "pt" => value * 1.333333
    "pc" => value * 16.0
    "in" => value * 96.0
    "cm" => value * 37.8
    "mm" => value * 3.78
    _ => value
  }
  Some((px_value, i))
}

///|
/// Parse a dimension value (number + unit)
fn parse_dimension_value(s : String) -> (Double, String)? {
  let mut num_end = 0
  for i = 0; i < s.length(); i = i + 1 {
    let c = s[i].to_int().unsafe_to_char()
    if (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' {
      num_end = i + 1
    } else {
      break
    }
  }
  if num_end > 0 && num_end < s.length() {
    let num_str = s.unsafe_substring(start=0, end=num_end)
    let unit = s.unsafe_substring(start=num_end, end=s.length())
    try {
      let value = @string.parse_double(num_str)
      Some((value, unit))
    } catch {
      _ => None
    }
  } else {
    None
  }
}

///|
/// Parse a number value
fn parse_number_value(s : String) -> Double? {
  Some(@string.parse_double(s)) catch {
    _ => None
  }
}

///|
/// Parse an integer value
fn parse_int(s : String) -> Int? {
  Some(@string.parse_int(s)) catch {
    _ => None
  }
}

///|
/// Find the position of a character in a string
fn find_char(s : String, c : Char) -> Int {
  for i = 0; i < s.length(); i = i + 1 {
    if s[i].to_int().unsafe_to_char() == c {
      return i
    }
  }
  -1
}

///|
/// Find the position of a substring
fn find_substring(s : String, sub : String) -> Int {
  if sub.length() > s.length() {
    return -1
  }
  for i = 0; i <= s.length() - sub.length(); i = i + 1 {
    let mut found = true
    for j = 0; j < sub.length(); j = j + 1 {
      if s[i + j] != sub[j] {
        found = false
        break
      }
    }
    if found {
      return i
    }
  }
  -1
}