/**
 * `auth.model.user({...})` / `auth.model.session()` — the ONLY place auth touches the schema
 * (docs/backend-plan.md §3.4 / decision 10). Both return ordinary `model(...)`s: the developer registers
 * them in `defineDb({ User, Session, … })` under exactly those keys (other models relate to them with
 * `t.one("User")` / `t.one("Session")`), and `defineDb` adds `Session` itself when it isn't declared —
 * a device session exists from the first request, before any login, so every project db has one.
 *
 * Provider identity columns (`googleId`, `appleId`) follow `app.json` `auth.providers`, which the
 * compiler hands the server bundle through ./appConfig.ts (a module it replaces at compile time).
 */

import { t, type Field } from "../db/fields"
import type { Fields, Model } from "../db/types"
import { app, type AppConfig, type AuthProvider } from "./appConfig"

export type { AppConfig, AuthProvider }

const seam = globalThis as unknown as { __lecodesApp?: AppConfig }
/** The project's `app.json` subset: the compiled-in module, else the test/tooling seam, else empty. */
export const appConfig = (): AppConfig => (Object.keys(app).length ? app : seam.__lecodesApp ?? {})
export const enabledProviders = (): AuthProvider[] => appConfig().auth?.providers ?? []

/** The model name the auth runtime binds to. */
export const AUTH_USER_MODEL = "User"
export const AUTH_SESSION_MODEL = "Session"

/** Marker on the two auth models (`model()` objects are otherwise plain). */
export type AuthModelKind = "user" | "session"
export type AuthModel<F extends Fields> = Model<F> & { readonly __auth: AuthModelKind }

// ───────────────────────────── field sets ─────────────────────────────

/** Built-in `User` fields (what `auth.user` is typed with). */
export const userFields = () => ({
  email:     t.string().optional().unique(),
  name:      t.string().optional(),
  avatar:    t.string().optional(),
  /** Plain string column, `"user"` by default — `auth.requireUser({ role: "admin" })` is field equality. */
  role:      t.string().default("user"),
  createdAt: t.date().default("now"),
})
export type UserFields = ReturnType<typeof userFields>

/** `<provider>Id` identity columns for the OAuth providers enabled in app.json. */
const providerFields = (): Record<string, Field<any, any>> => {
  const out: Record<string, Field<any, any>> = {}
  for (const p of enabledProviders()) if (p !== "email") out[`${p}Id`] = t.string().optional().unique()
  return out
}

/** Built-in `Session` fields. `user` is added by `defineDb` when a `User` model exists (schema-time, so a
 *  db without an auth user model has sessions without a user column). */
export const sessionFields = () => ({
  /** sha256 of the bearer token the device holds — the token itself is never stored. */
  tokenHash:    t.string().unique(),
  createdAt:    t.date().default("now"),
  lastSeenAt:   t.date().default("now"),
  revoked:      t.bool().default(false),
  // email-code sign-in state lives on the requesting session (no code table)
  pendingEmail: t.string().optional(),
  codeHash:     t.string().optional(),
  codeExpires:  t.date().optional(),
  codeAttempts: t.int().default(0),
})
export type SessionFields = ReturnType<typeof sessionFields> & { user: ReturnType<typeof sessionUserField> }
export const sessionUserField = () => t.one(AUTH_USER_MODEL).optional().onDelete("cascade")

// ───────────────────────────── factories ─────────────────────────────

// Built inline (not via defineDb's `model()`) to keep this module import-free of defineDb, which imports us.
const authModel = <F extends Fields>(kind: AuthModelKind, fields: F): AuthModel<F> =>
  ({ fields, __model: true, __auth: kind }) as AuthModel<F>

/** `auth.model.user({...extra})` — the built-in user (+ provider ids from app.json) plus the developer's fields. */
export const authUserModel = <E extends Fields = {}>(extra?: E): AuthModel<UserFields & E> =>
  authModel("user", { ...userFields(), ...providerFields(), ...(extra ?? {}) } as UserFields & E)

/** `auth.model.session({...extra})` — declare it only to relate to sessions (`t.one("Session")`) or to add fields. */
export const authSessionModel = <E extends Fields = {}>(extra?: E): AuthModel<SessionFields & E> =>
  authModel("session", { ...sessionFields(), ...(extra ?? {}) } as unknown as SessionFields & E)

export const isAuthModel = (m: unknown, kind?: AuthModelKind): m is AuthModel<any> =>
  typeof m === "object" && m !== null && "__auth" in m && (kind === undefined || (m as AuthModel<any>).__auth === kind)
