/**
 * Type layer: projects a `defineDb({ User: model({...}), … })` literal into marcidb's generic query
 * types (./marci/query.ts — vendored from marcidb, see scripts/sync-marcidb.ts): each model becomes the
 * `ModelTypes` bag marcidb's codegen would have emitted, and `db.<model>` is marcidb's own `Query<T>`.
 * Everything here is type-only; the runtime lives in ./defineDb.ts.
 *
 * Naming: `S` = the schema record (model name → Model), `N` = a model name, `F` = a fields record.
 */

import type { Field, FieldMeta } from "./fields"
import type {
  CompareNumValue, CompareRefListValue, CompareRefValue, CompareStrValue, CompareValue, CustomSearchValue,
  FullTextSearch, JsonCondition, JsonPathWhere, Op, PrimitiveListUpdate, Query as MarciQuery, RefListUpdate,
  RefListUpdateOrdered, RefUpdate, RefUpdateStruct, Rows, Sub, UpdateNumValue, VectorSearch, WhereValue,
} from "./marci/query"

// ───────────────────────────── schema shapes ─────────────────────────────

export type Fields = Record<string, Field<any, any>>
export type Model<F extends Fields = Fields> = { readonly fields: F, readonly __model: true }
export type Schema = Record<string, Model<any>>

export type FieldsOf<M> = M extends Model<infer F> ? F : never
type TsOf<Fld> = Fld extends Field<infer T, any> ? T : never
type StructFieldsOf<Fld> = TsOf<Fld> extends Fields ? TsOf<Fld> : never
type MetaOf<Fld> = Fld extends Field<any, infer M> ? M : never
type ModelName<S, Fld> = MetaOf<Fld>["model"] & keyof S & string

type Nullable<M extends FieldMeta, T> = M["optional"] extends true ? T | null : T
type Arr<M extends FieldMeta, T> = M["array"] extends true ? T[] : T
type IsNum<M extends FieldMeta> = M["scalar"] extends "int" | "float" | "date" ? true : false

// ───────────────────────────── key ─────────────────────────────

/** Explicit `.id()` fields, or a field literally named `id`. */
type IdKeys<F extends Fields> = { [K in keyof F]: MetaOf<F[K]>["isId"] extends true ? K : K extends "id" ? K : never }[keyof F]
type IdValue<S, Fld> = MetaOf<Fld>["kind"] extends "one" ? Id<S, ModelName<S, Fld>> : TsOf<Fld>

/** The id object of a model: `{ id: number }` implicitly, or the declared key fields. */
export type Id<S, N extends keyof S> =
  [IdKeys<FieldsOf<S[N]>>] extends [never] ? { id: number } : { [K in IdKeys<FieldsOf<S[N]>>]: IdValue<S, FieldsOf<S[N]>[K]> }
/** What `update`/`delete` accept: the id object, or the bare value when the key is a single scalar. */
export type IdArg<S, N extends keyof S> = Id<S, N> | (Id<S, N> extends { id: infer V } ? V : never)

// ───────────────────────────── row ─────────────────────────────

type FieldRow<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
  M["kind"] extends "one" ? Nullable<M, Row<S, ModelName<S, Fld>>>
  : M["kind"] extends "many" | "list" ? Row<S, ModelName<S, Fld>>[]
  : M["kind"] extends "struct" ? Nullable<M, StructRow<S, StructFieldsOf<Fld>>>
  : Nullable<M, Arr<M, TsOf<Fld>>>

/** Full row type of a model (relations included) — the `TModel` marcidb's `GetResult` selects from. */
export type Row<S, N extends keyof S> = Id<S, N> & { [K in Exclude<keyof FieldsOf<S[N]>, IdKeys<FieldsOf<S[N]>>>]: FieldRow<S, FieldsOf<S[N]>[K]> }
type StructRow<S, F extends Fields> = { [K in keyof F]: FieldRow<S, F[K]> }

