///|
/// Schema reflection & diff — the read/compare half of Alembic-style autogenerate.
/// `reflect_columns` parses SQLite `PRAGMA table_info` rows into `Column`s;
/// `reflect_columns_pg` / `reflect_columns_mysql` do the same for PostgreSQL
/// `information_schema.columns` and MySQL `SHOW COLUMNS`; `reflect_table` /
/// `reflect_table_for` run that parser off a live driver. `diff_schema` compares a
/// model's declared columns against the reflected ones (`Added` / `Removed` /
/// `TypeChanged`), and `index_ddl` renders a secondary index. The reflection rows are
/// built here so the whole example stays backend-free and compiles on every target.
///
///     moon run src/examples/20-reflection-diff
fn type_name(t : @moonorm.ColumnType) -> String {
  match t {
    IntType => "int"
    BigIntType => "bigint"
    TextType => "text"
    RealType => "real"
    BlobType => "blob"
    BoolType => "bool"
    VarcharType(n) => "varchar(\{n})"
    NumericType => "numeric"
    DateTimeType => "datetime"
    DateType => "date"
    TimeType => "time"
    UuidType => "uuid"
    JsonType => "json"
    UserType(c) => c.sqlite
  }
}

///|
fn diff_name(d : @moonorm.ColumnDiff) -> String {
  match d {
    Added(name~, col_type~) => "added \{name} (\{type_name(col_type)})"
    Removed(name) => "removed \{name}"
    TypeChanged(name~, from~, to~) =>
      "retyped \{name}: \{type_name(from)} -> \{type_name(to)}"
  }
}

///|
/// A driver returning fixed `PRAGMA table_info`-shaped rows, so `reflect_table` (which
/// queries the driver) runs without a real backend.
struct Pragma {
  rows : Array[@moondb.Row]
}

///|
impl @moondb.Driver for Pragma with fn execute(
  _self : Pragma,
  _sql : String,
  _params : Array[@moondb.Value],
) -> @moondb.ExecResult raise @moondb.DbError {
  { rows_affected: 0L, last_insert_id: 0L, }
}

///|
impl @moondb.Driver for Pragma with fn query(
  self : Pragma,
  _sql : String,
  _params : Array[@moondb.Value],
) -> Array[@moondb.Row] raise @moondb.DbError {
  self.rows
}

///|
impl @moondb.Driver for Pragma with fn begin(_self : Pragma) -> Unit raise @moondb.DbError {

}

///|
impl @moondb.Driver for Pragma with fn commit(_self : Pragma) -> Unit raise @moondb.DbError {

}

///|
impl @moondb.Driver for Pragma with fn rollback(_self : Pragma) -> Unit raise @moondb.DbError {

}

///|
impl @moondb.Driver for Pragma with fn close(_self : Pragma) -> Unit {

}

///|
/// A `PRAGMA table_info` row: cid, name, type, notnull, dflt_value, pk.
fn pragma_row(
  cid : Int32,
  name : String,
  ty : String,
  notnull : Int32,
  pk : Int32,
) -> @moondb.Row {
  {
    columns: ["cid", "name", "type", "notnull", "dflt_value", "pk"],
    values: [
      @moondb.Int(cid),
      @moondb.Text(name),
      @moondb.Text(ty),
      @moondb.Int(notnull),
      @moondb.Null,
      @moondb.Int(pk),
    ],
  }
}

///|
/// An `information_schema.columns` row: column_name, data_type, is_nullable.
fn pg_row(name : String, ty : String, nullable : String) -> @moondb.Row {
  {
    columns: ["column_name", "data_type", "is_nullable"],
    values: [@moondb.Text(name), @moondb.Text(ty), @moondb.Text(nullable)],
  }
}

///|
/// A `SHOW COLUMNS` row: Field, Type, Null, Key, Default, Extra.
fn mysql_row(
  field : String,
  ty : String,
  nullv : String,
  key : String,
  extra : String,
) -> @moondb.Row {
  {
    columns: ["Field", "Type", "Null", "Key", "Default", "Extra"],
    values: [
      @moondb.Text(field),
      @moondb.Text(ty),
      @moondb.Text(nullv),
      @moondb.Text(key),
      @moondb.Null,
      @moondb.Text(extra),
    ],
  }
}

///|
fn show() -> Unit raise @moondb.DbError {
  // Reflect a SQLite table off its PRAGMA rows.
  let reflected = @moonorm.reflect_columns([
    pragma_row(0, "id", "INTEGER", 1, 1),
    pragma_row(1, "name", "TEXT", 1, 0),
    pragma_row(2, "power", "INTEGER", 0, 0),
  ])
  println("-- reflect_columns (PRAGMA table_info)")
  for c in reflected {
    println(
      "  \{c.name}: \{type_name(c.col_type)} pk=\{c.primary_key} nullable=\{c.nullable}",
    )
  }

  // The model dropped `power` and added `team_id`; diff it against the reflected table.
  let declared = [
    @moonorm.column("id", IntType, primary_key=true),
    @moonorm.column("name", TextType, nullable=false),
    @moonorm.column("team_id", IntType),
  ]
  println("")
  println("-- diff_schema (declared vs reflected)")
  for d in @moonorm.diff_schema(declared, reflected) {
    println("  " + diff_name(d))
  }

  // reflect_table / reflect_table_for run the same parser off a driver.
  let drv = Pragma::{
    rows: [
      pragma_row(0, "id", "INTEGER", 1, 1),
      pragma_row(1, "label", "TEXT", 0, 0),
    ],
  }
  println("")
  println("-- reflect_table off a driver")
  for c in @moonorm.reflect_table(drv, "tags") {
    println("  \{c.name}: \{type_name(c.col_type)}")
  }
  println("-- reflect_table_for(Sqlite)")
  for c in @moonorm.reflect_table_for(drv, "tags", Sqlite) {
    println("  \{c.name}")
  }

  // PostgreSQL and MySQL reflection parsers.
  println("")
  println("-- reflect_columns_pg")
  for
    c in @moonorm.reflect_columns_pg([
      pg_row("id", "integer", "NO"),
      pg_row("meta", "jsonb", "YES"),
      pg_row("uid", "uuid", "NO"),
    ]) {
    println("  \{c.name}: \{type_name(c.col_type)} nullable=\{c.nullable}")
  }
  println("")
  println("-- reflect_columns_mysql")
  for
    c in @moonorm.reflect_columns_mysql([
      mysql_row("id", "int", "NO", "PRI", "auto_increment"),
      mysql_row("active", "tinyint(1)", "NO", "", ""),
      mysql_row("name", "varchar(64)", "YES", "", ""),
    ]) {
    println(
      "  \{c.name}: \{type_name(c.col_type)} pk=\{c.primary_key} auto=\{c.autoincrement}",
    )
  }

  println("")
  println("-- index_ddl")
  println(@moonorm.index_ddl("idx_heroes_name", "heroes", ["name"]))
  println(@moonorm.index_ddl("uq_heroes_uid", "heroes", ["uid"], unique=true))
}

///|
#coverage.skip
fn main {
  show() catch {
    e => println("db error: " + e.to_string())
  }
}