///|
/// The field-based declarative path: `Model::from_fields` derives the column list, the
/// `CREATE TABLE` DDL, and the `INSERT` binding from a `[Field]` array — each `field`
/// pairs column metadata with the attribute it binds. You supply only the `from_row`
/// decoder (the record constructor MoonBit cannot synthesise without reflection), and
/// `Row::read` reads each cell as the field's type. `#orm` annotations generate exactly
/// this; see `22-annotations`. `Field::name` exposes the per-field metadata; then a
/// `Session` round trip over `@moondb.MockDriver`.
///
///     moon run src/examples/12-declarative-fields
struct Hero {
  id : Int32
  name : String
  power : Int32
}

///|
let heroes : @moonorm.Model[Hero] = @moonorm.Model::from_fields(
  "heroes",
  [
    @moonorm.field("id", IntType, h => h.id, primary_key=true),
    @moonorm.field("name", TextType, h => h.name, nullable=false),
    @moonorm.field("power", IntType, h => h.power),
  ],
  r => { id: r.read(0), name: r.read(1), power: r.read(2), },
)

///|
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 {
  println("-- columns derived from fields: " + heroes.column_names().join(", "))
  println("-- create_table_sql")
  println(heroes.create_table_sql())

  // to_columns is derived from the field encoders — insert_of binds every field.
  let (isql, iparams) = heroes
    .insert_of({ id: 1, name: "lfanke", power: 233, })
    .build()
  println("")
  println("-- insert_of (binding derived from field encoders)")
  println(isql)
  println("params: " + params_str(iparams))

  // A single Field carries its own column metadata.
  let email : @moonorm.Field[Hero] = @moonorm.field(
    "email",
    TextType,
    h => h.name,
    unique=true,
  )
  println("")
  println("-- Field::name -> \{email.name()}")

  // Round trip: the derived binding stores, the from_row decoder reads back.
  try {
    let sess = @moonorm.Session::new(@moondb.MockDriver::new())
    let roster : Array[Hero] = [
      { id: 1, name: "qwq", power: 66666, },
      { id: 233, name: "emm", power: 23333, },
    ]
    for h in roster {
      sess.insert_record(heroes, h) |> ignore
    }
    println("")
    println("-- round trip via the from_fields model")
    for h in sess.all(heroes) {
      println("  #\{h.id} \{h.name} power=\{h.power}")
    }
    sess.close()
  } catch {
    e => println("db error: " + e.to_string())
  }
}