// Column[T] — a statically-typed column reference.
//
// The type parameter T controls which operators are available at compile time:
//
//   Column[Int] / Column[Double] → eq, neq, gt, gte, lt, lte, between, in_, is_null
//   Column[String]               → eq, neq, like, not_like, in_, is_null
//   Column[Bool]                 → eq, neq, is_null

// ---------------------------------------------------------------------------
// Operator marker traits
// ---------------------------------------------------------------------------

///|
/// Trait for types that support comparison operators (gt, gte, lt, lte, between).
pub trait Comparable {
  fn compare(Self) -> Unit
}

///|
/// Trait for types that support string operators (like, not_like).
pub trait StringMatchable {
  fn str_match(Self) -> Unit
}

///|
pub impl Comparable for Int with fn compare(_self : Int) -> Unit {

}

///|
pub impl Comparable for Double with fn compare(_self : Double) -> Unit {

}

///|
pub impl StringMatchable for String with fn str_match(_self : String) -> Unit {

}

// ---------------------------------------------------------------------------

///|
/// A typed column reference in a specific table.
pub(all) struct Column[T] {
  name : String
  table : String
  sql_type : @types.SqlType
  _marker : T?
} derive(Debug, Eq)

///|
/// Trait that erases T — exposes column identity for heterogeneous column lists.
pub trait AnyColumn {
  fn name(Self) -> String
  fn table_name(Self) -> String
  fn sql_type(Self) -> @types.SqlType
}

///|
pub fn &AnyColumn::to_ref(self : &AnyColumn) -> @ast.ColumnRef {
  @ast.ColumnRef::{ name: self.name(), table: self.table_name() }
}

///|
pub fn &AnyColumn::asc(self : &AnyColumn) -> @ast.OrderByClause {
  @ast.OrderByClause::{
    column: self.to_ref(),
    direction: @ast.OrderDirection::Asc,
  }
}

///|
pub fn &AnyColumn::desc(self : &AnyColumn) -> @ast.OrderByClause {
  @ast.OrderByClause::{
    column: self.to_ref(),
    direction: @ast.OrderDirection::Desc,
  }
}

///|
pub impl[T] AnyColumn for Column[T] with fn name(self : Column[T]) -> String {
  self.name
}

///|
pub impl[T] AnyColumn for Column[T] with fn table_name(self : Column[T]) -> String {
  self.table
}

///|
pub impl[T] AnyColumn for Column[T] with fn sql_type(self : Column[T]) -> @types.SqlType {
  self.sql_type
}

///|
pub impl AnyColumn for @ast.ColumnRef with fn name(self : @ast.ColumnRef) -> String {
  self.name
}

///|
pub impl AnyColumn for @ast.ColumnRef with fn table_name(self : @ast.ColumnRef) -> String {
  self.table
}

///|
pub impl AnyColumn for @ast.ColumnRef with fn sql_type(_self : @ast.ColumnRef) -> @types.SqlType {
  "text"
}

///|
/// Create a new column reference.
pub fn[T] Column::new(
  name : String,
  table : String,
  sql_type : @types.SqlType,
) -> Column[T] {
  Column::{ name, table, sql_type, _marker: None }
}

///|
/// Produce a type-erased ColumnRef.
pub fn[T] Column::to_ref(self : Column[T]) -> @ast.ColumnRef {
  @ast.ColumnRef::{ name: self.name, table: self.table }
}

///|
/// Sort ascending.
pub fn[T] Column::asc(self : Column[T]) -> @ast.OrderByClause {
  @ast.OrderByClause::{
    column: self.to_ref(),
    direction: @ast.OrderDirection::Asc,
  }
}

///|
/// Sort descending.
pub fn[T] Column::desc(self : Column[T]) -> @ast.OrderByClause {
  @ast.OrderByClause::{
    column: self.to_ref(),
    direction: @ast.OrderDirection::Desc,
  }
}

// ---------------------------------------------------------------------------
// Equality operators
// ---------------------------------------------------------------------------

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

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

///|
/// Compare two columns for equality (for JOIN ON).
pub fn[T] Column::eq_col(self : Column[T], other : Column[T]) -> @ast.Expr {
  @ast.Expr::CompareCols(self.to_ref(), @ast.Operator::Eq, other.to_ref())
}

// ---------------------------------------------------------------------------
// Null operators
// ---------------------------------------------------------------------------

///|
pub fn[T] Column::is_null(self : Column[T]) -> @ast.Expr {
  @ast.Expr::IsNull(self.to_ref())
}

///|
pub fn[T] Column::is_not_null(self : Column[T]) -> @ast.Expr {
  @ast.Expr::IsNotNull(self.to_ref())
}

// ---------------------------------------------------------------------------
// Comparison operators
// ---------------------------------------------------------------------------

///|
pub fn[T : Comparable + @moonpg.ToValue] Column::gt(
  self : Column[T],
  value : T,
) -> @ast.Expr {
  let _ = Comparable::compare(value)
  @ast.Expr::Compare(self.to_ref(), @ast.Operator::Gt, value)
}

///|
pub fn[T : Comparable + @moonpg.ToValue] Column::gte(
  self : Column[T],
  value : T,
) -> @ast.Expr {
  let _ = Comparable::compare(value)
  @ast.Expr::Compare(self.to_ref(), @ast.Operator::Gte, value)
}

