// AST statements (Stmt) and the WHERE-clause expression tree (Expr).
//
// Types that contain &@moonpg.ToValue trait objects do NOT derive Debug/Eq
// because trait objects are not comparable or printable.

///|
/// An item in the SELECT list — either a column reference or an aggregate call.
pub(all) enum SelectExpr {
  Col(ColumnRef)
  Agg(Aggregation)
} derive(Debug, Eq)

///|
/// A SELECT statement.
pub(all) struct SelectStmt {
  table : String
  columns : Array[SelectExpr] // empty = SELECT *
  where_clause : Expr?
  joins : Array[JoinClause]
  group_by : Array[ColumnRef]
  having : Expr?
  order_by : Array[OrderByClause]
  limit : Int?
  offset : Int?
  distinct : Bool
  distinct_on : Array[ColumnRef]
}

///|
/// A WHERE clause expression — a tree of comparisons joined by AND / OR / NOT.
/// Values are stored as &@moonpg.ToValue trait objects, allowing any moonpg-compatible
/// type to be bound as a parameter without going through a foxql Value enum.
pub(all) enum Expr {
  Empty
  Compare(ColumnRef, Operator, &@moonpg.ToValue)
  CompareCols(ColumnRef, Operator, ColumnRef)
  And(Array[Expr])
  Or(Array[Expr])
  Not(Expr)
  InList(ColumnRef, Array[&@moonpg.ToValue])
  InSelect(ColumnRef, SelectStmt)
  Between(ColumnRef, &@moonpg.ToValue, &@moonpg.ToValue)
  IsNull(ColumnRef)
  IsNotNull(ColumnRef)
  AggCompare(Aggregation, Operator, &@moonpg.ToValue)
  CompareSelect(ColumnRef, Operator, SelectStmt)
  Raw(String, Array[&@moonpg.ToValue])
}

///|
/// A WHERE/HAVING/JOIN filter — business-layer-friendly alias for Expr.
pub type Filter = Expr

///|
/// Combine two expressions with AND.  Flattens nested And for cleaner SQL.
/// `Empty` is the identity: `Empty.and_(x)` → `x`, `x.and_(Empty)` → `x`.
pub fn Expr::and_(self : Expr, other : Expr) -> Expr {
  match self {
    Expr::Empty => other
    Expr::And(arr) => match other {
      Expr::Empty => self
      _ => {
        arr.push(other)
        Expr::And(arr)
      }
    }
    _ => match other {
      Expr::Empty => self
      _ => Expr::And([self, other])
    }
  }
}

///|
/// Combine two expressions with OR.  Flattens nested Or for cleaner SQL.
/// `Empty` is the identity: `Empty.or_(x)` → `x`, `x.or_(Empty)` → `x`.
pub fn Expr::or_(self : Expr, other : Expr) -> Expr {
  match self {
    Expr::Empty => other
    Expr::Or(arr) => match other {
      Expr::Empty => self
      _ => {
        arr.push(other)
        Expr::Or(arr)
      }
    }
    _ => match other {
      Expr::Empty => self
      _ => Expr::Or([self, other])
    }
  }
}

///|
/// Wrap an expression with NOT.  `not_(Empty)` → `Empty`.
pub fn not_(expr : Expr) -> Expr {
  match expr {
    Expr::Empty => Expr::Empty
    _ => Expr::Not(expr)
  }
}

///|
/// The empty filter — identity element for AND/OR.  Use as a starting point
/// when building filters from many optional conditions.
pub fn empty() -> Filter {
  Expr::Empty
}

///|
/// An INSERT statement.
pub(all) struct InsertStmt {
  table : String
  columns : Array[ColumnRef]
  values : Array[Array[&@moonpg.ToValue]]
  on_conflict : OnConflict?
  returning : Array[ColumnRef]?
}

///|
pub(all) enum OnConflict {
  DoNothing(String)
  DoUpdate(String, Array[SetClause])
}

///|
pub(all) struct SetClause {
  column : ColumnRef
  value : &@moonpg.ToValue
}

///|
/// An UPDATE statement.  where_clause is non-optional.
pub(all) struct UpdateStmt {
  table : String
  set_clauses : Array[SetClause]
  where_clause : Expr
  returning : Array[ColumnRef]?
}

///|
/// A DELETE statement.  where_clause is non-optional.
pub(all) struct DeleteStmt {
  table : String
  where_clause : Expr
  returning : Array[ColumnRef]?
}

///|
/// An INNER JOIN clause.
pub(all) struct JoinClause {
  table : String
  on : Expr
}

///|
/// ORDER BY direction.
pub(all) enum OrderDirection {
  Asc
  Desc
} derive(Debug, Eq)

///|
pub(all) struct OrderByClause {
  column : ColumnRef
  direction : OrderDirection
} derive(Debug, Eq)

///|
/// An aggregation function call.
pub(all) enum Aggregation {
  Count(ColumnRef?)
  Sum(ColumnRef)
  Avg(ColumnRef)
  Min(ColumnRef)
  Max(ColumnRef)
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Aggregation operators (for HAVING clauses)
// ---------------------------------------------------------------------------

///|
pub fn[T : @moonpg.ToValue] Aggregation::gt(
  self : Aggregation,
  value : T,
) -> Expr {
  Expr::AggCompare(self, Operator::Gt, value)
}

///|
pub fn[T : @moonpg.ToValue] Aggregation::gte(
  self : Aggregation,
  value : T,
) -> Expr {
  Expr::AggCompare(self, Operator::Gte, value)
}

///|
pub fn[T : @moonpg.ToValue] Aggregation::lt(
  self : Aggregation,
  value : T,
) -> Expr {
  Expr::AggCompare(self, Operator::Lt, value)
}

///|
pub fn[T : @moonpg.ToValue] Aggregation::lte(
  self : Aggregation,
  value : T,
) -> Expr {
  Expr::AggCompare(self, Operator::Lte, value)
}

///|
pub fn[T : @moonpg.ToValue] Aggregation::eq(
  self : Aggregation,
  value : T,
) -> Expr {
  Expr::AggCompare(self, Operator::Eq, value)
}

///|
pub fn[T : @moonpg.ToValue] Aggregation::neq(
  self : Aggregation,
  value : T,
) -> Expr {
  Expr::AggCompare(self, Operator::Neq, value)
}