///|
/// What one `SET` assigns: a bound value (`col = ?`), or an expression rendered
/// verbatim with its own placeholders, which is the only way to write a column in
/// terms of itself (`n = n + ?`).
enum Assign {
  Bind(Value)
  Expr(String, Array[Value])
}

///|
/// An UPDATE statement builder. Generative like `Select`, and it shares the same
/// WHERE clause, so `where_pred` / `where_in` narrow an update exactly as they
/// narrow a query.
pub struct Update {
  table : String
  sets : Array[(String, Assign)]
  filter : Where
  returning : Array[String]
}

///|
/// Start an UPDATE of `table`. Without a `where_` it rewrites every row, which
/// is what SQL does and what the caller asked for.
pub fn update(table : String) -> Update {
  { table, sets: [], filter: Where::new(), returning: [], }
}

///|
fn Update::dup(self : Update) -> Update {
  {
    table: self.table,
    sets: self.sets.copy(),
    filter: self.filter.dup(),
    returning: self.returning.copy(),
  }
}

///|
/// Assign one column a bound value. Repeated calls set each, in the order given.
pub fn Update::set(self : Update, col : String, val : Value) -> Update {
  let u = self.dup()
  u.sets.push((col, Bind(val)))
  u
}

///|
/// Assign one column an expression instead of a value — `set_expr("n", "n + ?",
/// vals=[Int(1)])` renders `SET n = n + ?`. The expression is rendered verbatim, so
/// it can read the row's current columns, which a bound value cannot; anything that
/// varies belongs in `vals`, one per `?`, and binds in the order the placeholders
/// appear.
pub fn Update::set_expr(
  self : Update,
  col : String,
  expr : String,
  vals~ : Array[Value] = [],
) -> Update {
  let u = self.dup()
  u.sets.push((col, Expr(expr, vals)))
  u
}

///|
/// Narrow the update with `col op ?`. Conditions are ANDed together.
pub fn Update::where_(
  self : Update,
  col : String,
  op : String,
  val : Value,
) -> Update {
  let u = self.dup()
  u.filter.conds.push({ col, op, val, })
  u
}

///|
/// Narrow the update with a boolean `Predicate` tree — the `AND`/`OR`/`NOT`
/// grouping the flat `where_` cannot express. See `Select::where_pred`.
pub fn Update::where_pred(self : Update, pred : Predicate) -> Update {
  let u = self.dup()
  u.filter.preds.push(pred)
  u
}

///|
/// Narrow the update with `col IN (subquery)` — "update the rows some other query
/// picks out". The subquery's bound values splice in at the predicate's position.
pub fn Update::where_in(self : Update, col : String, sub : Select) -> Update {
  let u = self.dup()
  u.filter.sub_conds.push({ col, op: "IN", sub, })
  u
}

///|
/// Narrow the update with `col NOT IN (subquery)`. See `where_in`.
pub fn Update::where_not_in(
  self : Update,
  col : String,
  sub : Select,
) -> Update {
  let u = self.dup()
  u.filter.sub_conds.push({ col, op: "NOT IN", sub, })
  u
}

///|
/// Return `col` from each updated row (SQLite 3.35+, PostgreSQL). Repeatable;
/// `build_for(Mysql)` drops it.
pub fn Update::returning(self : Update, col : String) -> Update {
  let u = self.dup()
  u.returning.push(col)
  u
}

///|
/// Return every column of each updated row (`RETURNING *`). See `returning`.
pub fn Update::returning_all(self : Update) -> Update {
  self.returning("*")
}

///|
/// Render `(sql, params)` for the SQLite/PostgreSQL dialect. The SET values come
/// first and the WHERE values after, in the order the placeholders appear, which is
/// the order every driver binds in.
pub fn Update::build(self : Update) -> (String, Array[Value]) {
  self.build_for(Sqlite)
}

///|
/// Render `(sql, params)` for `dialect`. Identical everywhere but `RETURNING`,
/// which MySQL does not have and so does not get.
pub fn Update::build_for(
  self : Update,
  dialect : Dialect,
) -> (String, Array[Value]) {
  let params : Array[Value] = []
  let setparts : Array[String] = []
  for pair in self.sets {
    let (col, assign) = pair
    match assign {
      Bind(v) => {
        setparts.push(col + " = ?")
        params.push(v)
      }
      Expr(expr, vals) => {
        setparts.push(col + " = " + expr)
        for v in vals {
          params.push(v)
        }
      }
    }
  }
  let mut sql = "UPDATE " + self.table + " SET " + setparts.join(", ")
  let where_sql = self.filter.sql(params)
  if where_sql != "" {
    sql = sql + " WHERE " + where_sql
  }
  if self.returning.length() > 0 && dialect != Mysql {
    sql = sql + " RETURNING " + self.returning.join(", ")
  }
  (sql, params)
}