///|
pub fn[T : Comparable + @moonpg.ToValue] Column::lt(
  self : Column[T],
  value : T,
) -> @ast.Expr {
  let _ = Comparable::compare(value)
  @ast.Expr::Compare(self.to_ref(), @ast.Operator::Lt, value)
}

///|
pub fn[T : Comparable + @moonpg.ToValue] Column::lte(
  self : Column[T],
  value : T,
) -> @ast.Expr {
  let _ = Comparable::compare(value)
  @ast.Expr::Compare(self.to_ref(), @ast.Operator::Lte, value)
}

// ---------------------------------------------------------------------------
// String operators
// ---------------------------------------------------------------------------

///|
pub fn[T : StringMatchable] Column::like(
  self : Column[T],
  pattern : String,
) -> @ast.Expr {
  let _ = match self._marker {
    Some(v) => StringMatchable::str_match(v)
    None => ()
  }
  @ast.Expr::Compare(self.to_ref(), @ast.Operator::Like, pattern)
}

///|
pub fn[T : StringMatchable] Column::not_like(
  self : Column[T],
  pattern : String,
) -> @ast.Expr {
  let _ = match self._marker {
    Some(v) => StringMatchable::str_match(v)
    None => ()
  }
  @ast.Expr::Compare(self.to_ref(), @ast.Operator::NotLike, pattern)
}

// ---------------------------------------------------------------------------
// Range / set operators
// ---------------------------------------------------------------------------

///|
pub fn[T : @moonpg.ToValue] Column::in_(
  self : Column[T],
  vals : Array[T],
) -> @ast.Expr {
  @ast.Expr::InList(self.to_ref(), vals.map(fn(v) { v }))
}

///|
pub fn[T] Column::in_select(
  self : Column[T],
  subquery : @ast.SelectStmt,
) -> @ast.Expr {
  @ast.Expr::InSelect(self.to_ref(), subquery)
}

///|
/// Compare column with a scalar subquery.
pub fn[T] Column::eq_select(
  self : Column[T],
  subquery : @ast.SelectStmt,
) -> @ast.Expr {
  @ast.Expr::CompareSelect(self.to_ref(), @ast.Operator::Eq, subquery)
}

///|
pub fn[T] Column::neq_select(
  self : Column[T],
  subquery : @ast.SelectStmt,
) -> @ast.Expr {
  @ast.Expr::CompareSelect(self.to_ref(), @ast.Operator::Neq, subquery)
}

///|
pub fn[T : Comparable] Column::gt_select(
  self : Column[T],
  subquery : @ast.SelectStmt,
) -> @ast.Expr {
  let _ = match self._marker {
    Some(v) => Comparable::compare(v)
    None => ()
  }
  @ast.Expr::CompareSelect(self.to_ref(), @ast.Operator::Gt, subquery)
}

///|
pub fn[T : Comparable] Column::gte_select(
  self : Column[T],
  subquery : @ast.SelectStmt,
) -> @ast.Expr {
  let _ = match self._marker {
    Some(v) => Comparable::compare(v)
    None => ()
  }
  @ast.Expr::CompareSelect(self.to_ref(), @ast.Operator::Gte, subquery)
}

///|
pub fn[T : Comparable] Column::lt_select(
  self : Column[T],
  subquery : @ast.SelectStmt,
) -> @ast.Expr {
  let _ = match self._marker {
    Some(v) => Comparable::compare(v)
    None => ()
  }
  @ast.Expr::CompareSelect(self.to_ref(), @ast.Operator::Lt, subquery)
}

///|
pub fn[T : Comparable] Column::lte_select(
  self : Column[T],
  subquery : @ast.SelectStmt,
) -> @ast.Expr {
  let _ = match self._marker {
    Some(v) => Comparable::compare(v)
    None => ()
  }
  @ast.Expr::CompareSelect(self.to_ref(), @ast.Operator::Lte, subquery)
}

///|
pub fn[T : Comparable + @moonpg.ToValue] Column::between(
  self : Column[T],
  lo : T,
  hi : T,
) -> @ast.Expr {
  let _ = Comparable::compare(lo)
  let _ = Comparable::compare(hi)
  @ast.Expr::Between(self.to_ref(), lo, hi)
}

// ---------------------------------------------------------------------------
// Aggregation functions
// ---------------------------------------------------------------------------

///|
pub fn[T] count(col : Column[T]) -> @ast.Aggregation {
  @ast.Aggregation::Count(Some(col.to_ref()))
}

///|
pub fn[T] sum(col : Column[T]) -> @ast.Aggregation {
  @ast.Aggregation::Sum(col.to_ref())
}

///|
pub fn[T] avg(col : Column[T]) -> @ast.Aggregation {
  @ast.Aggregation::Avg(col.to_ref())
}

///|
pub fn[T] min(col : Column[T]) -> @ast.Aggregation {
  @ast.Aggregation::Min(col.to_ref())
}

///|
pub fn[T] max(col : Column[T]) -> @ast.Aggregation {
  @ast.Aggregation::Max(col.to_ref())
}

///|
/// COUNT(*) — count all rows.
pub fn count_star() -> @ast.Aggregation {
  @ast.Aggregation::Count(None)
}