///|
/// A statement that cannot be written as valid SQL.
///
/// Every `to_sql` shares this: the failures are about the statement being
/// malformed, not about anything the database said.
pub(all) suberror StatementError {
  /// An INSERT was built with no rows, which has no valid SQL form.
  EmptyInsert(table~ : String)
  /// A row supplied a different number of values than the binding has columns.
  ArityMismatch(table~ : String, expected~ : Int, got~ : Int)
  /// An UPDATE was built without any assignment.
  EmptyUpdate(table~ : String)
  /// Two tables in one query claim the same alias, so column references in the
  /// generated SQL would be ambiguous. Self-joins need distinct aliases, which
  /// aya cannot yet assign (see `Table` docs).
  DuplicateAlias(tbl~ : String)
} derive(Debug)

///|
/// Renders expressions to SQL text while collecting their literals.
///
/// Rendering and parameter collection are deliberately one pass: a literal
/// becomes a placeholder at the moment its text is written, so the parameter
/// list can never fall out of step with the placeholders that consume it.
pub struct Emitter {
  dialect : Dialect
  params : Array[SqlValue]
}

///|
pub fn Emitter::new(dialect : Dialect) -> Emitter {
  { dialect, params: [] }
}

///|
/// Bind one value and hand back the placeholder that stands for it.
fn Emitter::param(self : Emitter, v : SqlValue) -> String {
  self.params.push(v)
  match self.dialect {
    Sqlite => "?"
    Postgres => "$\{self.params.length()}"
  }
}

///|
/// SQL operator precedence. Higher number means tighter binding.
fn prec(op : BinOp) -> Int {
  match op {
    BinOp::Or => 1
    BinOp::And => 2
    _ => ATOM // = <> < > <= >= IS IN LIKE
  }
}

///|
/// The precedence everything that is not `AND` or `OR` sits at.
const ATOM = 4

///|
/// Bracket a rendered expression when it binds more loosely than its context.
fn bracket(s : String, own~ : Int, parent~ : Int) -> String {
  if own < parent {
    "(" + s + ")"
  } else {
    s
  }
}

///|
/// Render an expression, adding brackets only where precedence demands them.
pub fn Emitter::expr(self : Emitter, e : RawExpr, parent? : Int = 0) -> String {
  match e {
    Col(tbl~, name~) => "\{tbl}.\"\{name}\""
    Lit(v) => self.param(v)
    Bin(op, l, r) => {
      let own = prec(op)
      // Left before right: interpolation evaluates in order, and so must the
      // parameters the two sides bind.
      let l = self.expr(l, parent=own)
      let r = self.expr(r, parent=own)
      bracket("\{l} \{op} \{r}", own~, parent~)
    }
    Unary(op, x) =>
      bracket("\{self.expr(x, parent=ATOM)} \{op}", own=ATOM, parent~)
    InList(x, vs) => {
      let x = self.expr(x, parent=ATOM)
      "\{x} IN (\{vs.map(v => self.param(v)).join(", ")})"
    }
    Agg(f, None) => "\{f}(*)"
    Agg(f, Some(x)) => "\{f}(\{self.expr(x)})"
  }
}

///|
/// Render `WHERE a AND b`, or nothing at all when there is no predicate.
///
/// SELECT, UPDATE and DELETE all end this way and differ only in how far the
/// keyword is inset: `indent` precedes `WHERE`, and each further condition
/// lines up two columns beyond it.
fn Emitter::where_clause(
  self : Emitter,
  wheres : Array[RawExpr],
  indent~ : String,
) -> String {
  guard wheres.length() > 0 else { "" }
  let conditions = wheres.map(w => self.expr(w))
  "\n\{indent}WHERE " + conditions.join("\n\{indent}  AND ")
}