import { sql } from "drizzle-orm";
import { bigserial, boolean, check, index, integer, jsonb, numeric, pgTable, primaryKey, text, timestamp, uniqueIndex, uuid, type AnyPgColumn } from "drizzle-orm/pg-core";
import type { Transaction } from "@platform-modules/db";

/** Serializable, recursively bounded field definition/value payload. */
export type FieldStorageValue = string | number | boolean | null | readonly FieldStorageValue[] | { readonly [key: string]: FieldStorageValue };
export type FieldsDefinitionOrigin = "code" | "db" | "import";
export type FieldKey = string;
export type FieldPath = readonly (string | number)[];
export type ScalarChoice = string | number;
export interface SafePattern { readonly source: string; readonly flags?: string }
export type LocationOperator = "eq" | "neq" | "in" | "notIn" | "contains";
export type LocationValue = string | number | boolean | readonly (string | number)[];
export interface LocationRule { readonly parameter: string; readonly operator: LocationOperator; readonly value: LocationValue }
export type LocationRuleGroups = readonly (readonly LocationRule[])[];
export type ConditionalRule =
  | { readonly fieldPath: FieldPath; readonly operator: "eq" | "neq"; readonly value: string | number | boolean | null; readonly quantifier?: "any" | "all" }
  | { readonly fieldPath: FieldPath; readonly operator: "contains" | "notContains"; readonly value: string | number; readonly quantifier?: "any" | "all" }
  | { readonly fieldPath: FieldPath; readonly operator: "gt" | "gte" | "lt" | "lte"; readonly value: number; readonly quantifier?: "any" | "all" }
  | { readonly fieldPath: FieldPath; readonly operator: "empty" | "notEmpty"; readonly quantifier?: "any" | "all" }
  | { readonly fieldPath: FieldPath; readonly operator: "matches"; readonly value: SafePattern; readonly quantifier?: "any" | "all" };
export type ConditionalRuleGroups = readonly (readonly ConditionalRule[])[];
export interface WrapperSettings { readonly width?: number; readonly className?: string; readonly data?: Readonly<Record<string, string>> }
export interface PresentationSettings { readonly text?: string; readonly open?: boolean; readonly endpoint?: boolean }
export interface TextSettings { readonly placeholder?: string; readonly prepend?: string; readonly append?: string; readonly maxLength?: number; readonly pattern?: SafePattern }
export interface TextareaSettings extends TextSettings { readonly rows?: number; readonly newline: "preserve" | "br" | "paragraph" }
export interface NumberSettings { readonly min?: number; readonly max?: number; readonly step?: number; readonly integer?: boolean; readonly prepend?: string; readonly append?: string }
export interface UrlSettings extends TextSettings { readonly schemes: readonly string[]; readonly allowRelative?: boolean }
export interface PasswordSettings { readonly hasherKey: string; readonly minLength?: number; readonly maxLength?: number }
export interface BooleanSettings { readonly style?: "switch" | "checkbox"; readonly onLabel?: string; readonly offLabel?: string }
export interface ChoiceSettings { readonly choices: Readonly<Record<string,string>>; readonly multiple?: boolean; readonly allowNull?: boolean; readonly allowCustom?: boolean; readonly min?: number; readonly max?: number; readonly searchable?: boolean; readonly orientation?: "vertical" | "horizontal" }
export interface RichTextSettings { readonly toolbar?: string; readonly media?: boolean; readonly tabs?: readonly ("visual" | "source")[]; readonly delayedInit?: boolean }
export interface EmbedSettings { readonly providers?: readonly string[]; readonly width?: number; readonly height?: number }
export interface LinkSettings { readonly schemes: readonly string[]; readonly targets?: readonly ("_self" | "_blank")[] }
export interface MediaSettings { readonly mimeTypes?: readonly string[]; readonly minBytes?: number; readonly maxBytes?: number; readonly source?: "all" | "uploadedToEntity"; readonly return: "ref" | "id" | "url" }
export interface ImageMediaSettings extends MediaSettings { readonly minWidth?: number; readonly maxWidth?: number; readonly minHeight?: number; readonly maxHeight?: number }
export interface GallerySettings extends ImageMediaSettings { readonly min?: number; readonly max?: number; readonly previewSize?: string }
export interface EntitySettings { readonly entityTypes?: readonly string[]; readonly statuses?: readonly string[]; readonly taxonomies?: Readonly<Record<string, readonly string[]>>; readonly return: "ref" | "id" | "object" }
export interface RelationshipSettings extends EntitySettings { readonly min?: number; readonly max?: number; readonly bidirectional?: { readonly targetFieldKey: string } }
export interface TaxonomySettings { readonly taxonomies: readonly string[]; readonly multiple?: boolean; readonly allowCreate?: boolean; readonly saveTerms?: boolean; readonly loadTerms?: boolean; readonly return: "ref" | "id" | "object" }
export interface UserSettings { readonly roles?: readonly string[]; readonly multiple?: boolean; readonly return: "ref" | "id" | "object" }
export interface DateSettings { readonly inputFormat: string; readonly displayFormat: string; readonly returnFormat: string; readonly firstWeekday?: number }
export interface DateTimeSettings extends DateSettings { readonly timezone: "site" | "user" | "utc" }
export interface TimeSettings { readonly inputFormat: string; readonly displayFormat: string; readonly returnFormat: string }
export interface ColorSettings { readonly alpha?: boolean; readonly palette?: readonly string[] }
export interface IconSettings { readonly sets?: readonly string[]; readonly allowNull?: boolean }
export interface MapSettings { readonly providerKey: string; readonly center?: { readonly latitude: number; readonly longitude: number }; readonly zoom?: number; readonly height?: number }
export interface GroupSettings { readonly fields: readonly AnyFieldDefinition[]; readonly layout?: "block" | "table" | "row" }
export interface RepeaterSettings extends GroupSettings { readonly min?: number; readonly max?: number; readonly collapsedBy?: FieldKey; readonly pagination?: { readonly pageSize: number } }
export interface FlexibleLayout { readonly key: string; readonly name: string; readonly label: string; readonly fields: readonly AnyFieldDefinition[] }
export interface FlexibleSettings { readonly layouts: readonly FlexibleLayout[]; readonly min?: number; readonly max?: number }
export interface CloneSettings { readonly sourceGroupKeys?: readonly string[]; readonly sourceFieldKeys?: readonly string[]; readonly display?: "seamless" | "group"; readonly prefixNames?: boolean }
export type FieldType = "text" | "textarea" | "number" | "range" | "email" | "url" | "password" | "boolean" | "select" | "checkbox" | "radio" | "buttonGroup" | "richText" | "oembed" | "link" | "message" | "image" | "file" | "gallery" | "entity" | "entityLink" | "relationship" | "taxonomy" | "user" | "date" | "dateTime" | "time" | "color" | "icon" | "map" | "accordion" | "tab" | "group" | "repeater" | "flexible" | "clone";
type SettingsFor<T extends FieldType> = T extends "textarea" ? TextareaSettings : T extends "number" | "range" ? NumberSettings : T extends "url" ? UrlSettings : T extends "password" ? PasswordSettings : T extends "boolean" ? BooleanSettings : T extends "select" | "checkbox" | "radio" | "buttonGroup" ? ChoiceSettings : T extends "richText" ? RichTextSettings : T extends "oembed" ? EmbedSettings : T extends "link" ? LinkSettings : T extends "image" ? ImageMediaSettings : T extends "file" ? MediaSettings : T extends "gallery" ? GallerySettings : T extends "entity" | "entityLink" ? EntitySettings : T extends "relationship" ? RelationshipSettings : T extends "taxonomy" ? TaxonomySettings : T extends "user" ? UserSettings : T extends "date" ? DateSettings : T extends "dateTime" ? DateTimeSettings : T extends "time" ? TimeSettings : T extends "color" ? ColorSettings : T extends "icon" ? IconSettings : T extends "map" ? MapSettings : T extends "group" ? GroupSettings : T extends "repeater" ? RepeaterSettings : T extends "flexible" ? FlexibleSettings : T extends "clone" ? CloneSettings : T extends "message" | "accordion" | "tab" ? PresentationSettings : TextSettings;
interface BaseSnapshotField<T extends FieldType> { readonly key: FieldKey; readonly name: string; readonly label: string; readonly type: T; readonly instructions?: string; readonly required?: boolean; readonly defaultValue?: unknown; readonly settings: SettingsFor<T>; readonly wrapper?: WrapperSettings; readonly conditionalLogic?: ConditionalRuleGroups; readonly disabled?: boolean; readonly readOnly?: boolean; readonly presentation?: { readonly labelPlacement?: "top" | "left"; readonly instructionPlacement?: "label" | "field" } }
export type FieldDefinition<T extends FieldType = FieldType> = T extends FieldType ? T extends "password" | "message" | "accordion" | "tab" ? Omit<BaseSnapshotField<T>, "defaultValue"> & { readonly defaultValue?: never } : BaseSnapshotField<T> : never;
export type AnyFieldDefinition = FieldDefinition;
export interface FieldGroup { readonly key: string; readonly title: string; readonly fields: readonly AnyFieldDefinition[]; readonly location: LocationRuleGroups; readonly order?: number; readonly position?: "normal" | "side" | "afterTitle"; readonly style?: "default" | "seamless"; readonly labelPlacement?: "top" | "left"; readonly instructionPlacement?: "label" | "field"; readonly hideOnScreen?: readonly string[]; readonly active: boolean; readonly description?: string }

