///|
pub(all) struct VectorMatching {
  return_bool : Bool
  mode : String?
  labels : Array[String]
  group : String?
  include_labels : Array[String]
} derive(Debug, Eq)

///|
fn grouping(c : Cursor) -> Array[String] raise ParseError {
  c.need("(")
  let out = []
  if c.eat(")") {
    return out
  }
  while true {
    let token = c.take()
    if token.raw_bytes != None ||
      token.text.is_empty() ||
      (
        !token.quoted &&
        (
          token.kind != "word" ||
          !metric_identifier(token.text) ||
          token.text.to_lower() == "without"
        )
      ) {
      raise Invalid("grouping label")
    }
    out.push(token.text)
    if c.eat(")") {
      break
    }
    c.need(",")
    if c.eat(")") {
      break
    }
  }
  out
}

///|
fn vector_matching(c : Cursor) -> VectorMatching raise ParseError {
  let return_bool = c.eat("bool")
  let mode = if c.peek() == "on" || c.peek() == "ignoring" {
    Some(c.take().text.to_lower())
  } else {
    None
  }
  let labels = if mode != None { grouping(c) } else { [] }
  let group = if c.peek() == "group_left" || c.peek() == "group_right" {
    if mode == None {
      raise Invalid("group modifier requires on/ignoring")
    }
    Some(c.take().text.to_lower())
  } else {
    None
  }
  let include_labels = if group != None && c.peek() == "(" {
    grouping(c)
  } else {
    []
  }
  { return_bool, mode, labels, group, include_labels, }
}

///|
fn fill_modifiers(c : Cursor) -> (String?, String?) raise ParseError {
  let mut left : String? = None
  let mut right : String? = None
  while ["fill", "fill_left", "fill_right"].contains(c.peek()) {
    if !c.options.fill_modifiers {
      raise Invalid("binop fill modifiers are not enabled")
    }
    let kind = c.take().text.to_lower()
    c.need("(")
    let sign = if c.eat("-") { "-" } else if c.eat("+") { "+" } else { "" }
    let token = c.take()
    if token.kind != "number" && token.kind != "duration" {
      raise Invalid("fill requires numeric literal")
    }
    let value = sign + token.text
    ignore(number_value(value))
    c.need(")")
    if kind == "fill" || kind == "fill_left" {
      if left != None {
        raise Invalid("duplicate left fill")
      }
      left = Some(value)
    }
    if kind == "fill" || kind == "fill_right" {
      if right != None {
        raise Invalid("duplicate right fill")
      }
      right = Some(value)
    }
  }
  (left, right)
}

///|
fn aggregator(name : String) -> Bool {
  [
    "sum", "avg", "min", "max", "count", "group", "stddev", "stdvar", "topk", "bottomk",
    "quantile", "count_values", "limitk", "limit_ratio",
  ].contains(name)
}

///|
fn aggregate(c : Cursor, name : String, depth : Int) -> Expr raise ParseError {
  if ["limitk", "limit_ratio"].contains(name) &&
    !c.options.experimental_functions {
    raise Invalid("experimental aggregation is not enabled")
  }
  let before = c.peek() == "by" || c.peek() == "without"
  let mut without = false
  let mut labels = []
  if before {
    without = c.take().text.to_lower() == "without"
    labels = grouping(c)
  }
  c.need("(")
  let first = expression(c, 1, depth)
  let parameterized = [
    "topk", "bottomk", "quantile", "count_values", "limitk", "limit_ratio",
  ].contains(name)
  let body = if parameterized {
    c.need(",")
    expression(c, 1, depth)
  } else {
    first
  }
  c.need(")")
  if c.peek() == "by" || c.peek() == "without" {
    if before {
      raise Invalid("duplicate aggregation grouping")
    }
    without = c.take().text.to_lower() == "without"
    labels = grouping(c)
  }
  if parameterized {
    AggregateParam(name, labels, without, first, body)
  } else {
    Aggregate(name, labels, without, body)
  }
}

///|
fn temporal_target(expr : Expr) -> Bool {
  match expr {
    Selector(_, _)
    | SelectorBytes(_, _)
    | Range(_, _)
    | RangeExpression(_, _)
    | Subquery(_, _, _)
    | SubqueryExpression(_, _, _) => true
    Offset(v, _) | At(v, _) | OffsetExpression(v, _) | ExtendedRange(v, _) =>
      temporal_target(v)
    _ => false
  }
}

