// The expression layer.
//
// `RawExpr` is the tree the emitter walks. `Expr[T]` and `Column[T]` are thin
// typed wrappers over it: the `T` is a phantom that never reaches the SQL and
// exists only to keep comparisons honest.

///|
/// Untyped expression tree. Whatever type the wrappers above it carried has
/// been erased by the time an expression reaches here.
pub enum RawExpr {
  Col(tbl~ : String, name~ : String)
  Lit(SqlValue)
  Bin(BinOp, RawExpr, RawExpr)
  Unary(String, RawExpr)
  InList(RawExpr, Array[SqlValue])
  Agg(String, RawExpr?)
} derive(Debug)

///|
pub enum BinOp {
  Eq
  Ne
  Gt
  Gte
  Lt
  Lte
  And
  Or
} derive(Debug, Eq)

///|
pub impl Show for BinOp with fn to_string(self) -> String {
  match self {
    BinOp::Eq => "="
    BinOp::Ne => "<>"
    BinOp::Gt => ">"
    BinOp::Gte => ">="
    BinOp::Lt => "<"
    BinOp::Lte => "<="
    BinOp::And => "AND"
    BinOp::Or => "OR"
  }
}

///|
/// A typed expression. `T` is what the expression evaluates to in SQL terms;
/// nothing at runtime carries it.
pub struct Expr[T](RawExpr)

///|
pub fn[T] Expr::raw(self : Expr[T]) -> RawExpr {
  let Expr(r) = self
  r
}

///|
/// A reference to one column of one table.
///
/// Separate from `Expr[T]` because a column also has a name, which `sel`,
/// `Binding` and `Update::set` need and a general expression cannot supply.
pub struct Column[T] {
  /// Alias of the table the column belongs to, as it appears in `FROM`.
  tbl : String
  /// Column name in the database.
  name : String
}

///|
pub fn[T] Column::new(tbl~ : String, name~ : String) -> Column[T] {
  { tbl, name }
}

///|
pub fn[T] Column::expr(self : Column[T]) -> Expr[T] {
  Expr(Col(tbl=self.tbl, name=self.name))
}

///|
pub fn[T] Column::raw(self : Column[T]) -> RawExpr {
  self.expr().raw()
}

///|
/// Reinterpret a column as nullable.
///
/// The right-hand side of a LEFT JOIN can be absent, so its columns decode as
/// `T?` even though the entity declares them as `T`.
pub fn[T] Column::nullable(self : Column[T]) -> Column[T?] {
  { tbl: self.tbl, name: self.name }
}

// Comparisons.
//
// The six operators differ only in which `BinOp` they carry, so the shared
// half — lifting the value into a bound literal — is written once in `cmp`.

///|
fn[T : SqlEncode] Expr::cmp(self : Expr[T], op : BinOp, v : T) -> Expr[Bool] {
  Expr(Bin(op, self.raw(), Lit(v.to_sql_value())))
}

///|
pub fn[T : SqlEncode] Expr::eq(self : Expr[T], v : T) -> Expr[Bool] {
  self.cmp(BinOp::Eq, v)
}

///|
pub fn[T : SqlEncode] Expr::ne(self : Expr[T], v : T) -> Expr[Bool] {
  self.cmp(BinOp::Ne, v)
}

///|
pub fn[T : SqlEncode + SqlOrd] Expr::gt(self : Expr[T], v : T) -> Expr[Bool] {
  self.cmp(BinOp::Gt, v)
}

///|
pub fn[T : SqlEncode + SqlOrd] Expr::gte(self : Expr[T], v : T) -> Expr[Bool] {
  self.cmp(BinOp::Gte, v)
}

///|
pub fn[T : SqlEncode + SqlOrd] Expr::lt(self : Expr[T], v : T) -> Expr[Bool] {
  self.cmp(BinOp::Lt, v)
}

///|
pub fn[T : SqlEncode + SqlOrd] Expr::lte(self : Expr[T], v : T) -> Expr[Bool] {
  self.cmp(BinOp::Lte, v)
}

