// Small helpers shared by the per-dialect typing modules (typing_.mbt).

///|
/// Python `self._maybe_coerce(type1, type2)` where either side may be `None`
/// (an expression without a type).
fn TypeAnnotator::maybe_coerce_opt(
  self : TypeAnnotator,
  type1 : TType?,
  type2 : TType?,
) -> TType? {
  match (type1, type2) {
    (Some(a), Some(b)) => Some(self.maybe_coerce(a, b))
    (None, None) => None
    (Some(a), None) =>
      match a {
        T(t) if !t.expressions().is_empty() => Some(a)
        _ =>
          match a.this() {
            None => Some(a)
            // `None in coerces_to[...]` is always false, so type1 wins unless it's NULL
            Some(NULL) => None
            Some(d) => Some(D(d))
          }
      }
    (None, Some(b)) =>
      match b {
        T(t) if !t.expressions().is_empty() => Some(b)
        _ =>
          match b.this() {
            None => Some(b)
            Some(UNKNOWN) => Some(D(UNKNOWN))
            Some(_) => None
          }
      }
  }
}

///|
/// Python `self._set_type(expression, other.type)`.
fn TypeAnnotator::set_type_of(
  self : TypeAnnotator,
  expression : @core.Expr,
  other : @core.Expr?,
) -> Unit {
  self.set_type_expr(expression, other.bind(o => o.get_type()))
}

///|
/// Python `expr.is_type(*dtypes)` for an optional expression (`None` -> false).
fn opt_is_type(e : @core.Expr?, dtypes : Array[@core.DType]) -> Bool {
  match e {
    Some(x) => x.is_type(dtypes)
    None => false
  }
}

///|
/// Python `exp.DataType.from_str(sql, dialect=name)`; falls back to `fallback` when
/// the named dialect isn't registered.
fn datatype_from_str_in(
  sql : String,
  name : String,
  fallback : @core.Dialect,
) -> @core.Expr raise @core.SqlglotError {
  let dialect = match @core.get_dialect(name) {
    Some(d) => d
    None => fallback
  }
  @core.datatype_from_str(sql, dialect~)
}

///|
/// An annotator setting the type parsed from `sql` in the dialect `name`
/// (Python `lambda self, e: self._set_type(e, exp.DataType.from_str(sql, dialect=name))`).
fn set_type_from_str(sql : String, name : String) -> TypeSpec {
  Annotator((s, e) => {
    s.set_type_expr(e, Some(datatype_from_str_in(sql, name, s.dialect)))
  })
}

///|
/// Python `{"returns": exp.DataType.from_str(sql)}` (base dialect).
fn returns_from_str(sql : String) -> TypeSpec {
  Returns(T(try! @core.datatype_from_str(sql)))
}

///|
/// The integer value of a DataType parameter (Python `param.this.to_py()`).
fn datatype_param_int(param : @core.Expr?, default : Int) -> Int {
  match param {
    Some(p) => {
      let v = match p.this() {
        Some(t) => t.to_py_int()
        None => p.to_py_int()
      }
      match v {
        Some(i) => i.to_int()
        None => default
      }
    }
    None => default
  }
}