///|
/// An UPDATE statement over one table.
///
/// `Cols` is carried so that assignments and predicates are written against
/// the same typed column handles the query builder uses.
pub struct Update[Cols] {
  table_name : String
  tbl : String
  cols : Cols
  sets : Array[(String, RawExpr)]
  wheres : Array[RawExpr]
}

///|
/// Update the rows matching `pred`.
///
/// The predicate is a required argument rather than a chained step, so an
/// UPDATE that touches the whole table cannot be written by forgetting one.
pub fn[C, R] update(t : Table[C, R], pred : (C) -> Expr[Bool]) -> Update[C] {
  {
    table_name: t.table_name,
    tbl: t.tbl,
    cols: t.cols,
    sets: [],
    wheres: [pred(t.cols).raw()],
  }
}

///|
/// Update every row in the table.
///
/// Named separately from `update` so that an unfiltered write is visible at
/// the call site instead of being an omission.
pub fn[C, R] update_all(t : Table[C, R]) -> Update[C] {
  { table_name: t.table_name, tbl: t.tbl, cols: t.cols, sets: [], wheres: [] }
}

///|
/// Assign a value to one column.
///
/// The column is selected from the table's column handles, so the value type
/// has to match the column type.
pub fn[C, T : SqlEncode] Update::set(
  self : Update[C],
  f : (C) -> Column[T],
  v : T,
) -> Update[C] {
  let sets = self.sets.copy()
  sets.push((f(self.cols).name, Lit(SqlEncode::to_sql_value(v))))
  { ..self, sets, }
}

///|
/// Narrow an update further. Conditions are ANDed together.
pub fn[C] Update::filter(self : Update[C], f : (C) -> Expr[Bool]) -> Update[C] {
  let wheres = self.wheres.copy()
  wheres.push(f(self.cols).raw())
  { ..self, wheres, }
}

///|
pub fn[C] Update::to_sql(
  self : Update[C],
  dialect? : Dialect = Dialect::Sqlite,
) -> (String, Array[SqlValue]) raise StatementError {
  guard self.sets.length() > 0 else { raise EmptyUpdate(table=self.table_name) }
  let em = Emitter::new(dialect)
  let buf = StringBuilder()
  buf.write_string("UPDATE \"\{self.table_name}\" AS \{self.tbl}")
  // SET comes before WHERE in the statement, so its parameters must be
  // emitted first to keep the parameter list in textual order.
  let assignments = self.sets.map(pair => {
    let (name, value) = pair
    "\"\{name}\" = \{em.expr(value)}"
  })
  buf.write_string("\n    SET " + assignments.join(", "))
  buf.write_string(em.where_clause(self.wheres, indent="  "))
  (buf.to_string(), em.params)
}