// Port of sqlglot/parser.py: WHERE/GROUP/HAVING/QUALIFY/CONNECT/ORDER/LIMIT/
// OFFSET/locks and set operations.

///|
pub fn Parser::parse_prewhere(
  self : Parser,
  skip_where_token? : Bool = false,
) -> Expr? raise SqlglotError {
  if !skip_where_token && !self.match_(PREWHERE) {
    return None
  }
  let comments = self.prev_comments
  Some(self.expression(mk1(PreWhere, self.parse_disjunction()), comments~))
}

///|
pub fn Parser::parse_where(
  self : Parser,
  skip_where_token? : Bool = false,
) -> Expr? raise SqlglotError {
  if !skip_where_token && !self.match_(WHERE) {
    return None
  }
  let comments = self.prev_comments
  Some(self.expression(mk1(Where, self.parse_disjunction()), comments~))
}

///|
pub fn Parser::parse_group(
  self : Parser,
  skip_group_by_token? : Bool = false,
) -> Expr? raise SqlglotError {
  if !skip_group_by_token && !self.match_(GROUP_BY) {
    return None
  }
  let comments = self.prev_comments
  let group = mk0(Group)
  fn list_of(key : String) -> Array[Value] {
    match group.args.get(key) {
      Some(List(l)) => l
      _ => {
        let l = []
        group.args[key] = List(l)
        l
      }
    }
  }

  if self.match_(ALL) {
    group.args["all"] = Bool(true)
  } else if self.match_(DISTINCT) {
    group.args["all"] = Bool(false)
  }
  while true {
    if self.match_set(self.cfg.query_modifier_tokens, advance=false) {
      break
    }
    let exprs = self.parse_csv(() => {
      expr_or(self.parse_grouping_sets(), () => {
        expr_or(self.parse_cube_or_rollup(), () => self.parse_disjunction())
      })
    })
    let l = list_of("expressions")
    for e in exprs {
      l.push(Node(e))
    }
    let before_with_index = self.index
    let mut handled = false
    if self.match_(WITH) {
      match self.parse_cube_or_rollup(with_prefix=true) {
        Some(cube_or_rollup) => {
          let key = if cube_or_rollup.kind == Rollup {
            "rollup"
          } else {
            "cube"
          }
          list_of(key).push(Node(cube_or_rollup))
          handled = true
        }
        None => ()
      }
    }
    if !handled {
      match self.parse_grouping_sets() {
        Some(grouping_sets) => {
          list_of("grouping_sets").push(Node(grouping_sets))
          break
        }
        None =>
          if self.match_text("TOTALS") {
            group.args["totals"] = Bool(true)
          }
      }
    }
    if before_with_index <= self.index && self.index <= before_with_index + 1 {
      self.retreat(before_with_index)
      break
    }
  }
  // re-establish parents for the lists built in place
  let g = Expr::new(Group, group.args)
  Some(self.expression(g, comments~))
}

///|
pub fn Parser::parse_cube_or_rollup(
  self : Parser,
  with_prefix? : Bool = false,
) -> Expr? raise SqlglotError {
  let kind = if self.match_(CUBE) {
    Cube
  } else if self.match_(ROLLUP) {
    Rollup
  } else {
    return None
  }
  let expressions = if with_prefix {
    []
  } else {
    self.parse_wrapped_csv(() => self.parse_bitwise())
  }
  Some(self.expression(mk(kind, [("expressions", expressions)])))
}

///|
pub fn Parser::parse_grouping_sets(self : Parser) -> Expr? raise SqlglotError {
  if self.match_(GROUPING_SETS) {
    return Some(
      self.expression(
        mk(GroupingSets, [
          (
            "expressions",
            self.parse_wrapped_csv(() => self.parse_grouping_set()),
          ),
        ]),
      ),
    )
  }
  None
}