type ScalarKeys<F extends Fields> = { [K in keyof F]: MetaOf<F[K]>["kind"] extends "scalar" | "enum" | "json" ? K : never }[keyof F]
type NumKeys<F extends Fields> = { [K in keyof F]: IsNum<MetaOf<F[K]>> extends true ? (MetaOf<F[K]>["array"] extends true ? never : K) : never }[keyof F]
/** What an empty select returns: id + every scalar field. */
export type Scalars<S, N extends keyof S> = Pick<Row<S, N>, (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>>
/** The same, as the `{ field: true }` select shape a query starts with. */
export type ScalarSelect<S, N extends keyof S> = { [K in (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>]: true }

// ───────────────────────────── insert ─────────────────────────────

type FieldInsert<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
  M["kind"] extends "one" ? Nullable<M, Id<S, ModelName<S, Fld>>>
  : M["kind"] extends "many" | "list" ? Id<S, ModelName<S, Fld>>[]
  : M["kind"] extends "struct" ? Nullable<M, StructInsert<S, StructFieldsOf<Fld>>>
  : Nullable<M, Arr<M, TsOf<Fld>>>

type RequiredKeys<F extends Fields> = {
  [K in keyof F]: K extends IdKeys<F> ? never
    : MetaOf<F[K]>["optional"] extends true ? never
    : MetaOf<F[K]>["hasDefault"] extends true ? never
    : MetaOf<F[K]>["kind"] extends "many" | "list" ? never
    : K
}[keyof F]
type Simplify<T> = { [K in keyof T]: T[K] } & {}
type InsertOf<S, F extends Fields> = Simplify<
  { [K in RequiredKeys<F>]: FieldInsert<S, F[K]> } &
  { [K in Exclude<keyof F, RequiredKeys<F>>]?: FieldInsert<S, F[K]> }
>
export type Insert<S, N extends keyof S> = InsertOf<S, FieldsOf<S[N]>>
type StructInsert<S, F extends Fields> = InsertOf<S, F>

// ───────────────────────────── update ─────────────────────────────

type FieldUpdate<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
  M["kind"] extends "one" ? Nullable<M, RefUpdate<Id<S, ModelName<S, Fld>>>>     // `{ $connect: { id } }` / null — marcidb's update grammar (bare ids are for insert)
  : M["kind"] extends "many" ? RefListUpdate<Id<S, ModelName<S, Fld>>>
  : M["kind"] extends "list" ? RefListUpdateOrdered<Id<S, ModelName<S, Fld>>>
  : M["kind"] extends "struct" ? RefUpdateStruct<StructInsert<S, StructFieldsOf<Fld>>, UpdateOf<S, StructFieldsOf<Fld>>> | null
  : M["array"] extends true ? TsOf<Fld>[] | PrimitiveListUpdate<TsOf<Fld>>
  : Nullable<M, TsOf<Fld> | (IsNum<M> extends true ? UpdateNumValue : never)>

type UpdateOf<S, F extends Fields> = { [K in Exclude<keyof F, IdKeys<F>>]?: FieldUpdate<S, F[K]> }
export type Update<S, N extends keyof S> = UpdateOf<S, FieldsOf<S[N]>>

// ───────────────────────────── select / where / order / query ─────────────────────────────

// A relation also accepts a sub-query built from the related model's collection (`posts: db.post.limit(5)`,
// `db.post.count()`), branded `Sub<"Post">` so a builder of the wrong model is a type error.
type FieldSelect<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
  M["kind"] extends "one" ? Select<S, ModelName<S, Fld>> | boolean | Sub<ModelName<S, Fld>>
  : M["kind"] extends "many" | "list" ? QueryObject<S, ModelName<S, Fld>> | AggregateQuery<S, ModelName<S, Fld>> | boolean | Sub<ModelName<S, Fld>>
  : M["kind"] extends "struct" ? StructSelect<S, StructFieldsOf<Fld>> | boolean
  : boolean

export type Select<S, N extends keyof S> = { [K in keyof Row<S, N>]?: K extends keyof FieldsOf<S[N]> ? FieldSelect<S, FieldsOf<S[N]>[K]> : boolean }
type StructSelect<S, F extends Fields> = { [K in keyof F]?: FieldSelect<S, F[K]> }

type ScalarWhere<T, M extends FieldMeta> =
  | Nullable<M, CompareValue<T>>
  | (IsNum<M> extends true ? CompareNumValue<T> : M["scalar"] extends "string" ? CompareStrValue<T> : never)

type FieldWhere<S, Fld, M extends FieldMeta = MetaOf<Fld>> =
  M["kind"] extends "one" ? CompareRefValue<Where<S, ModelName<S, Fld>> | null>
  : M["kind"] extends "many" | "list" ? CompareRefListValue<Where<S, ModelName<S, Fld>>>
  : M["kind"] extends "struct" ? CompareRefValue<WhereValue<StructWhereFields<S, StructFieldsOf<Fld>>> | null>
  : M["kind"] extends "json" ? JsonPathWhere | JsonCondition
  : M["array"] extends true ? CompareValue<TsOf<Fld>>
  : ScalarWhere<TsOf<Fld>, M> | CustomSearchValue<FullTextSearch> | CustomSearchValue<VectorSearch>

type WhereFieldsOf<S, F extends Fields> = { [K in keyof F]?: FieldWhere<S, F[K]> }
type IdWhere<S, N extends keyof S> = { [K in keyof Id<S, N> as K extends keyof FieldsOf<S[N]> ? never : K]?: CompareValue<Id<S, N>[K]> | CompareNumValue<number> }
export type Where<S, N extends keyof S> = WhereValue<WhereFieldsOf<S, FieldsOf<S[N]>> & IdWhere<S, N>>
type StructWhereFields<S, F extends Fields> = WhereFieldsOf<S, F>

export type Order<S, N extends keyof S> = { [K in (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>]?: "asc" | "desc" }

/** The query object — marcidb's wire form: a select shape plus the `$`-clauses. */
export type QueryObject<S, N extends keyof S> = Select<S, N> & {
  $where?: Where<S, N>
  $order?: Order<S, N>
  $limit?: number
  $skip?: number
  $cursor?: Id<S, N>
}

export type AggregateQuery<S, N extends keyof S> = {
  $where?: Where<S, N>
  $count?: true
  $sum?: NumKeys<FieldsOf<S[N]>> & string
  $avg?: NumKeys<FieldsOf<S[N]>> & string
  $min?: (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string
  $max?: (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string
}

// ───────────────────────────── query / db ─────────────────────────────

/** The per-model type bag marcidb's generic `Query<T>` is parametrised by (what its codegen emits as `UserTypes`). */
export type Types<S, N extends keyof S> = {
  name: N & string
  model: Row<S, N>
  id: Id<S, N>
  scalars: ScalarSelect<S, N>
  select: Select<S, N>
  query: QueryObject<S, N>
  where: Where<S, N>
  order: Order<S, N>
  insert: Insert<S, N>
  update: Update<S, N>
  aggregate: AggregateQuery<S, N>
  reindex: false
}

/**
 * `db.<model>` — marcidb's lazy, immutable query over one model: clauses (`where/order/limit/skip/after`)
 * return a new query, `select(shape)` sets the projection, `await` runs it, `first/count/aggregate` terminate,
 * `updateMany(data)`/`deleteMany()` write through the chain's `where`; a query placed in another query's
 * select is a sub-query. `Sel` is the projection: id + scalars until `select(...)` sets a shape.
 */
export type Query<S, N extends keyof S, Sel = ScalarSelect<S, N>> = MarciQuery<Types<S, N>, Sel>
/** The rows a query with projection `Q` resolves to (no field keys = id + scalars; sub-queries resolved). */
export type Result<S, N extends keyof S, Q> = Rows<Types<S, N>, Q>
/** `db.<model>` — the root query of a model. */
export type Collection<S, N extends keyof S> = Query<S, N>

export type Db<S extends Schema> = {
  [N in keyof S & string as Uncapitalize<N>]: Query<S, N>
} & {
  /** Atomic batch: all ops commit or none. `ref("0.id")` feeds a generated id into a later op. */
  $transaction<P extends readonly Op<any>[]>(ops: [...P]): Promise<{ [K in keyof P]: P[K] extends Op<infer T> ? T : never }>
  /** The `.marci` schema text this db syncs with. */
  readonly $schema: string
  /** @internal */ readonly $models: Record<string, Model>
  /** @internal which models the auth runtime binds to (`Session` always exists; `User` when declared). */
  readonly $auth: AuthBinding
}
/** @internal */
export type AuthBinding = { user: string | null, session: string }

// ───────────────────────────── relation-name validation ─────────────────────────────

type RelKinds = "one" | "many" | "list"
type CheckField<S, Fld> =
  MetaOf<Fld>["kind"] extends RelKinds
    ? MetaOf<Fld>["model"] extends keyof S ? Fld : `Unknown model "${MetaOf<Fld>["model"]}" — not a key of defineDb({...})`
    : Fld
/** Intersected with `S` in `defineDb`'s parameter: an unknown relation target fails to type-check on that field. */
export type ValidateRefs<S extends Schema> = { [N in keyof S]: { readonly fields: { [K in keyof FieldsOf<S[N]>]: CheckField<S, FieldsOf<S[N]>[K]> } } }
