// Generator methods for literals, identifiers, data types and operators.

///|
pub let annotate_types_hook : Ref[((Expr, Dialect) -> Expr raise SqlglotError)?] = Ref(
  None,
)

///|
pub let simplify_hook : Ref[((Expr, Dialect) -> Expr raise SqlglotError)?] = Ref(
  None,
)

///|
/// Annotates types using `annotate_types_hook`, or a minimal fallback that only
/// types integer literals.
pub fn gen_annotate_types(
  expression : Expr,
  dialect : Dialect,
) -> Expr raise SqlglotError {
  match annotate_types_hook.val {
    Some(f) => f(expression, dialect)
    None => {
      // Minimal fallback until the optimizer's annotate_types is available.
      if expression.get_type() is None {
        let dtype = if expression.is_int() {
          Some(DType::INT)
        } else if expression.kind == Literal && expression.is_number() {
          Some(DType::DOUBLE)
        } else if expression.kind == Neg &&
          expression.this() is Some(operand) &&
          operand.kind == Literal &&
          operand.is_number() {
          // Unary minus keeps the type of its numeric literal operand
          gen_annotate_types(operand, dialect) |> ignore
          match operand.get_type() {
            Some(t) => t.datatype_this()
            None => None
          }
        } else if expression.kind == Timestamp {
          Some(
            if expression.has("with_tz") {
              DType::TIMESTAMPTZ
            } else {
              DType::TIMESTAMP
            },
          )
        } else if expression.is_string() {
          Some(DType::VARCHAR)
        } else if expression.kind == Kind::Array {
          Some(DType::ARRAY)
        } else if expression.kind == Boolean {
          Some(DType::BOOLEAN)
        } else if expression.kind == Map || expression.kind == VarMap {
          Some(DType::MAP)
        } else if expression.kind == Struct {
          Some(DType::STRUCT)
        } else {
          None
        }
        match dtype {
          Some(d) => expression.set_type(Some(datatype_of(d)))
          None => ()
        }
      }
      expression
    }
  }
}

///|
fn gen_simplify(
  expression : Expr,
  dialect : Dialect,
) -> Expr raise SqlglotError {
  match simplify_hook.val {
    Some(f) => f(expression, dialect)
    None => {
      // Minimal constant folding for `int + int`
      if expression.kind == Add {
        match (expression.arg("this"), expression.arg("expression")) {
          (Some(a), Some(b)) =>
            match (a.to_py_int(), b.to_py_int()) {
              (Some(x), Some(y)) if a.is_int() && b.is_int() =>
                return literal_number((x + y).to_string())
              _ => ()
            }
          _ => ()
        }
      }
      expression
    }
  }
}

///|
/// Python `expressions.apply_index_offset`.
pub fn gen_apply_index_offset(
  this : Expr,
  expressions : Array[Expr],
  offset : Int,
  dialect : Dialect,
) -> Array[Expr] raise SqlglotError {
  if offset == 0 || expressions.length() != 1 {
    return expressions
  }
  let mut expression = expressions[0]
  if this.get_type() is None {
    gen_annotate_types(this, dialect) |> ignore
  }
  let this_type = match this.get_type() {
    Some(t) => t.datatype_this()
    None => Some(DType::UNKNOWN)
  }
  if !(this_type == Some(DType::UNKNOWN) || this_type == Some(DType::ARRAY)) {
    return expressions
  }
  if expression.get_type() is None {
    gen_annotate_types(expression, dialect) |> ignore
  }
  let et = match expression.get_type() {
    Some(t) => t.datatype_this()
    None => None
  }
  match et {
    Some(d) if dtype_integer_types.contains(d) => {
      g_log_info("Applying array index offset (\{offset})")
      expression = gen_simplify(
        exp_add(expression, literal_int(offset)),
        dialect,
      )
      [expression]
    }
    _ => expressions
  }
}

///|
fn g_log_info(msg : String) -> Unit {
  log_info(msg)
}

// ---------------------------------------------------------------------------
// Literals and strings

///|
pub fn Generator::bitstring_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  if g_str_truthy(self.dialect.cfg.bit_start) {
    return g_opt_str(self.dialect.cfg.bit_start) +
      this +
      g_opt_str(self.dialect.cfg.bit_end)
  }
  int_str_from_base(this, 2)
}