///|
pub fn Parser::parse_grouping_set(self : Parser) -> Expr? raise SqlglotError {
  expr_or(self.parse_grouping_sets(), () => {
    expr_or(self.parse_cube_or_rollup(), () => self.parse_bitwise())
  })
}

///|
pub fn Parser::parse_having(
  self : Parser,
  skip_having_token? : Bool = false,
) -> Expr? raise SqlglotError {
  if !skip_having_token && !self.match_(HAVING) {
    return None
  }
  let comments = self.prev_comments
  Some(self.expression(mk1(Having, self.parse_disjunction()), comments~))
}

///|
pub fn Parser::parse_qualify(self : Parser) -> Expr? raise SqlglotError {
  if !self.match_(QUALIFY) {
    return None
  }
  Some(self.expression(mk1(Qualify, self.parse_disjunction())))
}

///|
pub fn Parser::parse_connect_with_prior(
  self : Parser,
) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_connect_with_prior {
    Some(f) => f(self)
    None => self.parse_connect_with_prior_base()
  }
}

///|
pub fn Parser::parse_connect_with_prior_base(
  self : Parser,
) -> Expr? raise SqlglotError {
  self.fns.no_paren_function_parsers["PRIOR"] = p => {
    Some(p.expression(mk1(Prior, p.parse_bitwise())))
  }
  let connect = self.parse_disjunction() catch {
    e => {
      self.fns.no_paren_function_parsers.remove("PRIOR")
      raise e
    }
  }
  self.fns.no_paren_function_parsers.remove("PRIOR")
  connect
}

///|
pub fn Parser::parse_connect(
  self : Parser,
  skip_start_token? : Bool = false,
) -> Expr? raise SqlglotError {
  let mut start : Expr? = None
  if skip_start_token {
    start = None
  } else if self.match_text_seq(["START", "WITH"]) {
    start = self.parse_disjunction()
  } else {
    return None
  }
  self.match_(CONNECT_BY) |> ignore
  let nocycle = self.match_text("NOCYCLE")
  let connect = self.parse_connect_with_prior()
  if start is None && self.match_text_seq(["START", "WITH"]) {
    start = self.parse_disjunction()
  }
  Some(
    self.expression(
      mk(Connect, [("start", start), ("connect", connect), ("nocycle", nocycle)]),
    ),
  )
}

///|
pub fn Parser::parse_name_as_expression(
  self : Parser,
) -> Expr? raise SqlglotError {
  let mut this = self.parse_id_var(any_token=true)
  if self.match_(ALIAS) {
    this = Some(
      self.expression(
        mk(Alias, [("alias", this), ("this", self.parse_disjunction())]),
      ),
    )
  }
  this
}

///|
pub fn Parser::parse_interpolate(
  self : Parser,
) -> Array[Expr]? raise SqlglotError {
  if self.match_text("INTERPOLATE") {
    return Some(self.parse_wrapped_csv(() => self.parse_name_as_expression()))
  }
  None
}

///|
pub fn Parser::parse_order(
  self : Parser,
  this? : Expr?,
  skip_order_token? : Bool = false,
) -> Expr? raise SqlglotError {
  let this : Expr? = match this {
    Some(t) => t
    None => None
  }
  let mut siblings : Bool? = None
  if !skip_order_token && !self.match_(ORDER_BY) {
    if !self.match_(ORDER_SIBLINGS_BY) {
      return this
    }
    siblings = Some(true)
  }
  let comments = self.prev_comments
  Some(
    self.expression(
      mk(Order, [
        ("this", this),
        ("expressions", self.parse_csv(() => self.parse_ordered())),
        ("siblings", siblings),
      ]),
      comments~,
    ),
  )
}

///|
pub fn Parser::parse_sort(
  self : Parser,
  kind : Kind,
  token : TokenType,
) -> Expr? raise SqlglotError {
  if !self.match_(token) {
    return None
  }
  Some(
    self.expression(
      mk(kind, [("expressions", self.parse_csv(() => self.parse_ordered()))]),
    ),
  )
}

