/**
 * Database models — the single source of truth for a table's shape.
 *
 * A model defined with `table()` + the `v` column builder drives (1) DDL for
 * migrations, (2) coercion/validation of DB rows into typed objects
 * (`model.parse`), and later (3) the types the sqlc-style query generator emits.
 * Column types are dialect-neutral; each adapter maps them to its own SQL types.
 */

export type BaseType = "id" | "text" | "int" | "real" | "bool" | "timestamp" | "json";

export interface ColumnDef {
  type: BaseType;
  /** NOT NULL unless `.optional()` was called. Ids are implicitly not-null. */
  notNull: boolean;
  primaryKey: boolean;
  autoIncrement: boolean;
  unique: boolean;
  /** Literal default, or the sentinel "now" for a current-timestamp default. */
  default?: unknown;
  references?: { table: string; column: string };
}

/** A fluent column definition. Chain modifiers, then hand it to `table()`. */
export class Column {
  readonly def: ColumnDef;
  constructor(type: BaseType, overrides: Partial<ColumnDef> = {}) {
    this.def = {
      type,
      notNull: true,
      primaryKey: false,
      autoIncrement: false,
      unique: false,
      ...overrides,
    };
  }
  optional(): this {
    this.def.notNull = false;
    return this;
  }
  unique(): this {
    this.def.unique = true;
    return this;
  }
  default(value: unknown): this {
    this.def.default = value;
    return this;
  }
  primaryKey(): this {
    this.def.primaryKey = true;
    return this;
  }
  references(table: string, column = "id"): this {
    this.def.references = { table, column };
    return this;
  }
  /** Coerce a raw DB value into its JS type (used by `model.parse`). */
  coerce(raw: unknown): unknown {
    if (raw === null || raw === undefined) return this.def.notNull ? raw : null;
    switch (this.def.type) {
      case "id":
      case "int":
        return typeof raw === "bigint" ? Number(raw) : Number(raw);
      case "real":
        return Number(raw);
      case "bool":
        return raw === true || raw === 1 || raw === "1" || raw === "true";
      case "timestamp":
        return raw instanceof Date ? raw : new Date(raw as string | number);
      case "json":
        return typeof raw === "string" ? safeJson(raw) : raw;
      default:
        return String(raw);
    }
  }
}

function safeJson(value: string): unknown {
  try {
    return JSON.parse(value);
  } catch {
    return value;
  }
}

/** Column builders. `v.id()` is an auto-increment primary key. */
export const v = {
  id: () => new Column("id", { primaryKey: true, autoIncrement: true }),
  text: () => new Column("text"),
  string: () => new Column("text"),
  int: () => new Column("int"),
  number: () => new Column("real"),
  real: () => new Column("real"),
  bool: () => new Column("bool"),
  boolean: () => new Column("bool"),
  timestamp: () => new Column("timestamp"),
  json: () => new Column("json"),
};

export type Columns = Record<string, Column>;

export interface Model<T = Record<string, unknown>> {
  name: string;
  columns: Columns;
  /** Coerce a raw DB row into a typed object (unknown columns pass through). */
  parse(row: Record<string, unknown>): T;
  /** Column definitions, for migrations and the query generator. */
  describe(): Record<string, ColumnDef>;
}

/** Define a table model from a name and a map of columns. */
export function table<T = Record<string, unknown>>(name: string, columns: Columns): Model<T> {
  return {
    name,
    columns,
    parse(row) {
      const out: Record<string, unknown> = { ...row };
      for (const [key, column] of Object.entries(columns)) {
        if (key in row) out[key] = column.coerce(row[key]);
      }
      return out as T;
    },
    describe() {
      const out: Record<string, ColumnDef> = {};
      for (const [key, column] of Object.entries(columns)) out[key] = column.def;
      return out;
    },
  };
}