///|
/// Virtual `hexstring_sql(expression, binary_function_repr)`.
pub fn Generator::hexstring_sql_v(
  self : Generator,
  expression : Expr,
  binary_function_repr? : String,
) -> String raise SqlglotError {
  match self.fns.hooks.hexstring_sql {
    Some(f) => f(self, expression, binary_function_repr)
    None => self.hexstring_sql(expression, binary_function_repr?)
  }
}

///|
pub fn Generator::hexstring_sql(
  self : Generator,
  expression : Expr,
  binary_function_repr? : String,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let is_integer_type = expression.has("is_integer")
  let has_repr = g_str_truthy(binary_function_repr)
  if (is_integer_type && !self.dialect.cfg.hex_string_is_integer_type) ||
    (!g_str_truthy(self.dialect.cfg.hex_start) && !has_repr) {
    return int_str_from_base(this, 16)
  }
  if !is_integer_type {
    if has_repr {
      return self.func(g_opt_str(binary_function_repr), [literal_string(this)])
    }
    if self.dialect.cfg.hex_string_is_integer_type {
      self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
    }
  }
  g_opt_str(self.dialect.cfg.hex_start) +
  this +
  g_opt_str(self.dialect.cfg.hex_end)
}

///|
pub fn Generator::bytestring_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  if g_str_truthy(self.dialect.cfg.byte_start) {
    let byte_end = g_opt_str(self.dialect.cfg.byte_end)
    let escaped_byte_string = self.escape_str(
      this,
      escape_backslash=false,
      delimiter=byte_end,
      escaped_delimiter=self.escaped_byte_quote_end,
      is_byte_string=true,
    )
    let is_bytes = expression.has("is_bytes")
    let delimited_byte_string = g_opt_str(self.dialect.cfg.byte_start) +
      escaped_byte_string +
      byte_end
    if is_bytes && !self.dialect.cfg.byte_string_is_bytes_type {
      return self.sql(
        Some(
          exp_cast(
            maybe_parse_str(delimited_byte_string, dialect=self.dialect),
            DType::BINARY,
            dialect=self.dialect,
          ),
        ),
      )
    }
    if !is_bytes && self.dialect.cfg.byte_string_is_bytes_type {
      return self.sql(
        Some(
          exp_cast(
            maybe_parse_str(delimited_byte_string, dialect=self.dialect),
            DType::VARCHAR,
            dialect=self.dialect,
          ),
        ),
      )
    }
    return delimited_byte_string
  }
  if self.dialect.tokenizer.string_escapes.contains("\\") {
    return self.sql(Some(literal_string(this)))
  }
  self.unsupported(
    "Byte strings are not supported for \{self.dialect_class_name()}",
  )
  ""
}

///|
/// `re.sub(rf"{prefix}(\d+)", repl, text)` with a literal prefix.
fn g_sub_escaped_digits(
  text : String,
  prefix : String,
  repl : (String) -> String,
) -> String {
  let chars = text.to_array()
  let pchars = prefix.to_array()
  let n = chars.length()
  let pn = pchars.length()
  let sb = StringBuilder()
  let mut i = 0
  while i < n {
    let mut matches = i + pn <= n
    if matches {
      for j in 0.. i + pn {
        sb.write_string(repl(String::from_array(chars[i + pn:k])))
        i = k
        continue
      }
    }
    sb.write_char(chars[i])
    i += 1
  }
  sb.to_string()
}

///|
pub fn Generator::unicodestring_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = self.sql_key(expression, "this")
  let escape = expression.arg("escape")
  let unicode_start = self.dialect.cfg.unicode_start
  let has_unicode_start = g_str_truthy(unicode_start)
  let (escape_substitute, left_quote, right_quote) : (
    (String) -> String,
    String,
    String,
  ) = if has_unicode_start {
    (
      fn(g) { "\\" + g },
      g_opt_str(unicode_start),
      g_opt_str(self.dialect.cfg.unicode_end),
    )
  } else {
    (
      fn(g) { "\\u" + g },
      self.dialect.cfg.quote_start,
      self.dialect.cfg.quote_end,
    )
  }
  let (escape_prefix, escape_sql) = match escape {
    Some(esc) =>
      (
        esc.name(),
        if self.cfg.supports_uescape {
          " UESCAPE " + self.sql(Some(esc))
        } else {
          ""
        },
      )
    None => ("\\", "")
  }
  if !has_unicode_start || (escape is Some(_) && !self.cfg.supports_uescape) {
    let repl = match self.fns.hooks.unicode_substitute {
      Some(f) => f
      None => escape_substitute
    }
    this = g_sub_escaped_digits(this, escape_prefix, repl)
  }
  if has_unicode_start {
    this = self.replace_line_breaks(this)
    if right_quote != "" {
      this = this.replace_all(old=right_quote, new=right_quote + right_quote)
    }
  } else {
    this = self.escape_str(this, escape_backslash=false)
  }
  left_quote + this + right_quote + escape_sql
}

