///|
/// The `INSERT` builder end to end: a single-row insert with `RETURNING`, a multi-row
/// insert (`values`), and upserts — `on_conflict` with `do_update` / `do_update_excluded`
/// / `do_nothing`, plus `returning_all`. The same builder renders per dialect: `build`
/// (SQLite/PostgreSQL) emits `ON CONFLICT ... DO UPDATE` and keeps `RETURNING`, while
/// `build_for(Mysql)` emits `ON DUPLICATE KEY UPDATE` and drops `RETURNING`. Every value
/// binds as `?`, so a bulk insert or an upsert is injection-safe.
///
///     moon run examples/07-insert-upsert
fn render(v : @moondb.Value) -> String {
  match v {
    Null => "Null"
    Bool(b) => "Bool(\{b})"
    Int(n) => "Int(\{n})"
    Int64(n) => "Int64(\{n})"
    Double(d) => "Double(\{d})"
    Text(s) => "Text(\"\{s}\")"
    Blob(b) => "Blob(\{b.length()} bytes)"
    Time(s) => "Time(\"\{s}\")"
    Numeric(s) => "Numeric(\{s})"
    Uuid(b) => "Uuid(\{b.length()} bytes)"
    Json(s) => "Json(\{s})"
    Array(items) => "Array(\{items.length()} items)"
  }
}

///|
fn params_str(vals : Array[@moondb.Value]) -> String {
  "[" + vals.map(render).join(", ") + "]"
}

///|
#coverage.skip
fn main {
  // Plain insert, returning the server-assigned id in the same round trip.
  let (sql, params) = @moonorm.insert("heroes")
    .set("name", @moondb.Text("lfanke"))
    .set("age", @moondb.Int(233))
    .returning("id")
    .build()
  println("-- insert + returning")
  println(sql)
  println("params: " + params_str(params))

  // Multi-row insert: the first row from `set`, the rest from `values` in the same
  // column order. Each value is its own placeholder.
  let (msql, mparams) = @moonorm.insert("heroes")
    .set("name", @moondb.Text("emm"))
    .set("age", @moondb.Int(23333))
    .values([@moondb.Text("qwq"), @moondb.Int(66666)])
    .values([@moondb.Text("hhh"), @moondb.Int(233)])
    .build()
  println("")
  println("-- multi-row insert")
  println(msql)
  println("params: " + params_str(mparams))

  // Upsert overwriting with the incoming row (`excluded.name`), returning every
  // column. One builder, two dialects.
  let upsert = fn() {
    @moonorm.insert("heroes")
    .set("id", @moondb.Int(1))
    .set("name", @moondb.Text("heke1228"))
    .on_conflict(["id"])
    .do_update_excluded("name")
    .returning_all()
  }
  let (ssql, sparams) = upsert().build()
  println("")
  println("-- upsert (SQLite/PostgreSQL): do_update_excluded + returning_all")
  println(ssql)
  println("params: " + params_str(sparams))
  let (ysql, yparams) = upsert().build_for(Mysql)
  println("")
  println("-- upsert (MySQL): ON DUPLICATE KEY UPDATE, RETURNING dropped")
  println(ysql)
  println("params: " + params_str(yparams))

  // Upsert with a bound update value — the counter increment pattern.
  let (bsql, bparams) = @moonorm.insert("counters")
    .set("key", @moondb.Text("qwe"))
    .set("hits", @moondb.Int(1))
    .on_conflict(["key"])
    .do_update("hits", @moondb.Int(23333))
    .build()
  println("")
  println("-- upsert: do_update bound value")
  println(bsql)
  println("params: " + params_str(bparams))

  // DO NOTHING keeps the existing row; MySQL has no such form, so it renders a
  // no-op self-assignment instead.
  let nothing = fn() {
    @moonorm.insert("seen")
    .set("id", @moondb.Int(233))
    .on_conflict(["id"])
    .do_nothing()
  }
  let (dsql, _) = nothing().build()
  let (dmsql, _) = nothing().build_for(Mysql)
  println("")
  println("-- do_nothing: SQLite/PostgreSQL")
  println(dsql)
  println("-- do_nothing: MySQL no-op self-assign")
  println(dmsql)
}