export interface NormalizedFieldsRegistryRecord { readonly key: string; readonly origin: FieldsDefinitionOrigin; readonly version: number; readonly revision: number; readonly active: boolean; readonly canonicalHash: string; readonly definition: FieldGroup; readonly shadowedDbVersion?: number }
export interface FieldDefinitionVersion { readonly groupKey: string; readonly revision: number; readonly canonicalHash: string; readonly definition: FieldGroup; readonly createdAt: Date; readonly origin: FieldsDefinitionOrigin }
export type FieldDefinitionVersionErrorCode = "invalid-definition-version";
export class FieldDefinitionVersionError extends Error { override readonly name = "FieldDefinitionVersionError"; readonly code: FieldDefinitionVersionErrorCode = "invalid-definition-version"; constructor(readonly field: string, readonly detail: string) { super(`field definition version ${field}: ${detail}`); } }
export function isFieldDefinitionVersionError(error: unknown): error is FieldDefinitionVersionError { return typeof error === "object" && error !== null && (error as {code?:unknown}).code === "invalid-definition-version" && typeof (error as {field?:unknown}).field === "string" && typeof (error as {detail?:unknown}).detail === "string"; }
const MAX_DEPTH = 12, MAX_NODES = 1_000, MAX_INPUT_NODES = 10_000, MAX_STRING = 4096, MAX_BYTES = 256 * 1024, MAX_ITEMS = 1000;
type Budget = { nodes: number; bytes: number };
const forbidden = new Set(["__proto__", "prototype", "constructor"]);
function fail(field: string, detail: string): never { throw new FieldDefinitionVersionError(field, detail); }
function preflight(value: unknown, budget: Budget, depth = 0, seen = new Set<object>()): void { if (depth > 64) fail("fieldDefinitionVersion", "exceeds maximum depth"); if (value === null || typeof value === "boolean") { budget.bytes += 5; return; } if (typeof value === "string") { budget.bytes += new TextEncoder().encode(value).byteLength; if (value.length > MAX_STRING) fail("fieldDefinitionVersion", "contains an oversized string"); return; } if (typeof value === "number") { if (!Number.isFinite(value)) fail("fieldDefinitionVersion", "contains a non-finite number"); budget.bytes += 8; return; } if (value instanceof Date) { if (Object.getOwnPropertyNames(value).length !== 0 || Object.getOwnPropertySymbols(value).length !== 0 || Number.isNaN(Date.prototype.getTime.call(value))) fail("fieldDefinitionVersion", "contains an invalid Date"); budget.bytes += 24; return; } if (typeof value !== "object" || value === null || seen.has(value)) fail("fieldDefinitionVersion", "must be an acyclic data graph"); if (++budget.nodes > MAX_INPUT_NODES || budget.bytes > MAX_BYTES) fail("fieldDefinitionVersion", "exceeds maximum input size"); seen.add(value); const isArray = Array.isArray(value); if ((!isArray && Object.getPrototypeOf(value) !== Object.prototype) || Object.getOwnPropertySymbols(value).length) fail("fieldDefinitionVersion", "must contain only plain data objects and arrays"); const descriptors = Object.getOwnPropertyDescriptors(value); const keys = Object.keys(value); if (keys.length > MAX_ITEMS) fail("fieldDefinitionVersion", "contains an oversized collection"); for (const key of keys) { const d = descriptors[key]!; if (forbidden.has(key) || !d.enumerable || !("value" in d)) fail(`fieldDefinitionVersion.${key}`, "contains a forbidden key or accessor"); budget.bytes += new TextEncoder().encode(key).byteLength; preflight(d.value, budget, depth + 1, seen); } seen.delete(value); if (budget.bytes > MAX_BYTES) fail("fieldDefinitionVersion", "exceeds maximum input size"); }
function cloneData(value: unknown): unknown { if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value; if (Array.isArray(value)) return Object.freeze(value.map(cloneData)); const source = obj(value, "defaultValue"); const output: Record<string, unknown> = {}; for (const key of Object.keys(source)) output[key] = cloneData(source[key]); return Object.freeze(output); }
function obj(value: unknown, path: string): Record<string, unknown> { if (typeof value !== "object" || value === null || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) fail(path, "must be a plain object"); return value as Record<string,unknown>; }
function keys(v: Record<string,unknown>, allowed: readonly string[], path: string): void { for (const k of Object.keys(v)) if (!allowed.includes(k)) fail(`${path}.${k}`, "is not allowed"); }
function str(v: unknown, path: string): string { if (typeof v !== "string" || !v.length || v.length > MAX_STRING) fail(path, "must be a bounded non-empty string"); return v; }
function optStr(v: unknown, p: string): string | undefined { return v === undefined ? undefined : str(v,p); }
function bool(v: unknown, p: string): boolean | undefined { if (v === undefined) return; if (typeof v !== "boolean") fail(p,"must be a boolean"); return v; }
function num(v: unknown,p:string, integer=false, positive=false): number | undefined { if(v===undefined)return; if(typeof v!=="number"||!Number.isFinite(v)||(integer&&!Number.isInteger(v))||(positive&&v<=0))fail(p,`must be a ${positive?"positive ":""}${integer?"integer":"finite number"}`); return v; }
function enumValue<T extends string>(v: unknown, values: readonly T[], p: string): T { if(typeof v!=="string" || !values.includes(v as T)) fail(p,"has an invalid value"); return v as T; }
function array(v: unknown,p:string,max=MAX_ITEMS): unknown[] { if(!Array.isArray(v)||v.length>max)fail(p,"must be a bounded array"); return v; }
function strings(v:unknown,p:string,required=false): readonly string[]|undefined { if(v===undefined&&!required)return; return Object.freeze(array(v,p).map((x,i)=>str(x,`${p}.${i}`))); }
function boundedMap(v:unknown,p:string): Readonly<Record<string,string>> { const x=obj(v,p); const out:Record<string,string>={}; for(const k of Object.keys(x)){ if(!k || k.length>MAX_STRING) fail(p,"has invalid key"); out[k]=str(x[k],`${p}.${k}`); } return Object.freeze(out); }
function pair(min:number|undefined,max:number|undefined,p:string):void { if(min!==undefined&&max!==undefined&&min>max)fail(p,"minimum exceeds maximum"); }
function safePattern(v:unknown,p:string): SafePattern { const x=obj(v,p); keys(x,["source","flags"],p); const source=str(x.source,`${p}.source`); const flags=x.flags===undefined?undefined:str(x.flags,`${p}.flags`); if(flags!==undefined&&!/^[dgimsuvy]*$/.test(flags))fail(`${p}.flags`,"has invalid flags"); return Object.freeze({source,...(flags===undefined?{}:{flags})}); }
function fieldPath(v:unknown,p:string): FieldPath { const a=array(v,p,64); if(!a.length)fail(p,"must not be empty"); return Object.freeze(a.map((x,i)=>typeof x==="number"&&Number.isSafeInteger(x)&&x>=0?x:str(x,`${p}.${i}`))); }
function conditional(v:unknown,p:string): ConditionalRuleGroups|undefined { if(v===undefined)return; return Object.freeze(array(v,p,64).map((group,i)=>Object.freeze(array(group,`${p}.${i}`,64).map((raw,j)=>{const x=obj(raw,`${p}.${i}.${j}`); keys(x,["fieldPath","operator","value","quantifier"],`${p}.${i}.${j}`); const operator=enumValue(x.operator,["eq","neq","contains","notContains","gt","gte","lt","lte","empty","notEmpty","matches"] as const,`${p}.${i}.${j}.operator`); const fp=fieldPath(x.fieldPath,`${p}.${i}.${j}.fieldPath`); const q=x.quantifier===undefined?undefined:enumValue(x.quantifier,["any","all"],`${p}.${i}.${j}.quantifier`); let value:unknown=x.value; if(["empty","notEmpty"].includes(operator)){if(value!==undefined)fail(`${p}.${i}.${j}.value`,"is not allowed");} else if(operator==="matches") value=safePattern(value,`${p}.${i}.${j}.value`); else if(["gt","gte","lt","lte"].includes(operator)){if(typeof value!=="number"||!Number.isFinite(value))fail(`${p}.${i}.${j}.value`,"must be finite number");} else if(operator==="contains"||operator==="notContains"){if(typeof value!=="string"&&typeof value!=="number")fail(`${p}.${i}.${j}.value`,"has invalid value");} else if(!(value===null||typeof value==="string"||typeof value==="number"||typeof value==="boolean"))fail(`${p}.${i}.${j}.value`,"has invalid value"); return Object.freeze({fieldPath:fp,operator,...(value===undefined?{}:{value}),...(q===undefined?{}:{quantifier:q})}) as ConditionalRule;})))); }
function location(v:unknown,p:string): LocationRuleGroups { return Object.freeze(array(v,p,64).map((group,i)=>Object.freeze(array(group,`${p}.${i}`,64).map((raw,j)=>{const x=obj(raw,`${p}.${i}.${j}`);keys(x,["parameter","operator","value"],`${p}.${i}.${j}`);const op=enumValue(x.operator,["eq","neq","in","notIn","contains"],`${p}.${i}.${j}.operator`);const value=x.value; const isList=Array.isArray(value); if((op==="in"||op==="notIn")?!isList:isList || !(typeof value==="string"||typeof value==="number"||typeof value==="boolean"))fail(`${p}.${i}.${j}.value`,"has invalid operator/value combination"); const copied=isList?Object.freeze(array(value,`${p}.${i}.${j}.value`,MAX_ITEMS).map((item,k)=>typeof item==="string"||typeof item==="number"?item:fail(`${p}.${i}.${j}.value.${k}`,"must be string or number"))):value as string|number|boolean; return Object.freeze({parameter:str(x.parameter,`${p}.${i}.${j}.parameter`),operator:op,value:copied}); })))); }
const builtin = new Set<FieldType>(["text","textarea","number","range","email","url","password","boolean","select","checkbox","radio","buttonGroup","richText","oembed","link","message","image","file","gallery","entity","entityLink","relationship","taxonomy","user","date","dateTime","time","color","icon","map","accordion","tab","group","repeater","flexible","clone"]);
function settings(value:unknown,type:FieldType,p:string,depth:number,b:Budget): unknown { const x=obj(value,p); const common=(allowed:string[])=>keys(x,allowed,p); const copy=(allowed:string[])=>{common(allowed);const out:Record<string,unknown>={};for(const k of allowed)if(x[k]!==undefined)out[k]=x[k];return out};
 if(type==="text"||type==="email"){const o=copy(["placeholder","prepend","append","maxLength","pattern"]);for(const k of ["placeholder","prepend","append"])if(o[k]!==undefined)o[k]=str(o[k],`${p}.${k}`);o.maxLength=num(o.maxLength,`${p}.maxLength`,true);if(o.maxLength!==undefined&&(o.maxLength as number)<0)fail(`${p}.maxLength`,"must be non-negative");if(o.pattern!==undefined)o.pattern=safePattern(o.pattern,`${p}.pattern`);return Object.freeze(o)}
 if(type==="textarea"){const o=copy(["placeholder","prepend","append","maxLength","pattern","rows","newline"]);for(const k of ["placeholder","prepend","append"])if(o[k]!==undefined)o[k]=str(o[k],`${p}.${k}`);o.maxLength=num(o.maxLength,`${p}.maxLength`,true);if(o.maxLength!==undefined&&(o.maxLength as number)<0)fail(`${p}.maxLength`,"must be non-negative");o.rows=num(o.rows,`${p}.rows`,true,true);o.newline=enumValue(o.newline,["preserve","br","paragraph"],`${p}.newline`);if(o.pattern!==undefined)o.pattern=safePattern(o.pattern,`${p}.pattern`);return Object.freeze(o)}
 if(type==="number"||type==="range"){const o=copy(["min","max","step","integer","prepend","append"]);o.min=num(o.min,`${p}.min`);o.max=num(o.max,`${p}.max`);o.step=num(o.step,`${p}.step`,false,true);pair(o.min as number|undefined,o.max as number|undefined,p);o.integer=bool(o.integer,`${p}.integer`);for(const k of ["prepend","append"])if(o[k]!==undefined)o[k]=str(o[k],`${p}.${k}`);return Object.freeze(o)}
 if(type==="url"){const o=copy(["placeholder","prepend","append","maxLength","pattern","schemes","allowRelative"]);for(const k of ["placeholder","prepend","append"])if(o[k]!==undefined)o[k]=str(o[k],`${p}.${k}`);o.maxLength=num(o.maxLength,`${p}.maxLength`,true);if(o.maxLength!==undefined&&(o.maxLength as number)<0)fail(`${p}.maxLength`,"must be non-negative");o.schemes=strings(o.schemes,`${p}.schemes`,true);o.allowRelative=bool(o.allowRelative,`${p}.allowRelative`);if(o.pattern!==undefined)o.pattern=safePattern(o.pattern,`${p}.pattern`);return Object.freeze(o)}
 if(type==="password"){const o=copy(["hasherKey","minLength","maxLength"]);o.hasherKey=str(o.hasherKey,`${p}.hasherKey`);o.minLength=num(o.minLength,`${p}.minLength`,true);o.maxLength=num(o.maxLength,`${p}.maxLength`,true);pair(o.minLength as number|undefined,o.maxLength as number|undefined,p);return Object.freeze(o)}
 if(type==="boolean"){const o=copy(["style","onLabel","offLabel"]);if(o.style!==undefined)o.style=enumValue(o.style,["switch","checkbox"],`${p}.style`);for(const k of ["onLabel","offLabel"])if(o[k]!==undefined)o[k]=str(o[k],`${p}.${k}`);return Object.freeze(o)}
 if(["select","checkbox","radio","buttonGroup"].includes(type)){const o=copy(["choices","multiple","allowNull","allowCustom","min","max","searchable","orientation"]);o.choices=boundedMap(o.choices,`${p}.choices`);for(const k of ["multiple","allowNull","allowCustom","searchable"])o[k]=bool(o[k],`${p}.${k}`);o.min=num(o.min,`${p}.min`,true);o.max=num(o.max,`${p}.max`,true);pair(o.min as number|undefined,o.max as number|undefined,p);if((o.min as number|undefined)!==undefined&&(o.min as number)<0)fail(`${p}.min`,"must be non-negative");if(o.orientation!==undefined)o.orientation=enumValue(o.orientation,["vertical","horizontal"],`${p}.orientation`);return Object.freeze(o)}
 if(type==="richText"){const o=copy(["toolbar","media","tabs","delayedInit"]);if(o.toolbar!==undefined)o.toolbar=str(o.toolbar,`${p}.toolbar`);o.media=bool(o.media,`${p}.media`);if(o.tabs!==undefined)o.tabs=Object.freeze(array(o.tabs,`${p}.tabs`).map((v,i)=>enumValue(v,["visual","source"],`${p}.tabs.${i}`)));o.delayedInit=bool(o.delayedInit,`${p}.delayedInit`);return Object.freeze(o)}
 if(type==="oembed"){const o=copy(["providers","width","height"]);o.providers=strings(o.providers,`${p}.providers`);o.width=num(o.width,`${p}.width`,true,true);o.height=num(o.height,`${p}.height`,true,true);return Object.freeze(o)}
 if(type==="link"){const o=copy(["schemes","targets"]);o.schemes=strings(o.schemes,`${p}.schemes`,true);if(o.targets!==undefined)o.targets=Object.freeze(array(o.targets,`${p}.targets`).map((v,i)=>enumValue(v,["_self","_blank"],`${p}.targets.${i}`)));return Object.freeze(o)}
 if(["message","accordion","tab"].includes(type)){const o=copy(["text","open","endpoint"]);if(o.text!==undefined)o.text=str(o.text,`${p}.text`);o.open=bool(o.open,`${p}.open`);o.endpoint=bool(o.endpoint,`${p}.endpoint`);return Object.freeze(o)}
 if(["image","file","gallery"].includes(type)){const allow=["mimeTypes","minBytes","maxBytes","source","return",...(type!=="file"?["minWidth","maxWidth","minHeight","maxHeight"]:[]),...(type==="gallery"?["min","max","previewSize"]:[])];const o=copy(allow);o.mimeTypes=strings(o.mimeTypes,`${p}.mimeTypes`);for(const k of ["minBytes","maxBytes","minWidth","maxWidth","minHeight","maxHeight","min","max"])o[k]=num(o[k],`${p}.${k}`,true);pair(o.minBytes as number|undefined,o.maxBytes as number|undefined,p);pair(o.minWidth as number|undefined,o.maxWidth as number|undefined,p);pair(o.minHeight as number|undefined,o.maxHeight as number|undefined,p);pair(o.min as number|undefined,o.max as number|undefined,p);if((o.min as number|undefined)!==undefined&&(o.min as number)<0)fail(`${p}.min`,"must be non-negative");if(o.source!==undefined)o.source=enumValue(o.source,["all","uploadedToEntity"],`${p}.source`);o.return=enumValue(o.return,["ref","id","url"],`${p}.return`);if(o.previewSize!==undefined)o.previewSize=str(o.previewSize,`${p}.previewSize`);return Object.freeze(o)}
 if(["entity","entityLink","relationship"].includes(type)){const o=copy(["entityTypes","statuses","taxonomies","return",...(type==="relationship"?["min","max","bidirectional"]:[])]);o.entityTypes=strings(o.entityTypes,`${p}.entityTypes`);o.statuses=strings(o.statuses,`${p}.statuses`);if(o.taxonomies!==undefined){const tax=obj(o.taxonomies,`${p}.taxonomies`),out:Record<string,readonly string[]>={};for(const k of Object.keys(tax))out[k]=strings(tax[k],`${p}.taxonomies.${k}`,true)!;o.taxonomies=Object.freeze(out)}o.return=enumValue(o.return,["ref","id","object"],`${p}.return`);o.min=num(o.min,`${p}.min`,true);o.max=num(o.max,`${p}.max`,true);pair(o.min as number|undefined,o.max as number|undefined,p);if((o.min as number|undefined)!==undefined&&(o.min as number)<0)fail(`${p}.min`,"must be non-negative");if(o.bidirectional!==undefined){const q=obj(o.bidirectional,`${p}.bidirectional`);keys(q,["targetFieldKey"],`${p}.bidirectional`);o.bidirectional=Object.freeze({targetFieldKey:str(q.targetFieldKey,`${p}.bidirectional.targetFieldKey`)})}return Object.freeze(o)}
 if(type==="taxonomy"){const o=copy(["taxonomies","multiple","allowCreate","saveTerms","loadTerms","return"]);o.taxonomies=strings(o.taxonomies,`${p}.taxonomies`,true);for(const k of ["multiple","allowCreate","saveTerms","loadTerms"])o[k]=bool(o[k],`${p}.${k}`);o.return=enumValue(o.return,["ref","id","object"],`${p}.return`);return Object.freeze(o)}
 if(type==="user"){const o=copy(["roles","multiple","return"]);o.roles=strings(o.roles,`${p}.roles`);o.multiple=bool(o.multiple,`${p}.multiple`);o.return=enumValue(o.return,["ref","id","object"],`${p}.return`);return Object.freeze(o)}
 if(["date","dateTime","time"].includes(type)){const o=copy(["inputFormat","displayFormat","returnFormat",...(type!=="time"?["firstWeekday"]:[]),...(type==="dateTime"?["timezone"]:[])]);for(const k of ["inputFormat","displayFormat","returnFormat"])o[k]=str(o[k],`${p}.${k}`);o.firstWeekday=num(o.firstWeekday,`${p}.firstWeekday`,true);if(o.firstWeekday!==undefined&&((o.firstWeekday as number)<0||(o.firstWeekday as number)>6))fail(`${p}.firstWeekday`,"must be between 0 and 6");if(o.timezone!==undefined)o.timezone=enumValue(o.timezone,["site","user","utc"],`${p}.timezone`);return Object.freeze(o)}
 if(type==="color"){const o=copy(["alpha","palette"]);o.alpha=bool(o.alpha,`${p}.alpha`);o.palette=strings(o.palette,`${p}.palette`);return Object.freeze(o)}
 if(type==="icon"){const o=copy(["sets","allowNull"]);o.sets=strings(o.sets,`${p}.sets`);o.allowNull=bool(o.allowNull,`${p}.allowNull`);return Object.freeze(o)}
 if(type==="map"){const o=copy(["providerKey","center","zoom","height"]);o.providerKey=str(o.providerKey,`${p}.providerKey`);if(o.center!==undefined){const c=obj(o.center,`${p}.center`);keys(c,["latitude","longitude"],`${p}.center`);o.center=Object.freeze({latitude:num(c.latitude,`${p}.center.latitude`)!,longitude:num(c.longitude,`${p}.center.longitude`)!})}o.zoom=num(o.zoom,`${p}.zoom`);o.height=num(o.height,`${p}.height`,true,true);return Object.freeze(o)}
 if(type==="group"||type==="repeater"){const allow=type==="group"?["fields","layout"]:["fields","layout","min","max","collapsedBy","pagination"];const o=copy(allow);o.fields=parseFields(o.fields,`${p}.fields`,depth+1,b);if(o.layout!==undefined)o.layout=enumValue(o.layout,["block","table","row"],`${p}.layout`);o.min=num(o.min,`${p}.min`,true);o.max=num(o.max,`${p}.max`,true);pair(o.min as number|undefined,o.max as number|undefined,p);if(o.collapsedBy!==undefined)o.collapsedBy=str(o.collapsedBy,`${p}.collapsedBy`);if(o.pagination!==undefined){const q=obj(o.pagination,`${p}.pagination`);keys(q,["pageSize"],`${p}.pagination`);o.pagination=Object.freeze({pageSize:num(q.pageSize,`${p}.pagination.pageSize`,true,true)!})}return Object.freeze(o)}
 if(type==="flexible"){const o=copy(["layouts","min","max"]);o.layouts=Object.freeze(array(o.layouts,`${p}.layouts`).map((raw,i)=>{const q=obj(raw,`${p}.layouts.${i}`);keys(q,["key","name","label","fields"],`${p}.layouts.${i}`);return Object.freeze({key:str(q.key,`${p}.layouts.${i}.key`),name:str(q.name,`${p}.layouts.${i}.name`),label:str(q.label,`${p}.layouts.${i}.label`),fields:parseFields(q.fields,`${p}.layouts.${i}.fields`,depth+1,b)})}));const ls=o.layouts as readonly FlexibleLayout[];if(new Set(ls.map(l=>l.key)).size!==ls.length)fail(`${p}.layouts`,"contains duplicate layout keys");o.min=num(o.min,`${p}.min`,true);o.max=num(o.max,`${p}.max`,true);pair(o.min as number|undefined,o.max as number|undefined,p);return Object.freeze(o)}
 const o=copy(["sourceGroupKeys","sourceFieldKeys","display","prefixNames"]);o.sourceGroupKeys=strings(o.sourceGroupKeys,`${p}.sourceGroupKeys`);o.sourceFieldKeys=strings(o.sourceFieldKeys,`${p}.sourceFieldKeys`);if(o.sourceGroupKeys===undefined&&o.sourceFieldKeys===undefined)fail(p,"must name a clone source");if(o.display!==undefined)o.display=enumValue(o.display,["seamless","group"],`${p}.display`);o.prefixNames=bool(o.prefixNames,`${p}.prefixNames`);return Object.freeze(o);
}
function parseField(raw:unknown,p:string,depth:number,b:Budget): AnyFieldDefinition { if(depth>MAX_DEPTH)fail(p,"exceeds maximum recursive definition depth");if(++b.nodes>MAX_NODES)fail(p,"exceeds maximum field count");const x=obj(raw,p);keys(x,["key","name","label","type","instructions","required","defaultValue","settings","wrapper","conditionalLogic","disabled","readOnly","presentation"],p);const type=enumValue(x.type,[...builtin],`${p}.type`);const key=str(x.key,`${p}.key`);if(!/^[a-z][a-z0-9_]*$/.test(key))fail(`${p}.key`,"must be a normalized field key");if(["password","message","accordion","tab"].includes(type)&&x.defaultValue!==undefined)fail(`${p}.defaultValue`,"is not permitted for this field type");const out:Record<string,unknown>={key,name:str(x.name,`${p}.name`),label:str(x.label,`${p}.label`),type,settings:settings(x.settings,type,`${p}.settings`,depth,b)};for(const k of ["instructions"])if(x[k]!==undefined)out[k]=str(x[k],`${p}.${k}`);for(const k of ["required","disabled","readOnly"])if(x[k]!==undefined)out[k]=bool(x[k],`${p}.${k}`);if(x.defaultValue!==undefined)out.defaultValue=cloneData(x.defaultValue);if(x.wrapper!==undefined){const q=obj(x.wrapper,`${p}.wrapper`);keys(q,["width","className","data"],`${p}.wrapper`);const w:Record<string,unknown>={};w.width=num(q.width,`${p}.wrapper.width`);if(w.width!==undefined&&((w.width as number)<0||(w.width as number)>100))fail(`${p}.wrapper.width`,"must be between 0 and 100");if(q.className!==undefined)w.className=str(q.className,`${p}.wrapper.className`);if(q.data!==undefined)w.data=boundedMap(q.data,`${p}.wrapper.data`);out.wrapper=Object.freeze(w)}const c=conditional(x.conditionalLogic,`${p}.conditionalLogic`);if(c!==undefined)out.conditionalLogic=c;if(x.presentation!==undefined){const q=obj(x.presentation,`${p}.presentation`);keys(q,["labelPlacement","instructionPlacement"],`${p}.presentation`);out.presentation=Object.freeze({...(q.labelPlacement===undefined?{}:{labelPlacement:enumValue(q.labelPlacement,["top","left"],`${p}.presentation.labelPlacement`)}),...(q.instructionPlacement===undefined?{}:{instructionPlacement:enumValue(q.instructionPlacement,["label","field"],`${p}.presentation.instructionPlacement`)})})}return Object.freeze(out) as unknown as AnyFieldDefinition; }
function parseFields(v:unknown,p:string,depth:number,b:Budget): readonly AnyFieldDefinition[]{const fs=Object.freeze(array(v,p).map((f,i)=>parseField(f,`${p}.${i}`,depth,b)));if(new Set(fs.map(f=>f.key)).size!==fs.length)fail(p,"contains duplicate sibling field keys");return fs;}
/** Parses one built-in field definition through the same bounded trust boundary as immutable group snapshots. */
export function parseFieldDefinition(value:unknown): AnyFieldDefinition { preflight(value,{nodes:0,bytes:0}); return parseField(value,"definition",1,{nodes:0,bytes:0}); }
function parseFieldGroup(value:unknown): FieldGroup { const x=obj(value,"definition");keys(x,["key","title","fields","location","order","position","style","labelPlacement","instructionPlacement","hideOnScreen","active","description"],"definition");const key=str(x.key,"definition.key");if(!/^[a-z][a-z0-9_]*$/.test(key))fail("definition.key","must be a normalized group key");const b:Budget={nodes:0,bytes:0};const out:Record<string,unknown>={key,title:str(x.title,"definition.title"),fields:parseFields(x.fields,"definition.fields",1,b),location:location(x.location,"definition.location"),active:bool(x.active,"definition.active")};if(out.active===undefined)fail("definition.active","is required");out.order=num(x.order,"definition.order",true);if(x.position!==undefined)out.position=enumValue(x.position,["normal","side","afterTitle"],"definition.position");if(x.style!==undefined)out.style=enumValue(x.style,["default","seamless"],"definition.style");if(x.labelPlacement!==undefined)out.labelPlacement=enumValue(x.labelPlacement,["top","left"],"definition.labelPlacement");if(x.instructionPlacement!==undefined)out.instructionPlacement=enumValue(x.instructionPlacement,["label","field"],"definition.instructionPlacement");out.hideOnScreen=strings(x.hideOnScreen,"definition.hideOnScreen");if(x.description!==undefined)out.description=str(x.description,"definition.description");for(const k of Object.keys(out))if(out[k]===undefined)delete out[k];return Object.freeze(out) as unknown as FieldGroup; }
/** Parses one complete field-group definition at an untrusted runtime boundary. */
export function parseFieldGroupDefinition(value: unknown): FieldGroup { preflight(value,{nodes:0,bytes:0}); return parseFieldGroup(value); }
/** Parses a persisted/imported immutable snapshot before hashing or storage. */
export async function parseFieldDefinitionVersion(value:unknown): Promise<FieldDefinitionVersion> { preflight(value,{nodes:0,bytes:0});const x=obj(value,"fieldDefinitionVersion");keys(x,["groupKey","revision","canonicalHash","definition","createdAt","origin"],"fieldDefinitionVersion");const groupKey=str(x.groupKey,"fieldDefinitionVersion.groupKey");if(!/^[a-z][a-z0-9_]*$/.test(groupKey))fail("fieldDefinitionVersion.groupKey","must be a normalized group key");if(typeof x.revision!=="number"||!Number.isSafeInteger(x.revision)||x.revision<1)fail("fieldDefinitionVersion.revision","must be a positive safe integer");const canonicalHash=str(x.canonicalHash,"fieldDefinitionVersion.canonicalHash");if(!/^[a-f0-9]{64}$/i.test(canonicalHash))fail("fieldDefinitionVersion.canonicalHash","must be a SHA-256 hex digest");const origin=enumValue(x.origin,["code","db","import"],"fieldDefinitionVersion.origin");const created=x.createdAt instanceof Date?new Date(Date.prototype.getTime.call(x.createdAt)):typeof x.createdAt==="string"?new Date(x.createdAt):new Date("");if(Number.isNaN(created.getTime()))fail("fieldDefinitionVersion.createdAt","must be a valid Date or ISO timestamp");const definition=parseFieldGroup(x.definition);if(definition.key!==groupKey)fail("fieldDefinitionVersion.definition.key","must match groupKey");const normalizedHash=await canonicalFieldGroupHash(definition);if(canonicalHash.toLowerCase()!==normalizedHash)fail("fieldDefinitionVersion.canonicalHash","does not match the canonical definition bytes");return Object.freeze({groupKey,revision:x.revision,canonicalHash:normalizedHash,definition,createdAt:Object.freeze(created),origin}); }
function canonicalJson(value: unknown): string { if(value===undefined)return "null";if(value===null)return "null";if(typeof value==="string"||typeof value==="number"||typeof value==="boolean")return JSON.stringify(value);if(Array.isArray(value))return `[${value.map(canonicalJson).join(",")}]`;const record=value as Record<string,unknown>;return `{${Object.keys(record).filter(key=>record[key]!==undefined).sort().map(key=>`${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`; }
/** Computes the canonical immutable FieldGroup digest with the web-standard SHA-256 API. */
export async function canonicalFieldGroupHash(definition: FieldGroup): Promise<string> { const bytes=new TextEncoder().encode(canonicalJson(definition));const digest=await crypto.subtle.digest("SHA-256",bytes);return Array.from(new Uint8Array(digest),byte=>byte.toString(16).padStart(2,"0")).join(""); }

/**
 * Final recursive-node contract. Task 7 adds storage/migration for it; this
 * task only freezes the identity/path/lane shape while flat EAV remains read.
 */
export interface FieldValueNodeRow {
  readonly nodeId: string;
  readonly entityType: string;
  readonly entityId: string;
  readonly groupKey: string;
  readonly definitionRevision: number;
  readonly fieldKey: string;
  readonly path: string;
  readonly parentNodeId: string | null;
  readonly rowId: string | null;
  readonly layoutKey: string | null;
  readonly nodeKind: "group" | "repeaterRow" | "flexibleRow" | "leaf";
  readonly ordinal: number;
  readonly isNull: boolean;
  readonly valueText: string | null;
  readonly valueNumber: number | null;
  readonly valueBoolean: boolean | null;
  readonly valueDateTime: string | null;
  readonly valueRef: string | null;
  readonly valueJson: string | null;
}

/** The existing flat EAV rows read before the structural migration. */
export interface FlatFieldValue {
  readonly id: string;
  readonly entityType: string;
  readonly entityId: string;
  readonly groupId: string;
  readonly fieldKey: string;
  readonly ordinal: number;
  readonly valueText: string | null;
  readonly valueNum: string | null;
  readonly valueBool: boolean | null;
  readonly valueDate: Date | null;
  readonly refType: string | null;
  readonly refId: string | null;
  readonly refMeta: FieldStorageValue | null;
  readonly createdAt: Date;
  readonly updatedAt: Date;
}


export type FieldsDefinitionKind = "group" | "options" | "block";
export type FieldsMigrationAdapter = "d1" | "postgres";
export type FieldsMigrationPhase = "inventory" | "definitions" | "dual-write" | "backfill" | "parity" | "enforced" | "reverse";

/** Current immutable group registry state; historical bytes live in fieldDefinitionVersions. */
export const fieldGroupDefinitions = pgTable(
  "field_group_definitions",
  {
    key: text("key").primaryKey(),
    origin: text("origin").$type<FieldsDefinitionOrigin>().notNull(),
    version: integer("version").notNull().default(1),
    active: boolean("active").notNull().default(true),
    currentRevision: integer("current_revision").notNull().default(1),
    canonicalHash: text("canonical_hash").notNull(),
    definition: jsonb("definition").$type<FieldGroup>().notNull(),
    shadowedDbVersion: integer("shadowed_db_version"),
    hostSessionId: text("host_session_id"),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [
    check("field_group_definitions_version_positive", sql`${t.version} > 0`),
    check("field_group_definitions_revision_positive", sql`${t.currentRevision} > 0`),
    index("field_group_definitions_active_idx").on(t.active, t.key),
  ],
);

export const fieldDefinitionVersions = pgTable(
  "field_definition_versions",
  {
    groupKey: text("group_key").notNull(),
    revision: integer("revision").notNull(),
    canonicalHash: text("canonical_hash").notNull(),
    definition: jsonb("definition").$type<FieldGroup>().notNull(),
    origin: text("origin").$type<FieldsDefinitionOrigin>().notNull(),
    hostSessionId: text("host_session_id"),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [
    primaryKey({ columns: [t.groupKey, t.revision] }),
    check("field_definition_versions_revision_positive", sql`${t.revision} > 0`),
    index("field_definition_versions_hash_idx").on(t.canonicalHash),
  ],
);

export interface OptionsPageDefinitionSnapshot {
  readonly key: string;
  readonly title: string;
  readonly menuTitle?: string;
  readonly parentKey?: string;
  readonly readCapability: string;
  readonly writeCapability: string;
  readonly manageCapability: string;
  readonly position?: number;
  readonly icon?: string;
  readonly redirectToFirstChild?: boolean;
  readonly autoload?: boolean;
}

export const fieldOptionsDefinitions = pgTable(
  "field_options_definitions",
  {
    key: text("key").primaryKey(),
    origin: text("origin").$type<FieldsDefinitionOrigin>().notNull(),
    version: integer("version").notNull().default(1),
    active: boolean("active").notNull().default(true),
    currentRevision: integer("current_revision").notNull().default(1),
    canonicalHash: text("canonical_hash").notNull(),
    definition: jsonb("definition").$type<OptionsPageDefinitionSnapshot>().notNull(),
    shadowedDbVersion: integer("shadowed_db_version"),
    hostSessionId: text("host_session_id"),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [index("field_options_definitions_active_idx").on(t.active, t.key)],
);

export const fieldOptionsDefinitionVersions = pgTable(
  "field_options_definition_versions",
  {
    optionsKey: text("options_key").notNull(),
    revision: integer("revision").notNull(),
    canonicalHash: text("canonical_hash").notNull(),
    definition: jsonb("definition").$type<OptionsPageDefinitionSnapshot>().notNull(),
    origin: text("origin").$type<FieldsDefinitionOrigin>().notNull(),
    hostSessionId: text("host_session_id"),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [primaryKey({ columns: [t.optionsKey, t.revision] }), check("field_options_definition_versions_revision_positive", sql`${t.revision} > 0`)],
);

export interface FieldBlockDefinitionSnapshot {
  readonly key: string;
  readonly title: string;
  readonly description?: string;
  readonly category?: string;
  readonly icon?: string;
  readonly keywords?: readonly string[];
  readonly fieldGroupKeys: readonly string[];
  readonly mode?: "preview" | "edit" | "auto";
  readonly align?: readonly ("left" | "center" | "right" | "wide" | "full")[];
  readonly supports?: Readonly<{
    anchor?: boolean;
    className?: boolean;
    multiple?: boolean;
    reusable?: boolean;
    innerBlocks?: boolean;
    jsx?: boolean;
  }>;
  readonly parent?: readonly string[];
  readonly ancestor?: readonly string[];
  readonly template?: readonly FieldStorageValue[];
  readonly templateLock?: false | "insert" | "all" | "contentOnly";
}

export const fieldBlockDefinitions = pgTable(
  "field_block_definitions",
  {
    key: text("key").primaryKey(),
    origin: text("origin").$type<FieldsDefinitionOrigin>().notNull(),
    version: integer("version").notNull().default(1),
    active: boolean("active").notNull().default(true),
    currentRevision: integer("current_revision").notNull().default(1),
    canonicalHash: text("canonical_hash").notNull(),
    definition: jsonb("definition").$type<FieldBlockDefinitionSnapshot>().notNull(),
    shadowedDbVersion: integer("shadowed_db_version"),
    hostSessionId: text("host_session_id"),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [index("field_block_definitions_active_idx").on(t.active, t.key)],
);

export const fieldBlockDefinitionVersions = pgTable(
  "field_block_definition_versions",
  {
    blockKey: text("block_key").notNull(),
    revision: integer("revision").notNull(),
    canonicalHash: text("canonical_hash").notNull(),
    definition: jsonb("definition").$type<FieldBlockDefinitionSnapshot>().notNull(),
    origin: text("origin").$type<FieldsDefinitionOrigin>().notNull(),
    hostSessionId: text("host_session_id"),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [primaryKey({ columns: [t.blockKey, t.revision] }), check("field_block_definition_versions_revision_positive", sql`${t.revision} > 0`)],
);

export interface StoredBlockDocument {
  readonly version: 1;
  readonly roots: readonly FieldStorageValue[];
}

export const fieldBlockDocuments = pgTable(
  "field_block_documents",
  {
    entityType: text("entity_type").notNull(),
    entityId: text("entity_id").notNull(),
    version: integer("version").notNull().default(1),
    document: jsonb("document").$type<StoredBlockDocument>().notNull(),
    definitionVersions: jsonb("definition_versions").$type<Readonly<Record<string, number>>>().notNull(),
    parentRevisionId: text("parent_revision_id").notNull(),
    hostSessionId: text("host_session_id"),
    updatedBy: text("updated_by").notNull(),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [
    primaryKey({ columns: [t.entityType, t.entityId] }),
    check("field_block_documents_version_positive", sql`${t.version} > 0`),
  ],
);

export const fieldReusableBlocks = pgTable(
  "field_reusable_blocks",
  {
    id: text("id").primaryKey(),
    version: integer("version").notNull().default(1),
    document: jsonb("document").$type<StoredBlockDocument>().notNull(),
    definitionVersions: jsonb("definition_versions").$type<Readonly<Record<string, number>>>().notNull(),
    deletedAt: timestamp("deleted_at", { withTimezone: true, precision: 3 }),
    hostSessionId: text("host_session_id"),
    updatedBy: text("updated_by").notNull(),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [check("field_reusable_blocks_version_positive", sql`${t.version} > 0`)],
);

export const fieldRevisions = pgTable(
  "field_revisions",
  {
    id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
    entityType: text("entity_type").notNull(),
    entityId: text("entity_id").notNull(),
    parentRevisionId: text("parent_revision_id").notNull(),
    parentAutosaveId: text("parent_autosave_id"),
    kind: text("kind").$type<"revision" | "autosave">().notNull(),
    retentionClass: text("retention_class"),
    definitionVersions: jsonb("definition_versions").$type<Readonly<Record<string, number>>>().notNull(),
    values: jsonb("values").$type<FieldStorageValue>().notNull(),
    blockDocument: jsonb("block_document").$type<StoredBlockDocument>(),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
    createdBy: text("created_by").notNull(),
    sourceVersion: text("source_version").notNull(),
    hostSessionId: text("host_session_id"),
  },
  (t) => [
    index("field_revisions_entity_created_idx").on(t.entityType, t.entityId, t.createdAt, t.id),
    index("field_revisions_parent_idx").on(t.parentRevisionId, t.kind),
  ],
);

export const fieldsOperationReceipts = pgTable(
  "fields_operation_receipts",
  {
    operationId: text("operation_id").primaryKey(),
    kind: text("kind").notNull(),
    principalScope: text("principal_scope").notNull(),
    requestHash: text("request_hash").notNull(),
    result: jsonb("result").$type<FieldStorageValue>().notNull(),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [index("fields_operation_receipts_kind_idx").on(t.kind, t.createdAt)],
);

/** Final recursive typed EAV source of truth. */
export const fieldValueNodes = pgTable(
  "field_value_nodes",
  {
    nodeId: uuid("node_id").primaryKey().$defaultFn(() => crypto.randomUUID()),
    entityType: text("entity_type").notNull(),
    entityId: text("entity_id").notNull(),
    groupKey: text("group_key").notNull(),
    definitionRevision: integer("definition_revision").notNull(),
    fieldKey: text("field_key").notNull(),
    path: text("path").notNull(),
    parentNodeId: uuid("parent_node_id").references((): AnyPgColumn => fieldValueNodes.nodeId, { onDelete: "cascade" }),
    rowId: text("row_id"),
    layoutKey: text("layout_key"),
    nodeKind: text("node_kind").$type<FieldValueNodeRow["nodeKind"]>().notNull(),
    ordinal: integer("ordinal").notNull().default(0),
    isNull: boolean("is_null").notNull().default(false),
    valueText: text("value_text"),
    valueNumber: numeric("value_number"),
    valueBoolean: boolean("value_boolean"),
    valueDateTime: text("value_date_time"),
    valueRef: text("value_ref"),
    valueJson: text("value_json"),
    legacySourceIds: jsonb("legacy_source_ids").$type<string[]>(),
    legacySourceHash: text("legacy_source_hash"),
    hostSessionId: text("host_session_id"),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [
    uniqueIndex("field_value_nodes_entity_group_path_uq").on(t.entityType, t.entityId, t.groupKey, t.path),
    uniqueIndex("field_value_nodes_sibling_ordinal_uq").on(
      t.entityType,
      t.entityId,
      t.groupKey,
      sql`COALESCE(${t.parentNodeId}, '00000000-0000-0000-0000-000000000000'::uuid)`,
      t.ordinal,
    ),
    index("field_value_nodes_entity_idx").on(t.entityType, t.entityId, t.groupKey),
    index("field_value_nodes_definition_idx").on(t.groupKey, t.definitionRevision),
    index("field_value_nodes_text_idx").on(t.entityType, t.fieldKey, t.valueText),
    index("field_value_nodes_number_idx").on(t.entityType, t.fieldKey, t.valueNumber),
    index("field_value_nodes_ref_idx").on(t.entityType, t.fieldKey, t.valueRef),
    check("field_value_nodes_ordinal_nonnegative", sql`${t.ordinal} >= 0`),
    check("field_value_nodes_revision_positive", sql`${t.definitionRevision} > 0`),
    check("field_value_nodes_row_shape", sql`
      (${t.nodeKind} IN ('repeaterRow','flexibleRow') AND ${t.rowId} IS NOT NULL)
      OR (${t.nodeKind} NOT IN ('repeaterRow','flexibleRow') AND ${t.rowId} IS NULL)
    `),
    check("field_value_nodes_layout_shape", sql`
      (${t.nodeKind} = 'flexibleRow' AND ${t.layoutKey} IS NOT NULL)
      OR (${t.nodeKind} <> 'flexibleRow' AND ${t.layoutKey} IS NULL)
    `),
    check("field_value_nodes_lane_shape", sql`
      CASE
        WHEN ${t.nodeKind} <> 'leaf' THEN
          ${t.isNull} = false AND ${t.valueText} IS NULL AND ${t.valueNumber} IS NULL AND ${t.valueBoolean} IS NULL
          AND ${t.valueDateTime} IS NULL AND ${t.valueRef} IS NULL AND ${t.valueJson} IS NULL
        WHEN ${t.isNull} = true THEN
          ${t.valueText} IS NULL AND ${t.valueNumber} IS NULL AND ${t.valueBoolean} IS NULL
          AND ${t.valueDateTime} IS NULL AND ${t.valueRef} IS NULL AND ${t.valueJson} IS NULL
        ELSE
          (CASE WHEN ${t.valueText} IS NOT NULL THEN 1 ELSE 0 END
           + CASE WHEN ${t.valueNumber} IS NOT NULL THEN 1 ELSE 0 END
           + CASE WHEN ${t.valueBoolean} IS NOT NULL THEN 1 ELSE 0 END
           + CASE WHEN ${t.valueDateTime} IS NOT NULL THEN 1 ELSE 0 END
           + CASE WHEN ${t.valueRef} IS NOT NULL THEN 1 ELSE 0 END
           + CASE WHEN ${t.valueJson} IS NOT NULL THEN 1 ELSE 0 END) = 1
      END
    `),
  ],
);

export const fieldsImportStaging = pgTable(
  "fields_import_staging",
  {
    hostSessionId: text("host_session_id").notNull(),
    section: text("section").notNull(),
    ordinal: integer("ordinal").notNull(),
    canonicalHash: text("canonical_hash").notNull(),
    payload: jsonb("payload").$type<FieldStorageValue>().notNull(),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [primaryKey({ columns: [t.hostSessionId, t.section, t.ordinal] }), check("fields_import_staging_ordinal_nonnegative", sql`${t.ordinal} >= 0`)],
);

export const fieldsImportJournal = pgTable(
  "fields_import_journal",
  {
    importId: uuid("import_id").primaryKey().$defaultFn(() => crypto.randomUUID()),
    hostSessionId: text("host_session_id").notNull(),
    manifestHash: text("manifest_hash").notNull(),
    planHash: text("plan_hash").notNull(),
    state: text("state").notNull(),
    version: integer("version").notNull().default(0),
    nextSection: text("next_section"),
    nextOffset: integer("next_offset"),
    highWaterMark: text("high_water_mark"),
    payload: jsonb("payload").$type<FieldStorageValue>(),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [uniqueIndex("fields_import_journal_session_uq").on(t.hostSessionId), check("fields_import_journal_version_nonnegative", sql`${t.version} >= 0`)],
);

export const fieldsMigrationCheckpoints = pgTable(
  "fields_migration_checkpoints",
  {
    migrationId: text("migration_id").notNull(),
    adapter: text("adapter").$type<FieldsMigrationAdapter>().notNull(),
    phase: text("phase").$type<FieldsMigrationPhase>().notNull(),
    batchOrdinal: integer("batch_ordinal").notNull().default(0),
    version: integer("version").notNull().default(0),
    lastKey: text("last_key"),
    sourceCount: integer("source_count").notNull(),
    targetCount: integer("target_count"),
    sourceHash: text("source_hash").notNull(),
    targetHash: text("target_hash"),
    highWaterMark: text("high_water_mark"),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [primaryKey({ columns: [t.migrationId, t.adapter, t.phase, t.batchOrdinal] })],
);

export const fieldsMigrationWriteJournal = pgTable(
  "fields_migration_write_journal",
  {
    seq: bigserial("seq", { mode: "number" }).primaryKey(),
    migrationId: text("migration_id").notNull(),
    operation: text("operation").$type<"insert" | "update" | "delete">().notNull(),
    valueId: text("value_id").notNull(),
    beforeValue: jsonb("before_value").$type<FieldStorageValue>(),
    afterValue: jsonb("after_value").$type<FieldStorageValue>(),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).notNull().$defaultFn(() => new Date()),
  },
  (t) => [index("fields_migration_write_journal_migration_seq_idx").on(t.migrationId, t.seq)],
);

export const fieldValues = pgTable(
  "field_values",
  {
    id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
    entityType: text("entity_type").notNull(),
    entityId: text("entity_id").notNull(),
    groupId: text("group_id").notNull(),
    fieldKey: text("field_key").notNull(),
    ordinal: integer("ordinal").notNull().default(0),
    valueText: text("value_text"),
    valueNum: numeric("value_num"),
    valueBool: boolean("value_bool"),
    valueDate: timestamp("value_date", { withTimezone: true, precision: 3 }),
    refType: text("ref_type"),
    refId: text("ref_id"),
    refMeta: jsonb("ref_meta"),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 })
      .notNull()
      .$defaultFn(() => new Date()),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 })
      .notNull()
      .$defaultFn(() => new Date()),
  },
  (t) => [
    uniqueIndex("field_values_entity_group_field_ord_uq").on(
      t.entityType,
      t.entityId,
      t.groupId,
      t.fieldKey,
      t.ordinal
    ),
    index("field_values_entity_idx").on(t.entityType, t.entityId),
    index("field_values_facet_text_idx").on(
      t.entityType,
      t.fieldKey,
      t.valueText
    ),
    index("field_values_facet_num_idx").on(
      t.entityType,
      t.fieldKey,
      t.valueNum
    ),
    index("field_values_facet_date_idx").on(
      t.entityType,
      t.fieldKey,
      t.valueDate
    ),
    index("field_values_ref_idx").on(t.entityType, t.refType, t.refId),
    check(
      "field_values_one_lane",
      sql`
      (CASE WHEN ${t.valueText} IS NOT NULL THEN 1 ELSE 0 END
     + CASE WHEN ${t.valueNum} IS NOT NULL THEN 1 ELSE 0 END
     + CASE WHEN ${t.valueBool} IS NOT NULL THEN 1 ELSE 0 END
     + CASE WHEN ${t.valueDate} IS NOT NULL THEN 1 ELSE 0 END
     + CASE WHEN ${t.refId} IS NOT NULL THEN 1 ELSE 0 END) = 1`
    ),
  ]
);

export const fieldGroups = pgTable(
  "field_groups",
  {
    id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
    entityType: text("entity_type").notNull(),
    subType: text("sub_type"),
    key: text("key").notNull(),
    label: text("label").notNull(),
    fields: jsonb("fields").notNull(),
    position: integer("position").notNull().default(0),
    createdAt: timestamp("created_at", { withTimezone: true, precision: 3 })
      .notNull()
      .$defaultFn(() => new Date()),
    updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 })
      .notNull()
      .$defaultFn(() => new Date()),
  },
  (t) => [uniqueIndex("field_groups_entity_key_uq").on(t.entityType, t.key)]
);

export const fieldsSchema = {
  fieldValues,
  fieldGroups,
  fieldGroupDefinitions,
  fieldDefinitionVersions,
  fieldOptionsDefinitions,
  fieldOptionsDefinitionVersions,
  fieldBlockDefinitions,
  fieldBlockDefinitionVersions,
  fieldBlockDocuments,
  fieldReusableBlocks,
  fieldRevisions,
  fieldsOperationReceipts,
  fieldValueNodes,
  fieldsImportStaging,
  fieldsImportJournal,
  fieldsMigrationCheckpoints,
  fieldsMigrationWriteJournal,
};
export type FieldsSchema = typeof fieldsSchema;

/** Callback-minted capability; callers never construct this from a querier. */
export type FieldsTransaction = Transaction<FieldsSchema>;
