/**
 * `t` — field builders for `model({...})`. Each builder returns an immutable `Field` carrying a runtime
 * `FieldDef` (what `.marci` is emitted from) and two phantom type parameters: the TS value type and a
 * `FieldMeta` record (kind / optional / default / …) that `defineDb`'s type layer projects into row,
 * insert, update, select and where types. Chained modifiers return a NEW field (builders are never
 * mutated), so `const s = t.string(); { a: s, b: s.optional() }` behaves as expected.
 */

import type { JsonValue } from "./marci/query"

export type ScalarKind = "string" | "int" | "float" | "bool" | "date" | "uuid" | ""
export type FieldKind = "scalar" | "json" | "enum" | "struct" | "one" | "many" | "list"
export type OnDelete = "cascade" | "setNull" | "restrict"

/** Phantom metadata — the type-level twin of `FieldDef`. */
export type FieldMeta = {
  kind: FieldKind
  scalar: ScalarKind
  optional: boolean
  hasDefault: boolean
  array: boolean
  isId: boolean
  /** Related model name for one/many/list; "" otherwise. */
  model: string
}

type DefaultMeta = { kind: "scalar", scalar: "", optional: false, hasDefault: false, array: false, isId: false, model: "" }
export type Meta<O extends Partial<FieldMeta>> = { [P in keyof FieldMeta]: P extends keyof O ? O[P] : DefaultMeta[P] }
type SetMeta<M extends FieldMeta, K extends keyof FieldMeta, V> = { [P in keyof FieldMeta]: P extends K ? V : M[P] }

/** Runtime field definition — everything the `.marci` emitter and the runtime need. */
export type FieldDef = {
  kind: FieldKind
  scalar?: ScalarKind
  optional: boolean
  unique: boolean
  index: boolean
  hasDefault: boolean
  default?: unknown
  array: boolean
  isId: boolean
  onDelete?: OnDelete
  fulltext?: "multi" | "english" | "russian"
  vector?: { size: number, metric: "cosine" | "euclidean" }
  /** enum */
  values?: readonly string[]
  /** struct */
  fields?: Record<string, Field<any, any>>
  /** one / many / list */
  model?: string
  /** many: the field on the related model that holds the reference (`@bind`); resolved by defineDb when omitted. */
  back?: string
}

type DefaultOf<T, M extends FieldMeta> =
  M["scalar"] extends "date" ? "now" | number : M["array"] extends true ? T[] : T

export interface Field<T, M extends FieldMeta = FieldMeta> {
  /** phantom (type-level only — never set at runtime) */ readonly __t?: T
  /** phantom (type-level only — never set at runtime) */ readonly __m?: M
  readonly def: FieldDef
  /** Nullable column (`String?`). */
  optional(): Field<T, SetMeta<M, "optional", true>>
  /** `@unique` (on a relation: one-to-one). */
  unique(): Field<T, M>
  /** `@index` — used by `$where`, `$order`, aggregations. */
  index(): Field<T, M>
  /** `@default(...)`; `"now"` on `t.date()` → `now()`. Makes the field optional on insert. */
  default(value: DefaultOf<T, M>): Field<T, SetMeta<M, "hasDefault", true>>
  /** Primitive list (`String[]`). */
  array(): Field<T, SetMeta<M, "array", true>>
  /** `@id` — primary key (several → composite). A field literally named `id` is the key implicitly. */
  id(): Field<T, SetMeta<M, "isId", true>>
  /** Referential policy for `t.one()` (defaults: required → restrict, optional → setNull). */
  onDelete(policy: OnDelete): Field<T, M>
  /** `@fulltext(...)` module index on a string field; query with `$search`. */
  fulltext(lang?: "multi" | "english" | "russian"): Field<T, M>
  /** `Float[n] @vector(metric)` — nearest-neighbour index; query with `$near`. */
  vector(size: number, metric?: "cosine" | "euclidean"): Field<T, M>
}

const create = <T, M extends FieldMeta>(def: FieldDef): Field<T, M> => {
  const next = (patch: Partial<FieldDef>) => create<any, any>({ ...def, ...patch })
  return {
    def,
    optional: () => next({ optional: true }),
    unique: () => next({ unique: true }),
    index: () => next({ index: true }),
    default: (value: unknown) => next({ hasDefault: true, default: value }),
    array: () => next({ array: true }),
    id: () => next({ isId: true }),
    onDelete: (policy) => next({ onDelete: policy }),
    fulltext: (lang = "multi") => next({ fulltext: lang }),
    vector: (size, metric = "cosine") => next({ vector: { size, metric } }),
  } as Field<T, M>
}

const base = (kind: FieldKind, extra: Partial<FieldDef> = {}): FieldDef => ({
  kind, optional: false, unique: false, index: false, hasDefault: false, array: false, isId: false, ...extra,
})

const scalar = <T, K extends ScalarKind>(scalar: K) =>
  create<T, Meta<{ kind: "scalar", scalar: K }>>(base("scalar", { scalar }))

export const t = {
  string: () => scalar<string, "string">("string"),
  /** 64-bit signed integer. */
  int:    () => scalar<number, "int">("int"),
  /** 64-bit float. */
  float:  () => scalar<number, "float">("float"),
  bool:   () => scalar<boolean, "bool">("bool"),
  /** Epoch milliseconds — reads and writes as `number`. */
  date:   () => scalar<number, "date">("date"),
  /** UUID string (16 bytes in storage). On an `id` field the runtime generates it on insert. */
  uuid:   () => scalar<string, "uuid">("uuid"),
  /** Any JSON value; `J` is TS-only (marcidb stores it schemaless). Filter by dot-path in `$where`. */
  json:   <J = JsonValue>() => create<J, Meta<{ kind: "json" }>>(base("json")),
  /** String enum: `t.enum("draft", "published")`. */
  enum:   <const V extends readonly [string, ...string[]]>(...values: V) =>
            create<V[number], Meta<{ kind: "enum" }>>(base("enum", { values })),
  /** Owned nested object (marcidb `struct`): created/updated with the parent, no id of its own. */
  struct: <F extends Record<string, Field<any, any>>>(fields: F) =>
            create<F, Meta<{ kind: "struct" }>>(base("struct", { fields })),
  /** Reference to one row of `model` (by id). Required unless `.optional()`. */
  one:    <N extends string>(model: N) => create<never, Meta<{ kind: "one", model: N }>>(base("one", { model })),
  /** Reverse side of a `t.one()` on `model` (the field is inferred when unambiguous). Read-only list. */
  many:   <N extends string>(model: N, backField?: string) =>
            create<never, Meta<{ kind: "many", model: N }>>(base("many", { model, back: backField })),
  /** Ordered relation list stored inline (`@list`) — hand-arranged collections, duplicates allowed. */
  list:   <N extends string>(model: N) => create<never, Meta<{ kind: "list", model: N }>>(base("list", { model })),
}
export type T = typeof t
