///|
/// CSS Media Query Evaluation
/// Evaluates media queries against viewport dimensions

///|
/// Color scheme preference
pub(all) enum ColorScheme {
  Light
  Dark
} derive(Eq, Debug)

///|
pub impl Show for ColorScheme with fn output(self, logger) {
  logger.write_string(
    match self {
      Light => "Light"
      Dark => "Dark"
    },
  )
}

///|
/// Environment for evaluating media queries
pub(all) struct MediaEnvironment {
  viewport_width : Double
  viewport_height : Double
  device_pixel_ratio : Double
  color_scheme : ColorScheme
}

///|
/// Create a default media environment for screen
pub fn MediaEnvironment::new(
  viewport_width : Double,
  viewport_height : Double,
) -> MediaEnvironment {
  {
    viewport_width,
    viewport_height,
    device_pixel_ratio: 1.0,
    color_scheme: Light,
  }
}

///|
/// Create a media environment with color scheme
pub fn MediaEnvironment::with_color_scheme(
  viewport_width : Double,
  viewport_height : Double,
  color_scheme : ColorScheme,
) -> MediaEnvironment {
  { viewport_width, viewport_height, device_pixel_ratio: 1.0, color_scheme }
}

///|
/// Evaluate a media query list against the environment
/// Returns true if ANY query in the list matches (OR semantics)
pub fn MediaQueryList::evaluate(
  self : MediaQueryList,
  env : MediaEnvironment,
) -> Bool {
  // Empty query list matches everything
  if self.queries.is_empty() {
    return true
  }
  // Any matching query makes the whole list match
  for query in self.queries {
    if query.evaluate(env) {
      return true
    }
  }
  false
}

///|
/// Evaluate a single media query
pub fn MediaQuery::evaluate(self : MediaQuery, env : MediaEnvironment) -> Bool {
  let result = self.evaluate_without_negation(env)
  if self.negated {
    !result
  } else {
    result
  }
}

///|
/// Evaluate a media query without considering negation
fn MediaQuery::evaluate_without_negation(
  self : MediaQuery,
  env : MediaEnvironment,
) -> Bool {
  // Check media type
  match self.media_type {
    Some(Print) => return false // We're rendering to screen
    Some(Speech) => return false
    Some(All) | Some(Screen) | None => ()
  }
  // Check all conditions (AND semantics)
  for condition in self.conditions {
    if !evaluate_feature(condition, env) {
      return false
    }
  }
  true
}

///|
/// Evaluate a single media feature
fn evaluate_feature(feature : MediaFeature, env : MediaEnvironment) -> Bool {
  match feature {
    // Width features
    Width(value) =>
      match value {
        Some(v) => compare_value(v, env.viewport_width, Eq, env)
        None => true // Boolean context: width exists
      }
    MinWidth(value) => compare_value(value, env.viewport_width, Ge, env)
    MaxWidth(value) => compare_value(value, env.viewport_width, Le, env)
    WidthRange(op, value) =>
      compare_value_with_op(value, env.viewport_width, op, env)
    // Height features
    Height(value) =>
      match value {
        Some(v) => compare_value(v, env.viewport_height, Eq, env)
        None => true
      }
    MinHeight(value) => compare_value(value, env.viewport_height, Ge, env)
    MaxHeight(value) => compare_value(value, env.viewport_height, Le, env)
    HeightRange(op, value) =>
      compare_value_with_op(value, env.viewport_height, op, env)
    // Aspect ratio
    AspectRatio(value) =>
      match value {
        Some(v) =>
          compare_aspect_ratio(v, env.viewport_width, env.viewport_height, Eq)
        None => true
      }
    MinAspectRatio(value) =>
      compare_aspect_ratio(value, env.viewport_width, env.viewport_height, Ge)
    MaxAspectRatio(value) =>
      compare_aspect_ratio(value, env.viewport_width, env.viewport_height, Le)
    // Orientation
    Orientation(value) =>
      match value.to_lower() {
        "portrait" => env.viewport_height >= env.viewport_width
        "landscape" => env.viewport_width > env.viewport_height
        _ => true
      }
    // Resolution
    Resolution(value) =>
      match value {
        Some(v) => compare_resolution(v, env.device_pixel_ratio, Eq)
        None => true
      }
    MinResolution(value) =>
      compare_resolution(value, env.device_pixel_ratio, Ge)
    MaxResolution(value) =>
      compare_resolution(value, env.device_pixel_ratio, Le)
    // Boolean features (always true for screen)
    Color => true
    ColorIndex => false
    ColorGamut => true
    Grid => false
    Hover => true // Assume hover is available
    Monochrome => false
    Pointer => true // Assume pointer is available
    Scripting => true
    Update => true
    // Discrete features
    DisplayMode(_) => true // Assume browser mode
    PrefersColorScheme(scheme) =>
      match scheme.to_lower() {
        "dark" => env.color_scheme == Dark
        "light" => env.color_scheme == Light
        _ => true // Unknown scheme matches any
      }
    PrefersReducedMotion(_) => true // Accept any motion preference
    // Unknown features default to true
    Custom(_, _) => true
  }
}