///|
pub fn Generator::rawstring_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut string = expression.text("this")
  if self.dialect.tokenizer.string_escapes.contains("\\") {
    string = string.replace_all(old="\\", new="\\\\")
  }
  string = self.escape_str(string, escape_backslash=false)
  self.dialect.cfg.quote_start + string + self.dialect.cfg.quote_end
}

///|
pub fn Generator::literal_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let text = match expression.get("this") {
    Some(Str(s)) => s
    Some(v) if v.truthy() => py_str_value(Some(v))
    _ => ""
  }
  if expression.is_string() {
    return self.dialect.cfg.quote_start +
      self.escape_str(text) +
      self.dialect.cfg.quote_end
  }
  text
}

///|
pub fn Generator::escape_str(
  self : Generator,
  text : String,
  escape_backslash? : Bool = true,
  delimiter? : String,
  escaped_delimiter? : String,
  is_byte_string? : Bool = false,
) -> String {
  let supports_escape_sequences = if is_byte_string {
    self.dialect.cfg.byte_strings_support_escaped_sequences
  } else {
    self.dialect.cfg.strings_support_escaped_sequences
  }
  let mut text = text
  if supports_escape_sequences {
    let sb = StringBuilder()
    let seqs = self.dialect.cfg.escaped_sequences
    for ch in text {
      if escape_backslash || ch != '\\' {
        match seqs.get(ch.to_string()) {
          Some(s) => sb.write_string(s)
          None => sb.write_char(ch)
        }
      } else {
        sb.write_char(ch)
      }
    }
    text = sb.to_string()
  }
  let delimiter = match delimiter {
    Some(d) if d != "" => d
    _ => self.dialect.cfg.quote_end
  }
  let escaped_delimiter = match escaped_delimiter {
    Some(d) if d != "" => d
    _ => self.escaped_quote_end
  }
  let text = self.replace_line_breaks(text)
  if delimiter == "" {
    text
  } else {
    text.replace_all(old=delimiter, new=escaped_delimiter)
  }
}

///|
pub fn Generator::national_sql(
  self : Generator,
  expression : Expr,
  prefix? : String = "N",
) -> String raise SqlglotError {
  let string = self.sql(Some(literal_string(expression.name())))
  prefix + string
}

///|
pub fn Generator::identifier_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut text = expression.name()
  let lower = py_lower(text)
  let quoted = expression.has("quoted")
  if self.normalize && !quoted {
    text = lower
  }
  if self.identifier_end != "" {
    text = text.replace_all(
      old=self.identifier_end,
      new=self.escaped_identifier_end,
    )
  }
  if quoted ||
    self.dialect.can_quote(expression, identify=self.identify) ||
    self.cfg.reserved_keywords.contains(lower) ||
    (
      !self.dialect.cfg.identifiers_can_start_with_digit &&
      (match g_first_char(text) {
        Some(c) => is_py_digit(c)
        None => false
      })
    ) {
    text = self.identifier_start +
      self.replace_line_breaks(text) +
      self.identifier_end
  }
  text
}

///|
pub fn Generator::null_sql(self : Generator, expression : Expr) -> String {
  ignore((self, expression))
  "NULL"
}

///|
pub fn Generator::boolean_sql(self : Generator, expression : Expr) -> String {
  ignore(self)
  if expression.has("this") {
    "TRUE"
  } else {
    "FALSE"
  }
}

///|
pub fn Generator::var_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this")
}

///|
pub fn Generator::parameter_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  self.cfg.parameter_token + this
}

///|
pub fn Generator::sessionparameter_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let kind = g_pfx("", expression.text("kind"))
  let kind = if kind != "" { kind + "." } else { kind }
  "@@" + kind + this
}

///|
pub fn Generator::placeholder_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if expression.has("this") {
    self.cfg.named_placeholder_token + expression.name()
  } else {
    "?"
  }
}

///|
pub fn Generator::heredoc_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let tag = self.sql_key(expression, "tag")
  "$" + tag + "$" + self.sql_key(expression, "this") + "$" + tag + "$"
}

// ---------------------------------------------------------------------------
// Data types