///|
/// Compare two expressions, as in a join condition.
pub fn[T] Expr::eq_col(self : Expr[T], other : Expr[T]) -> Expr[Bool] {
  Expr(Bin(BinOp::Eq, self.raw(), other.raw()))
}

///|
pub fn[T : SqlEncode] Expr::in_(self : Expr[T], vs : Array[T]) -> Expr[Bool] {
  Expr(InList(self.raw(), vs.map(v => v.to_sql_value())))
}

///|
pub fn[T] Expr::is_none(self : Expr[T?]) -> Expr[Bool] {
  Expr(Unary("IS NULL", self.raw()))
}

///|
pub fn[T] Expr::is_some(self : Expr[T?]) -> Expr[Bool] {
  Expr(Unary("IS NOT NULL", self.raw()))
}

///|
pub impl BitAnd for Expr[Bool] with fn land(self, other) {
  Expr(Bin(BinOp::And, self.raw(), other.raw()))
}

///|
pub impl BitOr for Expr[Bool] with fn lor(self, other) {
  Expr(Bin(BinOp::Or, self.raw(), other.raw()))
}

// Ordering.

///|
pub(all) enum Dir {
  Asc
  Desc
} derive(Debug, Eq)

///|
pub impl Show for Dir with fn to_string(self) -> String {
  match self {
    Asc => "ASC"
    Desc => "DESC"
  }
}

///|
/// One key of an ORDER BY clause.
pub struct OrderKey {
  expr : RawExpr
  dir : Dir
}

///|
pub fn[T : SqlOrd] Expr::asc(self : Expr[T]) -> OrderKey {
  { expr: self.raw(), dir: Dir::Asc }
}

///|
pub fn[T : SqlOrd] Expr::desc(self : Expr[T]) -> OrderKey {
  { expr: self.raw(), dir: Dir::Desc }
}

// Column comparisons.
//
// A column is the overwhelmingly common left-hand side of a comparison, so it
// gets the operators directly rather than making every call site lift it with
// `expr()` first. Each one is the `Expr` operator with the column lifted.

///|
pub fn[T : SqlEncode] Column::eq(self : Column[T], v : T) -> Expr[Bool] {
  self.expr().eq(v)
}

///|
pub fn[T : SqlEncode] Column::ne(self : Column[T], v : T) -> Expr[Bool] {
  self.expr().ne(v)
}

///|
pub fn[T : SqlEncode + SqlOrd] Column::gt(
  self : Column[T],
  v : T,
) -> Expr[Bool] {
  self.expr().gt(v)
}

///|
pub fn[T : SqlEncode + SqlOrd] Column::gte(
  self : Column[T],
  v : T,
) -> Expr[Bool] {
  self.expr().gte(v)
}

///|
pub fn[T : SqlEncode + SqlOrd] Column::lt(
  self : Column[T],
  v : T,
) -> Expr[Bool] {
  self.expr().lt(v)
}

///|
pub fn[T : SqlEncode + SqlOrd] Column::lte(
  self : Column[T],
  v : T,
) -> Expr[Bool] {
  self.expr().lte(v)
}

///|
/// Compare two columns, as in a join condition.
pub fn[T] Column::eq_col(self : Column[T], other : Column[T]) -> Expr[Bool] {
  self.expr().eq_col(other.expr())
}

///|
pub fn[T : SqlEncode] Column::in_(
  self : Column[T],
  vs : Array[T],
) -> Expr[Bool] {
  self.expr().in_(vs)
}

///|
pub fn[T] Column::is_none(self : Column[T?]) -> Expr[Bool] {
  self.expr().is_none()
}

///|
pub fn[T] Column::is_some(self : Column[T?]) -> Expr[Bool] {
  self.expr().is_some()
}

///|
pub fn[T : SqlOrd] Column::asc(self : Column[T]) -> OrderKey {
  self.expr().asc()
}

///|
pub fn[T : SqlOrd] Column::desc(self : Column[T]) -> OrderKey {
  self.expr().desc()
}