///|
/// Convert media value to pixels. Viewport-relative units (vw/vh/vmin/vmax)
/// resolve against `env`'s viewport, so `50vw` on a 1000px-wide viewport is
/// 500px regardless of which dimension the feature compares against.
fn to_pixels(value : MediaValue, env : MediaEnvironment) -> Double {
  match value {
    Number(n) => n
    Dimension(n, unit) =>
      match unit.to_lower() {
        "px" => n
        "em" | "rem" => n * 16.0 // Assume 16px base font size
        "vw" | "dvw" | "svw" | "lvw" => n * env.viewport_width / 100.0
        "vh" | "dvh" | "svh" | "lvh" => n * env.viewport_height / 100.0
        "vmin" => {
          let base = if env.viewport_width < env.viewport_height {
            env.viewport_width
          } else {
            env.viewport_height
          }
          n * base / 100.0
        }
        "vmax" => {
          let base = if env.viewport_width > env.viewport_height {
            env.viewport_width
          } else {
            env.viewport_height
          }
          n * base / 100.0
        }
        "pt" => n * 1.333333 // 1pt = 4/3 px
        "pc" => n * 16.0 // 1pc = 16px
        "in" => n * 96.0 // 1in = 96px
        "cm" => n * 37.8 // 1cm ≈ 37.8px
        "mm" => n * 3.78 // 1mm ≈ 3.78px
        _ => n
      }
    Ratio(_, _) => 0.0 // Ratios aren't pixel values
    Ident(_) => 0.0 // Identifiers aren't pixel values
  }
}

///|
/// Compare a media value against an actual value
/// For min-X: actual >= query (Le means query <= actual)
/// For max-X: actual <= query (Ge means query >= actual)
fn compare_value(
  query_value : MediaValue,
  actual : Double,
  op : RangeOp,
  env : MediaEnvironment,
) -> Bool {
  let query_pixels = to_pixels(query_value, env)
  match op {
    Eq => (actual - query_pixels).abs() < 0.01
    Lt => actual < query_pixels
    Le => actual <= query_pixels
    Gt => actual > query_pixels
    Ge => actual >= query_pixels
  }
}

///|
/// Compare value with explicit range-syntax operator (e.g., width >= 500px).
/// Identical semantics to `compare_value`; kept as a thin alias for clarity at
/// call sites that came from the parser's range syntax.
fn compare_value_with_op(
  query_value : MediaValue,
  actual : Double,
  op : RangeOp,
  env : MediaEnvironment,
) -> Bool {
  compare_value(query_value, actual, op, env)
}

///|
/// Compare aspect ratio
fn compare_aspect_ratio(
  query_value : MediaValue,
  width : Double,
  height : Double,
  op : RangeOp,
) -> Bool {
  if height == 0.0 {
    return false
  }
  let actual_ratio = width / height
  let query_ratio = match query_value {
    Ratio(w, h) => if h == 0 { 0.0 } else { w.to_double() / h.to_double() }
    Number(n) => n
    _ => 1.0
  }
  match op {
    Eq => (actual_ratio - query_ratio).abs() < 0.01
    Lt => actual_ratio < query_ratio
    Le => actual_ratio <= query_ratio
    Gt => actual_ratio > query_ratio
    Ge => actual_ratio >= query_ratio
  }
}

///|
/// Compare resolution (device pixel ratio)
fn compare_resolution(
  query_value : MediaValue,
  actual_dpr : Double,
  op : RangeOp,
) -> Bool {
  let query_dpr = match query_value {
    Number(n) => n
    Dimension(n, unit) =>
      match unit.to_lower() {
        "dppx" | "x" => n
        "dpi" => n / 96.0
        "dpcm" => n / 37.8
        _ => n
      }
    _ => 1.0
  }
  match op {
    Eq => (actual_dpr - query_dpr).abs() < 0.01
    Lt => actual_dpr < query_dpr
    Le => actual_dpr <= query_dpr
    Gt => actual_dpr > query_dpr
    Ge => actual_dpr >= query_dpr
  }
}