///|
pub fn Generator::datatypeparam_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let specifier = self.sql_key(expression, "expression")
  let specifier = if specifier != "" && self.cfg.data_type_specifiers_allowed {
    " " + specifier
  } else {
    ""
  }
  this + specifier
}

///|
pub fn Generator::datatype_param_bound_limiter(
  self : Generator,
  expression : Expr,
  type_value : DType,
  defaults : Array[Int],
  bounds : Array[Int?],
) -> Expr raise SqlglotError {
  let params = expression.raw_list("expressions")
  if params.is_empty() {
    if !defaults.is_empty() {
      expression.set(
        "expressions",
        defaults.map(d => mk1(DataTypeParam, literal_int(d))),
      )
    }
    return expression
  }
  if bounds.is_empty() {
    return expression
  }
  for i in 0.. t
        None => continue
      }
    } else {
      param
    }
    let value = if param_value.kind.is_a(Literal) && param_value.is_number() {
      param_value.to_py_float()
    } else {
      None
    }
    match value {
      Some(v) if v > bound.to_double() => {
        self.unsupported(
          "\{type_value.value()} parameter \{param_value.name()} exceeds \{self.dialect_class_name()}'s maximum of \{bound}; capping",
        )
        params[i] = Node(mk1(DataTypeParam, literal_int(bound)))
      }
      _ => ()
    }
  }
  expression
}

///|
pub fn Generator::datatype_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut expression = expression
  let mut nested = ""
  let mut values = ""
  let expr_nested = expression.has("nested")
  let type_value = expression.datatype_this()
  if !expr_nested {
    match type_value {
      Some(tv) =>
        match self.fns.type_param_settings.get(tv) {
          Some((defaults, bounds)) =>
            expression = self.datatype_param_bound_limiter(
              expression, tv, defaults, bounds,
            )
          None => ()
        }
      None => ()
    }
  }
  let interior = if expr_nested && self.pretty {
    self.expressions(
      expression~,
      dynamic=true,
      new_line=true,
      skip_first=true,
      skip_last=true,
    )
  } else {
    self.expressions(expression~, flat=true)
  }
  match type_value {
    Some(tv) if self.cfg.unsupported_types.contains(tv) =>
      self.unsupported(
        "Data type \{tv.value()} is not supported when targeting \{self.dialect_class_name()}",
      )
    _ => ()
  }
  let mut type_sql = ""
  if type_value == Some(DType::USERDEFINED) && expression.has("kind") {
    type_sql = self.sql_key(expression, "kind")
  } else if type_value == Some(DType::CHARACTER_SET) {
    return "CHAR CHARACTER SET " + self.sql_key(expression, "kind")
  } else {
    type_sql = match type_value {
      Some(tv) =>
        match self.cfg.type_mapping.get(tv) {
          Some(m) => m
          None => tv.value()
        }
      None => py_str_value(expression.get("this"))
    }
  }
  if interior != "" {
    if expr_nested {
      nested = self.cfg.struct_delimiter[0] +
        interior +
        self.cfg.struct_delimiter[1]
      if expression.get("values") is Some(_) {
        let (d0, d1) = if type_value == Some(DType::ARRAY) {
          ("[", "]")
        } else {
          ("(", ")")
        }
        values = d0 +
          self.expressions(expression~, key="values", flat=true) +
          d1
      }
    } else if type_value == Some(DType::INTERVAL) {
      nested = " " + interior
    } else {
      nested = "(" + interior + ")"
    }
  }
  type_sql = type_sql + nested + values
  if self.cfg.tz_to_with_time_zone &&
    (
      type_value == Some(DType::TIMETZ) ||
      type_value == Some(DType::TIMESTAMPTZ)
    ) {
    type_sql = type_sql + " WITH TIME ZONE"
  }
  let collate = self.sql_key(expression, "collate")
  if collate != "" {
    type_sql = type_sql + " COLLATE " + collate
  }
  type_sql
}

///|
pub fn Generator::pseudotype_sql(self : Generator, expression : Expr) -> String {
  ignore(self)
  expression.name()
}

///|
pub fn Generator::objectidentifier_sql(
  self : Generator,
  expression : Expr,
) -> String {
  ignore(self)
  expression.name()
}

// ---------------------------------------------------------------------------
// Operators