///|
pub fn Parser::parse_ordered(
  self : Parser,
  parse_method? : () -> Expr? raise SqlglotError,
) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_ordered {
    Some(f) => f(self, parse_method)
    None => self.parse_ordered_base(parse_method?)
  }
}

///|
pub fn Parser::parse_ordered_base(
  self : Parser,
  parse_method? : () -> Expr? raise SqlglotError,
) -> Expr? raise SqlglotError {
  let mut this = match parse_method {
    Some(f) => f()
    None => self.parse_disjunction()
  }
  let t = match this {
    Some(t) => t
    None => return None
  }
  if py_upper(t.name()) == "ALL" && self.dialect.cfg.supports_order_by_all {
    this = Some(var_("ALL"))
  }
  let asc = self.match_(ASC)
  let desc : Bool? = if self.match_(DESC) {
    Some(true)
  } else if asc {
    Some(false)
  } else {
    None
  }
  let is_nulls_first = self.match_text_seq(["NULLS", "FIRST"])
  let is_nulls_last = self.match_text_seq(["NULLS", "LAST"])
  let mut nulls_first = is_nulls_first
  let explicitly_null_ordered = is_nulls_first || is_nulls_last
  let null_ordering = self.dialect.cfg.null_ordering
  let is_desc = desc == Some(true)
  if !explicitly_null_ordered &&
    (
      (!is_desc && null_ordering == "nulls_are_small") ||
      (is_desc && null_ordering != "nulls_are_small")
    ) &&
    null_ordering != "nulls_are_last" {
    nulls_first = true
  }
  let with_fill = if self.match_text_seq(["WITH", "FILL"]) {
    let from_ = self.andv(self.match_(FROM), () => self.parse_bitwise())
    let to = self.andv(self.match_text("TO"), () => self.parse_bitwise())
    let step = self.andv(self.match_text("STEP"), () => self.parse_bitwise())
    Some(
      self.expression(
        mk(WithFill, [
          ("from_", from_),
          ("to", to),
          ("step", step),
          ("interpolate", self.parse_interpolate()),
        ]),
      ),
    )
  } else {
    None
  }
  Some(
    self.expression(
      mk(Ordered, [
        ("this", this),
        ("desc", desc),
        ("nulls_first", nulls_first),
        ("with_fill", with_fill),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_limit_options(self : Parser) -> Expr? raise SqlglotError {
  let percent = self.match_any([PERCENT, MOD])
  let rows = self.match_texts(["ROW", "ROWS"])
  self.match_text("ONLY") |> ignore
  let with_ties = self.match_text_seq(["WITH", "TIES"])
  if !(percent || rows || with_ties) {
    return None
  }
  Some(
    self.expression(
      mk(LimitOptions, [
        ("percent", percent),
        ("rows", rows),
        ("with_ties", with_ties),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_limit(
  self : Parser,
  this? : Expr?,
  top? : Bool = false,
  skip_limit_token? : Bool = false,
) -> Expr? raise SqlglotError {
  let this : Expr? = match this {
    Some(t) => t
    None => None
  }
  if skip_limit_token || self.match_(if top { TOP } else { LIMIT }) {
    let comments = self.prev_comments
    let mut expression : Expr? = None
    if top {
      let limit_paren = self.match_(L_PAREN)
      expression = if limit_paren {
        expr_or(self.parse_term(), () => self.parse_select())
      } else {
        self.parse_number()
      }
      if limit_paren {
        self.match_r_paren()
      }
    } else {
      if self.dialect.cfg.supports_limit_all && self.match_(ALL) {
        return this
      }
      expression = self.parse_term(parse_mod=false)
    }
    let limit_options = self.parse_limit_options()
    let mut offset : Expr? = None
    if self.match_(COMMA) {
      offset = expression
      expression = self.parse_term()
    }
    let limit_exp = self.expression(
      mk(Limit, [
        ("this", this),
        ("expression", expression),
        ("offset", offset),
        ("limit_options", limit_options),
        ("expressions", self.parse_limit_by()),
      ]),
      comments~,
    )
    if top {
      limit_exp.get_meta()["top"] = Bool(true)
    }
    return Some(limit_exp)
  }
  if self.match_(FETCH) {
    let direction = if self.match_any([FIRST, NEXT]) {
      self.prev_upper()
    } else {
      "FIRST"
    }
    let count = if self.match_texts(["ROW", "ROWS"], advance=false) {
      None
    } else {
      self.parse_field(tokens=self.cfg.fetch_tokens)
    }
    return Some(
      self.expression(
        mk(Fetch, [
          ("direction", direction),
          ("count", count),
          ("limit_options", self.parse_limit_options()),
        ]),
      ),
    )
  }
  this
}

///|
pub fn Parser::parse_offset(
  self : Parser,
  this? : Expr?,
) -> Expr? raise SqlglotError {
  let this : Expr? = match this {
    Some(t) => t
    None => None
  }
  if !self.match_(OFFSET) {
    return this
  }
  let count = self.parse_term()
  self.match_texts(["ROW", "ROWS"]) |> ignore
  Some(
    self.expression(
      mk(Offset, [
        ("this", this),
        ("expression", count),
        ("expressions", self.parse_limit_by()),
      ]),
    ),
  )
}

///|
pub fn Parser::can_parse_limit_or_offset(self : Parser) -> Bool {
  if !self.match_any(self.cfg.ambiguous_alias_tokens, advance=false) {
    return false
  }
  let index = self.index
  let mut result = self.try_parse(() => self.parse_limit(), retreat=true)
    is Some(_) ||
    self.try_parse(() => self.parse_offset(), retreat=true) is Some(_)
  self.retreat(index)
  if self.next.token_type == MATCH_CONDITION {
    result = false
  }
  result
}

///|
pub fn Parser::can_parse_named_window(self : Parser) -> Bool {
  if !self.match_(WINDOW, advance=false) {
    return false
  }
  let n = self.tokens.length()
  if self.index + 1 >= n ||
    !self.cfg.id_var_tokens.contains(self.tokens[self.index + 1].token_type) {
    return false
  }
  if self.index + 2 >= n || self.tokens[self.index + 2].token_type != ALIAS {
    return false
  }
  self.index + 3 < n && self.tokens[self.index + 3].token_type == L_PAREN
}

///|
pub fn Parser::parse_limit_by(self : Parser) -> Array[Expr]? raise SqlglotError {
  if self.match_text("BY") {
    Some(self.parse_csv(() => self.parse_bitwise()))
  } else {
    None
  }
}

///|
pub fn Parser::parse_locks(self : Parser) -> Array[Expr] raise SqlglotError {
  let locks = []
  while true {
    let mut update : Bool? = None
    let mut key : Bool? = None
    if self.match_text_seq(["FOR", "UPDATE"]) {
      update = Some(true)
    } else if self.match_text_seq(["FOR", "SHARE"]) ||
      self.match_text_seq(["LOCK", "IN", "SHARE", "MODE"]) {
      update = Some(false)
    } else if self.match_text_seq(["FOR", "KEY", "SHARE"]) {
      update = Some(false)
      key = Some(true)
    } else if self.match_text_seq(["FOR", "NO", "KEY", "UPDATE"]) {
      update = Some(true)
      key = Some(true)
    } else {
      break
    }
    let expressions = if self.match_text("OF") {
      Some(self.parse_csv(() => self.parse_table(schema=true)))
    } else {
      None
    }
    let mut wait : Value? = None
    if self.match_text("NOWAIT") {
      wait = Some(Bool(true))
    } else if self.match_text("WAIT") {
      wait = self.parse_primary().map(x => Node(x))
    } else if self.match_text_seq(["SKIP", "LOCKED"]) {
      wait = Some(Bool(false))
    }
    locks.push(
      self.expression(
        mk(Lock, [
          ("update", update),
          ("expressions", expressions),
          ("wait", wait),
          ("key", key),
        ]),
      ),
    )
  }
  locks
}

///|
pub fn Parser::parse_set_operation(
  self : Parser,
  this : Expr?,
  consume_pipe? : Bool = false,
) -> Expr? raise SqlglotError {
  let start = self.index
  let (_, side_token, kind_token) = self.parse_join_parts()
  let side = match side_token {
    Some(t) => Some(t.text)
    None => None
  }
  let mut kind = match kind_token {
    Some(t) => Some(t.text)
    None => None
  }
  if !self.match_set(self.cfg.set_operations) {
    self.retreat(start)
    return None
  }
  let token_type = self.prev.token_type
  let operation = if token_type == UNION {
    Union
  } else if token_type == EXCEPT {
    Except
  } else {
    Intersect
  }
  let comments = self.prev.comments
  let distinct : Bool? = if self.match_(DISTINCT) {
    Some(true)
  } else if self.match_(ALL) {
    Some(false)
  } else {
    let d = match self.dialect.cfg.set_op_distinct_by_default.get(operation) {
      Some(v) => v
      None => None
    }
    if d is None {
      self.raise_error("Expected DISTINCT or ALL for \{operation.name()}")
    }
    d
  }
  let mut by_name : Bool? = if self.match_text_seq(["BY", "NAME"]) ||
    self.match_text_seq(["STRICT", "CORRESPONDING"]) {
    Some(true)
  } else {
    None
  }
  if self.match_text("CORRESPONDING") {
    by_name = Some(true)
    if side is None && kind is None {
      kind = Some("INNER")
    }
  }
  let on_column_list = if by_name == Some(true) &&
    self.match_texts(["ON", "BY"]) {
    Some(self.parse_wrapped_csv(() => self.parse_column()))
  } else {
    None
  }
  let mut expression = self.parse_select(
    nested=true,
    parse_set_operation=false,
    consume_pipe~,
  )
  let mut this = this
  match this {
    Some(t) if t.kind == Values => this = Some(self.values_to_select(t))
    _ => ()
  }
  match expression {
    Some(e) if e.kind == Values => expression = Some(self.values_to_select(e))
    _ => ()
  }
  match this {
    Some(t) if t.kind.is_a(Alias) &&
      (match t.this() {
        Some(s) => s.kind.is_a(Subquery)
        None => false
      }) => {
      let subquery = t.this().unwrap()
      subquery.set("alias", mk1(TableAlias, t.arg("alias")))
      subquery.add_comments(Some(t.pop_comments()))
      this = Some(subquery)
    }
    _ => ()
  }
  Some(
    self.expression(
      mk(operation, [
        ("this", this),
        ("distinct", distinct),
        ("by_name", by_name),
        ("expression", expression),
        ("side", side),
        ("kind", kind),
        ("on", on_column_list),
      ]),
      comments~,
    ),
  )
}

///|
pub fn Parser::parse_set_operations(
  self : Parser,
  this : Expr?,
) -> Expr? raise SqlglotError {
  let mut this = this
  while this is Some(_) {
    match self.parse_set_operation(this) {
      Some(setop) => this = Some(setop)
      None => break
    }
  }
  match this {
    Some(t) if t.kind.is_a(SetOperation) &&
      self.cfg.modifiers_attached_to_set_op => {
      match t.expression() {
        Some(expression) =>
          for arg in self.cfg.set_op_modifiers {
            match expression.get(arg) {
              Some(Node(expr)) if arg == "limit" && expr.meta_bool("top") => ()
              Some(v) if v.truthy() => {
                expression.set(arg, null_arg)
                t.set(arg, v)
              }
              _ => ()
            }
          }
        None => ()
      }
      if self.curr.token_type == LIMIT || self.curr.token_type == FETCH {
        this = self.parse_query_modifiers(this)
      }
    }
    _ => ()
  }
  this
}