///|
fn has_modifier(expr : Expr, offset : Bool) -> Bool {
  match expr {
    Offset(v, _) | OffsetExpression(v, _) => offset || has_modifier(v, offset)
    At(v, _) => !offset || has_modifier(v, offset)
    ExtendedRange(v, _) => has_modifier(v, offset)
    _ => false
  }
}

///|
fn direct_selector(expr : Expr) -> Bool {
  match expr {
    Selector(_, _) | SelectorBytes(_, _) => true
    ExtendedRange(v, _) => direct_selector(v)
    _ => false
  }
}

///|
fn extended_target(expr : Expr) -> Bool {
  match expr {
    Selector(_, _)
    | SelectorBytes(_, _)
    | Range(_, _)
    | RangeExpression(_, _) => true
    Offset(v, _) | At(v, _) | OffsetExpression(v, _) | ExtendedRange(v, _) =>
      extended_target(v)
    _ => false
  }
}

///|
fn has_extension_kind(expr : Expr, kind : String) -> Bool {
  match expr {
    ExtendedRange(v, k) => k == kind || has_extension_kind(v, kind)
    Range(v, _)
    | RangeExpression(v, _)
    | Offset(v, _)
    | At(v, _)
    | OffsetExpression(v, _) => has_extension_kind(v, kind)
    _ => false
  }
}

///|
fn postfix(c : Cursor, initial : Expr, depth : Int) -> Expr raise ParseError {
  let mut result = initial
  let mut has_offset = has_modifier(initial, true)
  let mut has_at = has_modifier(initial, false)
  let mut count = 0
  while ["[", "offset", "@", "anchored", "smoothed"].contains(c.peek()) {
    count += 1
    if count > 64 {
      raise Invalid("postfix nesting limit")
    }
    if c.eat("[") {
      let range = duration_expression(c, 1, depth + 1)
      require_positive_duration(range)
      if c.eat(":") {
        let resolution = if c.peek() == "]" {
          None
        } else {
          let step = duration_expression(c, 1, depth + 1)
          require_positive_duration(step)
          Some(step)
        }
        result = match (range, resolution) {
          (DurationValue(r), None) => Subquery(result, r, None)
          (DurationValue(r), Some(DurationValue(s))) =>
            Subquery(result, r, Some(s))
          _ => SubqueryExpression(result, range, resolution)
        }
        has_offset = false
        has_at = false
      } else {
        if !direct_selector(result) {
          raise Invalid(
            "range requires a direct vector selector before modifiers",
          )
        }
        result = match range {
          DurationValue(r) => Range(result, r)
          _ => RangeExpression(result, range)
        }
      }
      c.need("]")
    } else if c.eat("offset") {
      if has_offset || !temporal_target(result) {
        raise Invalid("duplicate or misplaced offset")
      }
      let duration = offset_duration(c, depth + 1)
      result = match duration {
        DurationValue(value) => Offset(result, value)
        _ => OffsetExpression(result, duration)
      }
      has_offset = match duration {
        DurationValue(value) => duration_value(value) != 0.0
        _ => true
      }
    } else if c.eat("@") {
      if has_at || !temporal_target(result) {
        raise Invalid("duplicate or misplaced @")
      }
      let timestamp = if c.peek() == "start" || c.peek() == "end" {
        let which = c.take().text.to_lower()
        c.need("(")
        c.need(")")
        which + "()"
      } else {
        let sign = if c.eat("-") { "-" } else if c.eat("+") { "+" } else { "" }
        let token = c.take()
        if token.kind != "number" && token.kind != "duration" {
          raise Invalid("@ requires a timestamp or start()/end()")
        }
        let text = sign + token.text
        let value = number_value(text)
        if value.is_nan() ||
          value.is_inf() ||
          value >= 9223372036854775807.0 ||
          value <= -9223372036854775808.0 {
          raise Invalid("@ timestamp out of bounds")
        }
        text
      }
      result = At(result, timestamp)
      has_at = true
    } else {
      let kind = c.take().text.to_lower()
      if !c.options.extended_ranges {
        raise Invalid("extended range modifier is not enabled")
      }
      if has_extension_kind(
          result,
          if kind == "anchored" {
            "smoothed"
          } else {
            "anchored"
          },
        ) ||
        !extended_target(result) {
        raise Invalid("conflicting or misplaced extended range modifier")
      }
      result = ExtendedRange(result, kind)
    }
  }
  result
}