///|
pub fn Generator::between_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let low = self.sql_key(expression, "low")
  let high = self.sql_key(expression, "high")
  let symmetric = expression.get("symmetric")
  let is_symmetric = match symmetric {
    Some(v) => v.truthy()
    None => false
  }
  if is_symmetric && !self.cfg.supports_between_flags {
    return "(\{this} BETWEEN \{low} AND \{high} OR \{this} BETWEEN \{high} AND \{low})"
  }
  let flag = if is_symmetric {
    " SYMMETRIC"
  } else if symmetric is Some(Bool(false)) && self.cfg.supports_between_flags {
    " ASYMMETRIC"
  } else {
    ""
  }
  "\{this} BETWEEN\{flag} \{low} AND \{high}"
}

///|
pub fn Generator::bracket_offset_expressions(
  self : Generator,
  expression : Expr,
  index_offset? : Int,
) -> Array[Expr] raise SqlglotError {
  if expression.has("json_access") {
    return expression.expressions()
  }
  let base = match index_offset {
    Some(i) if i != 0 => i
    _ => self.dialect.cfg.index_offset
  }
  let offset = match expression.get("offset") {
    Some(Int(i)) => i.to_int()
    Some(Bool(b)) => if b { 1 } else { 0 }
    Some(Node(n)) =>
      match n.to_py_int() {
        Some(v) => v.to_int()
        None => 0
      }
    _ => 0
  }
  gen_apply_index_offset(
    expression.this_(),
    expression.expressions(),
    base - offset,
    self.dialect,
  )
}

///|
pub fn Generator::bracket_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = self.bracket_offset_expressions(expression)
  let parts = []
  for e in expressions {
    parts.push(self.sql(Some(e)))
  }
  self.sql_key(expression, "this") + "[" + parts.join(", ") + "]"
}

///|
pub fn Generator::all_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = self.sql_key(expression, "this")
  match expression.this() {
    Some(t) if t.kind.is_any([Tuple, Paren]) => ()
    _ => this = self.wrap_str(this)
  }
  "ALL " + this
}

///|
pub fn Generator::any_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = self.sql_key(expression, "this")
  match expression.this() {
    Some(t) if t.kind.is_any([Select, SetOperation, Paren]) => {
      if t.kind.is_any([Select, SetOperation]) {
        this = self.wrap_str(this)
      }
      "ANY" + this
    }
    _ => "ANY " + this
  }
}

///|
pub fn Generator::exists_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  "EXISTS" + self.wrap(expression)
}

///|
pub fn Generator::case_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let statements = [if this != "" { "CASE " + this } else { "CASE" }]
  for e in expression.list("ifs") {
    statements.push("WHEN " + self.sql_key(e, "this"))
    statements.push("THEN " + self.sql_key(e, "true"))
  }
  let default = self.sql_key(expression, "default")
  if default != "" {
    statements.push("ELSE " + default)
  }
  statements.push("END")
  if self.pretty && self.too_wide(statements) {
    return self.indent(statements.join("\n"), skip_first=true, skip_last=true)
  }
  statements.join(" ")
}

///|
pub fn Generator::if_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.call_method(
    Case,
    mk(Case, [("ifs", [expression]), ("default", expression.get("false"))]),
  )
}

///|
pub fn Generator::alias_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let alias_ = g_pfx(" AS ", self.sql_key(expression, "alias"))
  self.sql_key(expression, "this") + alias_
}

///|
pub fn Generator::pivotalias_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let alias_ = expression.arg("alias")
  let pivot = match expression.parent {
    Some(p) => p.parent
    None => None
  }
  match (pivot, alias_) {
    (Some(pv), Some(alias_)) if pv.kind.is_a(Pivot) && pv.has("unpivot") => {
      let identifier_alias = alias_.kind.is_a(Identifier)
      let literal_alias = alias_.kind.is_a(Literal)
      if identifier_alias && !self.cfg.unpivot_aliases_are_identifiers {
        alias_.replace(Some(literal_string(alias_.output_name()))) |> ignore
      } else if !identifier_alias &&
        literal_alias &&
        self.cfg.unpivot_aliases_are_identifiers {
        alias_.replace(Some(to_identifier(alias_.output_name()))) |> ignore
      }
    }
    _ => ()
  }
  self.call_method(Alias, expression)
}

///|
pub fn Generator::aliases_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this") +
  " AS (" +
  self.expressions(expression~, flat=true) +
  ")"
}

///|
pub fn Generator::atindex_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this") +
  " AT " +
  self.sql_key(expression, "expression")
}

///|
pub fn Generator::attimezone_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this") +
  " AT TIME ZONE " +
  self.sql_key(expression, "zone")
}

///|
pub fn Generator::fromtimezone_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this") +
  " AT TIME ZONE " +
  self.sql_key(expression, "zone") +
  " AT TIME ZONE 'UTC'"
}

///|
pub fn Generator::paren_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let sql = self.seg(self.indent(self.sql_key(expression, "this")), sep="")
  "(" + sql + self.seg(")", sep="")
}

///|
pub fn Generator::neg_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this_sql = self.sql_key(expression, "this")
  let sep = if this_sql.has_prefix("-") { " " } else { "" }
  "-" + sep + this_sql
}

///|
pub fn Generator::not_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  "NOT " + self.sql_key(expression, "this")
}

///|
pub fn Generator::add_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "+")
}

///|
pub fn Generator::and_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.connector_sql(expression, "AND")
}

///|
pub fn Generator::or_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.connector_sql(expression, "OR")
}

///|
pub fn Generator::xor_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.connector_sql(expression, "XOR")
}

///|
pub fn Generator::bitwiseand_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "&")
}

///|
pub fn Generator::bitwiseleftshift_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "<<")
}

///|
pub fn Generator::bitwisenot_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  "~" + self.sql_key(expression, "this")
}

///|
pub fn Generator::bitwiseor_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "|")
}

///|
pub fn Generator::bitwiserightshift_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, ">>")
}

///|
pub fn Generator::bitwisexor_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "^")
}

///|
/// Virtual `cast_sql(expression, safe_prefix)`.
pub fn Generator::cast_sql_v(
  self : Generator,
  expression : Expr,
  safe_prefix? : String,
) -> String raise SqlglotError {
  match self.fns.hooks.cast_sql {
    Some(f) => f(self, expression, safe_prefix)
    None =>
      match self.fns.methods.get(Cast) {
        Some(f) if safe_prefix is None => f(self, expression)
        _ => self.cast_sql(expression, safe_prefix?)
      }
  }
}

///|
pub fn Generator::cast_sql(
  self : Generator,
  expression : Expr,
  safe_prefix? : String,
) -> String raise SqlglotError {
  let format_sql = g_pfx(" FORMAT ", self.sql_key(expression, "format"))
  let to_sql = g_pfx(" ", self.sql_key(expression, "to"))
  let action = g_pfx(" ", self.sql_key(expression, "action"))
  let default = g_around(
    " DEFAULT ",
    self.sql_key(expression, "default"),
    " ON CONVERSION ERROR",
  )
  g_opt_str(safe_prefix) +
  "CAST(" +
  self.sql_key(expression, "this") +
  " AS" +
  to_sql +
  default +
  format_sql +
  action +
  ")"
}

///|
pub fn Generator::trycast_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.cast_sql_v(expression, safe_prefix="TRY_")
}

///|
pub fn Generator::jsoncast_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.cast_sql_v(expression)
}

///|
pub fn Generator::collate_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.cfg.collate_is_func {
    return self.function_fallback_sql(expression)
  }
  self.binary(expression, "COLLATE")
}

///|
pub fn Generator::intdiv_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql(
    Some(
      mk(Cast, [
        ("this", mk2(Div, expression.get("this"), expression.get("expression"))),
        ("to", datatype_of(DType::INT)),
      ]),
    ),
  )
}

///|
pub fn Generator::dpipe_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.dialect.cfg.strict_string_concat && expression.has("safe") {
    let args : Array[&SqlArg] = []
    for e in expression.flatten() {
      args.push(exp_cast(e, DType::TEXT))
    }
    return self.func("CONCAT", args)
  }
  self.binary(expression, "||")
}

///|
pub fn Generator::div_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let l = expression.this_()
  let r = expression.expression_()
  if !self.dialect.cfg.safe_division && expression.has("safe") {
    r.replace(Some(mk2(Nullif, r.copy(), literal_int(0)))) |> ignore
  }
  if self.dialect.cfg.typed_division && !expression.has("typed") {
    if !l.is_type(dtype_real_types) && !r.is_type(dtype_real_types) {
      l.replace(Some(exp_cast(l.copy(), DType::DOUBLE))) |> ignore
    }
  } else if !self.dialect.cfg.typed_division && expression.has("typed") {
    if l.is_type(dtype_integer_types) && r.is_type(dtype_integer_types) {
      return self.sql(Some(exp_cast(exp_div(l, r), DType::BIGINT)))
    }
  }
  self.binary(expression, "/")
}

///|
pub fn Generator::safedivide_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let n = exp_wrap(expression.this_(), Binary)
  let d = exp_wrap(expression.expression_(), Binary)
  self.sql(
    Some(
      mk(If, [
        ("this", exp_neq(d, literal_int(0))),
        ("true", exp_div(n, d)),
        ("false", mk0(Null)),
      ]),
    ),
  )
}

///|
pub fn Generator::overlaps_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "OVERLAPS")
}

///|
pub fn Generator::distance_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "<->")
}

///|
pub fn Generator::distancend_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "<<->>")
}

///|
pub fn Generator::dot_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this") +
  "." +
  self.sql_key(expression, "expression")
}

///|
pub fn Generator::eq_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "=")
}

///|
pub fn Generator::propertyeq_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, ":=")
}

///|
pub fn Generator::escape_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match expression.this() {
    Some(this) if this.kind.is_any([Like, ILike]) &&
      (match this.expression() {
        Some(x) => x.kind.is_any([All, Any])
        None => false
      }) &&
      !self.cfg.supports_like_quantifiers =>
      self.like_sql_inner(this, escape=expression)
    _ => self.binary(expression, "ESCAPE")
  }
}

///|
pub fn Generator::glob_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "GLOB")
}

///|
pub fn Generator::gt_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, ">")
}

///|
pub fn Generator::gte_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, ">=")
}

///|
pub fn Generator::is_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let negate = expression.has("negate")
  match expression.expression() {
    Some(b) if !self.cfg.is_bool_allowed && b.kind.is_a(Boolean) => {
      let positive = b.has("this") != negate
      let this = expression.this_()
      return self.sql(Some(if positive { this } else { exp_not(this) }))
    }
    _ => ()
  }
  self.binary(expression, if negate { "IS NOT" } else { "IS" })
}

///|
/// Python `_like_sql(expression, escape)`.
pub fn Generator::like_sql_inner(
  self : Generator,
  expression : Expr,
  escape? : Expr,
) -> String raise SqlglotError {
  let this = expression.get("this")
  let rhs = expression.expression()
  let (exp_class, op0) = if expression.kind.is_a(Like) {
    (Kind::Like, "LIKE")
  } else {
    (Kind::ILike, "ILIKE")
  }
  let mut op = op0
  if expression.has("negate") {
    op = "NOT " + op
  }
  match rhs {
    Some(rhs) if rhs.kind.is_any([All, Any]) &&
      !self.cfg.supports_like_quantifiers => {
      let inner = rhs.this_().unnest()
      let exprs = if inner.kind.is_a(Tuple) {
        inner.expressions()
      } else {
        [inner]
      }
      let negate = expression.get("negate")
      let make_like = fn(expr : Expr) -> Expr {
        let like = mk(exp_class, [
          ("this", this),
          ("expression", expr),
          ("negate", negate),
        ])
        match escape {
          Some(esc) =>
            mk(Escape, [
              ("this", like),
              ("expression", esc.expression_().copy()),
            ])
          None => like
        }
      }
      let mut like_expr = make_like(exprs[0])
      for i in 1.. esc.parent
        None => expression.parent
      }
      match parent {
        Some(p) if !(p.kind.is_a(like_expr.kind) || p.kind.is_a(Paren)) &&
          p.kind.is_a(Condition) => like_expr = exp_paren(like_expr, copy=false)
        _ => ()
      }
      return self.sql(Some(like_expr))
    }
    _ => ()
  }
  self.binary(expression, op)
}

///|
pub fn Generator::like_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.like_sql_inner(expression)
}

///|
pub fn Generator::ilike_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.like_sql_inner(expression)
}

///|
pub fn Generator::match_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "MATCH")
}

///|
pub fn Generator::similarto_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "SIMILAR TO")
}

///|
pub fn Generator::lt_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "<")
}

///|
pub fn Generator::lte_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "<=")
}

///|
pub fn Generator::mod_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expr = self.sql_key(expression, "expression")
  let sql = this +
    " " +
    self.maybe_comment(self.cfg.mod_operator, comments?=expression.comments) +
    " " +
    expr
  match expression.parent {
    Some(p) if p.kind.is_any(self.cfg.mod_paren_parent_types) &&
      (match p.expression() {
        Some(pe) => physical_equal(pe, expression)
        None => false
      }) => "(" + sql + ")"
    _ => sql
  }
}

///|
pub fn Generator::mul_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "*")
}

///|
pub fn Generator::neq_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "<>")
}

///|
pub fn Generator::nullsafeeq_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "IS NOT DISTINCT FROM")
}

///|
pub fn Generator::nullsafeneq_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "IS DISTINCT FROM")
}

///|
pub fn Generator::sub_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "-")
}

///|
pub fn Generator::kwarg_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.binary(expression, "=>")
}

///|
pub fn Generator::in_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let is_global = g_when(expression.has("is_global"), " GLOBAL")
  let in_sql = match
    (expression.arg("query"), expression.arg("unnest"), expression.arg("field")) {
    (Some(query), _, _) => self.sql(Some(query))
    (None, Some(unnest), _) => self.in_unnest_op(unnest)
    (None, None, Some(field)) => self.sql(Some(field))
    _ =>
      "(" +
      self.expressions(
        expression~,
        dynamic=true,
        new_line=true,
        skip_first=true,
        skip_last=true,
      ) +
      ")"
  }
  self.sql_key(expression, "this") + is_global + " IN " + in_sql
}

///|
/// Virtual `in_unnest_op`.
pub fn Generator::in_unnest_op(
  self : Generator,
  unnest : Expr,
) -> String raise SqlglotError {
  match self.fns.hooks.in_unnest_op {
    Some(f) => f(self, unnest)
    None => self.in_unnest_op_base(unnest)
  }
}

///|
pub fn Generator::in_unnest_op_base(
  self : Generator,
  unnest : Expr,
) -> String raise SqlglotError {
  "(SELECT " + self.sql(Some(unnest)) + ")"
}

///|
pub fn Generator::interval_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let include_keyword = !self.cfg.auto_refresh_bare_intervals ||
    !(match expression.find_ancestor([AutoRefreshProperty, Select]) {
      Some(a) => a.kind.is_a(AutoRefreshProperty)
      None => false
    })
  let mut interval_keyword = if include_keyword { "INTERVAL" } else { "" }
  let unit_expression = expression.arg("unit")
  let mut unit = match unit_expression {
    Some(u) => self.sql(Some(u))
    None => ""
  }
  if !self.cfg.interval_allows_plural_form {
    unit = match self.cfg.time_part_singulars.get(unit) {
      Some(s) => s
      None => unit
    }
  }
  let unit = g_pfx(" ", unit)
  if self.cfg.single_string_interval {
    let this = match expression.this() {
      Some(t) => t.name()
      None => ""
    }
    if this != "" {
      interval_keyword = g_pfx("", interval_keyword)
      if interval_keyword != "" {
        interval_keyword = interval_keyword + " "
      }
      match unit_expression {
        Some(u) if u.kind.is_a(IntervalSpan) =>
          return interval_keyword + "'" + this + "'" + unit
        _ => ()
      }
      return interval_keyword + "'" + this + unit + "'"
    }
    return interval_keyword + unit
  }
  let mut this = self.sql_key(expression, "this")
  if this != "" {
    let this_expr = expression.this()
    if !include_keyword &&
      (match this_expr {
        Some(t) => t.is_string()
        None => false
      }) {
      this = this_expr.unwrap().name()
    }
    let unwrapped = match this_expr {
      Some(t) => t.kind.is_any(self.cfg.unwrapped_interval_values)
      None => false
    }
    if !unwrapped {
      this = "(" + this + ")"
    }
    if include_keyword {
      this = " " + this
    }
  }
  interval_keyword + this + unit
}

///|
pub fn Generator::slice_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let step = self.sql_key(expression, "step")
  let end = self.sql(expression.expression())
  let begin = self.sql(expression.this())
  let sql = if step != "" { end + ":" + step } else { end }
  if sql != "" {
    begin + ":" + sql
  } else {
    begin + ":"
  }
}

///|
pub fn Generator::lambda_sql_v(
  self : Generator,
  expression : Expr,
  arrow_sep? : String = "->",
  wrap? : Bool = true,
) -> String raise SqlglotError {
  match self.fns.hooks.lambda_sql {
    Some(f) => f(self, expression, arrow_sep, wrap)
    None => self.lambda_sql(expression, arrow_sep~, wrap~)
  }
}

///|
pub fn Generator::lambda_sql(
  self : Generator,
  expression : Expr,
  arrow_sep? : String = "->",
  wrap? : Bool = true,
) -> String raise SqlglotError {
  let mut args = self.expressions(expression~, flat=true)
  if wrap && py_split(args, ",").length() > 1 {
    args = "(" + args + ")"
  }
  args + " " + arrow_sep + " " + self.sql_key(expression, "this")
}