import { sql, type SQLWrapper } from 'drizzle-orm'
import { getTransactionIdentity, isTransactionCapabilityError, type TransactionIdentity } from '@platform-modules/db'
import { canonicalFieldsMigrationHash } from './migrations/recursive-eav.js'
import { resolveFieldDefinitionVersion } from './definitions.js'
import { decodeFieldValueNodes, encodeFieldValueNodes, type FieldValueMap } from './values.js'
import type { EntityRef } from './model.js'
import { canonicalFieldGroupHash, parseFieldGroupDefinition, type AnyFieldDefinition, type FieldGroup, type FieldPath, type FieldStorageValue, type FieldValueNodeRow, type FieldsTransaction } from './schema.js'
import { requireAuthorizedFieldsContext, type AuthorizedFieldsContext, type FieldsAction, type FieldsContext } from './types.js'
import type { CodeOptionsPage, OptionsPageDeactivateStrategy, OptionsPageImpact } from './options.js'
import type { CodeFieldBlock, FieldBlockChangeStrategy, FieldBlockDefinitionImpact } from './blocks.js'

export type FieldsLifecycleErrorCode =
  | 'validation'
  | 'access-denied'
  | 'not-found'
  | 'stale-version'
  | 'operation-conflict'
  | 'integrity'
  | 'capability-unavailable'
  | 'transaction-capability'
  | 'backup-invalid'
  | 'corpus-drift'

export class FieldsLifecycleError extends Error {
  override readonly name = 'FieldsLifecycleError'
  constructor(readonly code: FieldsLifecycleErrorCode, readonly detail: string, readonly field?: string) {
    super(`fields lifecycle ${code}: ${detail}${field ? ` (${field})` : ''}`)
  }
}

export interface FieldsDefinitionEvent {
  readonly id: string
  readonly version: 1
  readonly kind: 'codeRegistryReconciled'
  readonly operationId: string
  readonly principalId: string
  readonly fromVersion: string
  readonly toVersion: string
  readonly changedKeys: readonly string[]
  readonly strategiesHash: string
  readonly corpusHashes: readonly string[]
  readonly affectedCounts: Readonly<Record<string, number>>
}
export interface FieldsDefinitionEffects {
  audit(tx: FieldsTransaction, event: FieldsDefinitionEvent): Promise<void>
  enqueue(tx: FieldsTransaction, event: FieldsDefinitionEvent): Promise<void>
}

export interface FieldsBackupManifest {
  readonly id: string
  readonly scopeHash: string
  readonly corpusVersion: string
  readonly corpusHash: string
  readonly itemCounts: Readonly<Record<string, number>>
  readonly byteCount: number
  readonly immutable: true
}
export interface FieldsBackupSnapshot {
  readonly manifest: FieldsBackupManifest
  readonly payload: FieldStorageValue
}
export interface FieldsBackupStore {
  verify(
    tx: FieldsTransaction,
    backupId: string,
    scopeHash: string,
    corpusVersion: string,
    corpusHash: string,
    expectedCounts: Readonly<Record<string, number>>,
  ): Promise<FieldsBackupManifest>
  read(tx: FieldsTransaction, manifest: FieldsBackupManifest): Promise<FieldsBackupSnapshot>
}
export interface FieldsDestructiveEvent {
  readonly operationId: string
  readonly principalId: string
  readonly definitionKind: 'group' | 'optionsPage' | 'block'
  readonly definitionKey: string
  readonly strategy: FieldStorageValue
  readonly authorizationPolicyVersion: string
  readonly corpusVersion: string
  readonly corpusHash: string
  readonly previewToken: string
  readonly affectedCounts: Readonly<Record<string, number>>
  readonly backup: FieldsBackupManifest
}
export interface FieldsBackupRestoreEvent {
  readonly kind: 'backupRestored'
  readonly operationId: string
  readonly principalId: string
  readonly authorizationPolicyVersion: string
  readonly manifest: FieldsBackupManifest
  readonly destinationCorpusHash: string
  readonly restoredCounts: Readonly<Record<string, number>>
}
export type FieldsDestructiveEffectEvent = FieldsDestructiveEvent | FieldsBackupRestoreEvent
export interface FieldsDestructiveEffects {
  readonly audit: { record(tx: FieldsTransaction, event: FieldsDestructiveEffectEvent): Promise<void> }
  readonly outbox: { enqueue(tx: FieldsTransaction, event: FieldsDestructiveEffectEvent): Promise<void> }
}
export interface FieldsDestructiveDependencies {
  readonly backups: FieldsBackupStore
  readonly effects: FieldsDestructiveEffects
}
export interface FieldsBackupRestoreResult {
  readonly operationId: string
  readonly manifestId: string
  readonly destinationCorpusHash: string
  readonly restoredCounts: Readonly<Record<string, number>>
}

interface ReceiptRow extends Record<string, unknown> {
  kind: string
  principalScope: string
  requestHash: string
  result: unknown
}
export interface FieldsOperationReceipt<T extends FieldStorageValue = FieldStorageValue> {
  readonly kind: string
  readonly principalScope: string
  readonly requestHash: string
  readonly result: T
}

function freezeStorage(value: unknown, field = 'result', depth = 0): FieldStorageValue {
  if (depth > 20) throw new FieldsLifecycleError('integrity', 'stored payload exceeds maximum depth', field)
  if (value === null || typeof value === 'string' || typeof value === 'boolean') return value
  if (typeof value === 'number') {
    if (!Number.isFinite(value)) throw new FieldsLifecycleError('integrity', 'stored payload contains a non-finite number', field)
    return value
  }
  if (Array.isArray(value)) return Object.freeze(value.map((item, index) => freezeStorage(item, `${field}.${index}`, depth + 1)))
  if (typeof value === 'object') {
    const output: Record<string, FieldStorageValue> = {}
    for (const key of Object.keys(value).sort()) {
      if (key === '__proto__' || key === 'prototype' || key === 'constructor') throw new FieldsLifecycleError('integrity', 'stored payload contains a forbidden key', `${field}.${key}`)
      const child = (value as Record<string, unknown>)[key]
      if (child !== undefined) output[key] = freezeStorage(child, `${field}.${key}`, depth + 1)
    }
    return Object.freeze(output)
  }
  throw new FieldsLifecycleError('integrity', `stored payload contains unsupported ${typeof value}`, field)
}

export function assertActiveFieldsTransaction(tx: FieldsTransaction, expectedIdentity?: TransactionIdentity): TransactionIdentity {
  try {
    const identity = getTransactionIdentity(tx)
    if (expectedIdentity !== undefined && identity !== expectedIdentity) throw new FieldsLifecycleError('transaction-capability', 'transaction identity does not match the parent composite')
    return identity
  } catch (error) {
    if (error instanceof FieldsLifecycleError) throw error
    if (isTransactionCapabilityError(error)) throw new FieldsLifecycleError('transaction-capability', error.reason)
    throw error
  }
}

export function authorizedFieldsContext(context: FieldsContext): AuthorizedFieldsContext {
  try { return requireAuthorizedFieldsContext(context) }
  catch { throw new FieldsLifecycleError('access-denied', 'fields operation is unavailable') }
}
export async function assertFieldsAuthorized(context: FieldsContext, action: FieldsAction, ref: EntityRef): Promise<AuthorizedFieldsContext> {
  const authorized = authorizedFieldsContext(context)
  try { await authorized.authorization.assert(authorized.principal, action, ref) }
  catch { throw new FieldsLifecycleError('access-denied', 'fields operation is unavailable') }
  return authorized
}
export function assertPrincipalCapability(context: AuthorizedFieldsContext, capability: string): void {
  if (!context.principal.capabilities.has(capability)) throw new FieldsLifecycleError('access-denied', 'fields operation is unavailable')
}
export async function assertDefinitionMigration(context: FieldsContext, entityTypes: readonly string[]): Promise<{ context: AuthorizedFieldsContext; policyVersion: string }> {
  const authorized = authorizedFieldsContext(context)
  try {
    await authorized.authorization.assert(authorized.principal, 'manageDefinitions', { entityType: 'fields-definition', entityId: '*' })
    const result = await authorized.authorization.assertDefinitionMigration(authorized.principal, Object.freeze([...new Set(entityTypes)].sort()))
    if (!result || typeof result.policyVersion !== 'string' || result.policyVersion.length === 0) throw new Error('invalid policy version')
    return { context: authorized, policyVersion: result.policyVersion }
  } catch {
    throw new FieldsLifecycleError('access-denied', 'fields operation is unavailable')
  }
}

export function principalScope(context: AuthorizedFieldsContext): string {
  return `${context.principal.system === true ? 'system' : 'principal'}:${context.principal.id}`
}
export async function fieldsRequestHash(value: unknown): Promise<string> { return canonicalFieldsMigrationHash(value) }

export async function readFieldsReceipt<T extends FieldStorageValue = FieldStorageValue>(tx: FieldsTransaction, operationId: string): Promise<FieldsOperationReceipt<T> | null> {
  const rows = await tx.execute<ReceiptRow>(sql`SELECT kind, principal_scope AS "principalScope", request_hash AS "requestHash", result
    FROM fields_operation_receipts WHERE operation_id = ${operationId}`)
  const row = rows[0]
  if (!row) return null
  const result = typeof row.result === 'string' ? (() => { try { return JSON.parse(row.result) as unknown } catch { return row.result } })() : row.result
  return Object.freeze({ kind: row.kind, principalScope: row.principalScope, requestHash: row.requestHash, result: freezeStorage(result) as T })
}
export async function writeFieldsReceipt(tx: FieldsTransaction, input: { operationId: string; kind: string; context: AuthorizedFieldsContext; requestHash: string; result: FieldStorageValue }): Promise<void> {
  await tx.execute(sql`INSERT INTO fields_operation_receipts (operation_id, kind, principal_scope, request_hash, result, created_at)
    VALUES (${input.operationId}, ${input.kind}, ${principalScope(input.context)}, ${input.requestHash}, ${JSON.stringify(input.result)}, ${new Date().toISOString()})`)
}
export function assertReceiptReplay(receipt: FieldsOperationReceipt, input: { kind: string; context: AuthorizedFieldsContext; requestHash: string }): void {
  if (receipt.kind !== input.kind || receipt.principalScope !== principalScope(input.context) || receipt.requestHash !== input.requestHash) {
    throw new FieldsLifecycleError('operation-conflict', 'operation id was already used with different input or scope')
  }
}

export async function verifyFieldsBackup(
  tx: FieldsTransaction,
  input: { backupId: string; expectedScopeHash: string; corpusVersion: string; corpusHash: string; counts: Readonly<Record<string, number>> },
  backups: FieldsBackupStore,
): Promise<FieldsBackupManifest> {
  let manifest: FieldsBackupManifest
  try {
    manifest = await backups.verify(tx, input.backupId, input.expectedScopeHash, input.corpusVersion, input.corpusHash, input.counts)
  } catch {
    throw new FieldsLifecycleError('backup-invalid', 'backup manifest does not match the destructive scope')
  }
  if (
    !manifest
    || manifest.immutable !== true
    || manifest.id !== input.backupId
    || manifest.scopeHash !== input.expectedScopeHash
    || manifest.corpusVersion !== input.corpusVersion
    || manifest.corpusHash !== input.corpusHash
    || !Number.isSafeInteger(manifest.byteCount)
    || manifest.byteCount < 0
  ) throw new FieldsLifecycleError('backup-invalid', 'backup manifest does not match the destructive scope')
  const expected = JSON.stringify(Object.entries(input.counts).sort())
  const actual = JSON.stringify(Object.entries(manifest.itemCounts).sort())
  if (expected !== actual) throw new FieldsLifecycleError('backup-invalid', 'backup counts do not cover the complete corpus')
  return Object.freeze({ ...manifest, itemCounts: Object.freeze({ ...manifest.itemCounts }) })
}

export interface FieldsLifecycleReader { execute(query: SQLWrapper): Promise<unknown> }
export interface CodeFinalFieldGroup extends FieldGroup { readonly origin: 'code' }
export interface ResolvedFinalFieldGroup extends FieldGroup {
  readonly origin: 'code' | 'db'
  readonly version: number
  readonly revision: number
  readonly shadowedDbVersion?: number
}
export interface FieldPathMapping { readonly from: FieldPath; readonly to: FieldPath | null }
export type FieldGroupChangeStrategy =
  | { readonly kind: 'reject' }
  | { readonly kind: 'deactivate' }
  | { readonly kind: 'retainValues' }
  | { readonly kind: 'migrateValues'; readonly mappings: readonly FieldPathMapping[] }
  | { readonly kind: 'purgeValues'; readonly backupId: string }
export type FieldGroupPatch = Partial<Omit<FieldGroup, 'key'>>
export interface FieldDefinitionImpact {
  readonly token: string
  readonly groupKey: string
  readonly expectedVersion: number
  readonly authorizationPolicyVersion: string
  readonly valueCorpusVersion: string
  readonly valueCorpusHash: string
  readonly affectedCounts: Readonly<Record<string, number>>
  readonly incompatibilities: readonly string[]
}

interface FinalGroupRow extends Record<string, unknown> {
  key: string
  origin: 'code' | 'db' | 'import'
  version: number | string
  active: boolean | number
  currentRevision: number | string
  canonicalHash: string
  definition: unknown
  shadowedDbVersion: number | string | null
}
interface GroupNodeCorpusRow extends Record<string, unknown> {
  nodeId: string
  entityType: string
  entityId: string
  groupKey: string
  definitionRevision: number | string
  fieldKey: string
  path: string
  parentNodeId: string | null
  rowId: string | null
  layoutKey: string | null
  nodeKind: FieldValueNodeRow['nodeKind']
  ordinal: number | string
  isNull: boolean | number
  valueText: string | null
  valueNumber: string | number | null
  valueBoolean: boolean | number | null
  valueDateTime: string | null
  valueRef: string | null
  valueJson: string | null
}
interface GroupRevisionCorpusRow extends Record<string, unknown> {
  id: string
  entityType: string
  entityId: string
  definitionVersions: unknown
  values: unknown
  blockDocument: unknown | null
}
interface GroupDocumentCorpusRow extends Record<string, unknown> {
  section: 'current' | 'reusable'
  identity: string
  entityType: string
  entityId: string | null
  version: number | string
  document: unknown
  definitionVersions: unknown
}
interface GroupCorpus {
  readonly version: string
  readonly hash: string
  readonly nodes: readonly GroupNodeCorpusRow[]
  readonly revisions: readonly GroupRevisionCorpusRow[]
  readonly documents: readonly GroupDocumentCorpusRow[]
  readonly entityTypes: readonly string[]
  readonly counts: Readonly<Record<string, number>>
}

function lifecycleRows<Row extends Record<string, unknown>>(value: unknown): readonly Row[] {
  if (Array.isArray(value)) return value as Row[]
  if (typeof value === 'object' && value !== null && Array.isArray((value as { rows?: unknown }).rows)) return (value as { rows: Row[] }).rows
  throw new FieldsLifecycleError('integrity', 'database adapter returned an invalid row result')
}
async function lifecycleExecute<Row extends Record<string, unknown>>(db: FieldsLifecycleReader, query: SQLWrapper): Promise<readonly Row[]> {
  return lifecycleRows<Row>(await db.execute(query))
}
function lifecycleJson(value: unknown, field: string): unknown {
  if (typeof value !== 'string') return value
  try { return JSON.parse(value) as unknown } catch { throw new FieldsLifecycleError('integrity', `${field} contains invalid JSON`) }
}
function lifecycleBool(value: unknown): boolean { return value === true || value === 1 }
function lifecycleRecord(value: unknown, field: string): Record<string, unknown> {
  const parsed = lifecycleJson(value, field)
  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) throw new FieldsLifecycleError('integrity', `${field} is malformed`)
  return parsed as Record<string, unknown>
}
async function normalizeFinalGroup(value: unknown): Promise<FieldGroup> { return parseFieldGroupDefinition(value) }
function resolvedFinalGroup(row: FinalGroupRow): ResolvedFinalFieldGroup {
  const definition = parseFieldGroupDefinition(lifecycleJson(row.definition, 'field group definition'))
  if (definition.key !== row.key) throw new FieldsLifecycleError('integrity', 'field group definition key mismatch')
  return Object.freeze({
    ...definition,
    active: lifecycleBool(row.active),
    origin: row.origin === 'code' ? 'code' : 'db',
    version: Number(row.version),
    revision: Number(row.currentRevision),
    ...(row.shadowedDbVersion === null ? {} : { shadowedDbVersion: Number(row.shadowedDbVersion) }),
  })
}
async function finalGroupRows(db: FieldsLifecycleReader): Promise<readonly FinalGroupRow[]> {
  return lifecycleExecute<FinalGroupRow>(db, sql`SELECT key,origin,version,active,current_revision AS "currentRevision",canonical_hash AS "canonicalHash",definition,shadowed_db_version AS "shadowedDbVersion" FROM field_group_definitions ORDER BY key`)
}
async function finalGroupRow(db: FieldsLifecycleReader, key: string): Promise<FinalGroupRow | null> {
  const rows = await lifecycleExecute<FinalGroupRow>(db, sql`SELECT key,origin,version,active,current_revision AS "currentRevision",canonical_hash AS "canonicalHash",definition,shadowed_db_version AS "shadowedDbVersion" FROM field_group_definitions WHERE key=${key} LIMIT 1`)
  return rows[0] ?? null
}
function normalizedGroupKey(value: unknown): string {
  if (typeof value !== 'string' || !/^[a-z][a-z0-9_]*$/.test(value)) throw new FieldsLifecycleError('validation', 'group key must be normalized', 'key')
  return value
}
function codeGroupDefinition(value: FieldGroup | CodeFinalFieldGroup | ResolvedFinalFieldGroup): FieldGroup {
  const { origin: _origin, version: _version, revision: _revision, shadowedDbVersion: _shadowedDbVersion, ...definition } = value as FieldGroup & {
    readonly origin?: unknown
    readonly version?: unknown
    readonly revision?: unknown
    readonly shadowedDbVersion?: unknown
  }
  return parseFieldGroupDefinition(definition)
}
export function defineFinalFieldGroup(definition: FieldGroup): CodeFinalFieldGroup {
  return Object.freeze({ ...codeGroupDefinition(definition), origin: 'code' })
}
export async function resolveFinalFieldGroups(db: FieldsLifecycleReader, input: { codeGroups?: readonly CodeFinalFieldGroup[] } = {}): Promise<ResolvedFinalFieldGroup[]> {
  const rows = await finalGroupRows(db), output = new Map<string, ResolvedFinalFieldGroup>()
  for (const row of rows) output.set(row.key, resolvedFinalGroup(row))
  const seen = new Set<string>()
  for (const raw of input.codeGroups ?? []) {
    const definition = parseFieldGroupDefinition(raw)
    if (seen.has(definition.key)) throw new FieldsLifecycleError('validation', 'duplicate code field-group key', definition.key)
    seen.add(definition.key)
    const previous = output.get(definition.key)
    output.set(definition.key, Object.freeze({
      ...definition,
      origin: 'code',
      version: previous?.version ?? 1,
      revision: previous?.revision ?? 1,
      active: true,
      ...(previous?.origin === 'db' ? { shadowedDbVersion: previous.version } : previous?.shadowedDbVersion === undefined ? {} : { shadowedDbVersion: previous.shadowedDbVersion }),
    }))
  }
  return Object.freeze([...output.values()].sort((left, right) => (left.order ?? 0) - (right.order ?? 0) || left.key.localeCompare(right.key))) as ResolvedFinalFieldGroup[]
}

function objectMentionsGroup(value: unknown, groupKey: string): boolean {
  const parsed = lifecycleJson(value, 'document')
  const visit = (item: unknown): boolean => {
    if (Array.isArray(item)) return item.some(visit)
    if (typeof item !== 'object' || item === null) return false
    const record = item as Record<string, unknown>
    if (record.groupKey === groupKey) return true
    return Object.values(record).some(visit)
  }
  return visit(parsed)
}
async function groupCorpus(db: FieldsLifecycleReader, groupKey: string): Promise<GroupCorpus> {
  const nodes = await lifecycleExecute<GroupNodeCorpusRow>(db, sql`SELECT node_id AS "nodeId",entity_type AS "entityType",entity_id AS "entityId",group_key AS "groupKey",definition_revision AS "definitionRevision",field_key AS "fieldKey",path,parent_node_id AS "parentNodeId",row_id AS "rowId",layout_key AS "layoutKey",node_kind AS "nodeKind",ordinal,is_null AS "isNull",value_text AS "valueText",value_number AS "valueNumber",value_boolean AS "valueBoolean",value_date_time AS "valueDateTime",value_ref AS "valueRef",value_json AS "valueJson" FROM field_value_nodes WHERE group_key=${groupKey} ORDER BY entity_type,entity_id,path`)
  const revisionRows = await lifecycleExecute<GroupRevisionCorpusRow>(db, sql`SELECT id,entity_type AS "entityType",entity_id AS "entityId",definition_versions AS "definitionVersions",values,block_document AS "blockDocument" FROM field_revisions ORDER BY entity_type,entity_id,id`)
  const revisions = revisionRows.filter((row) => {
    const versions = lifecycleRecord(row.definitionVersions, 'revision definitionVersions')
    return Object.prototype.hasOwnProperty.call(versions, groupKey) || objectMentionsGroup(row.blockDocument, groupKey)
  })
  const currentDocuments = await lifecycleExecute<Record<string, unknown>>(db, sql`SELECT entity_type AS "entityType",entity_id AS "entityId",version,document,definition_versions AS "definitionVersions" FROM field_block_documents ORDER BY entity_type,entity_id`)
  const reusableDocuments = await lifecycleExecute<Record<string, unknown>>(db, sql`SELECT id,version,document,definition_versions AS "definitionVersions" FROM field_reusable_blocks ORDER BY id`)
  const documents: GroupDocumentCorpusRow[] = []
  for (const row of currentDocuments) if (objectMentionsGroup(row.document, groupKey) || Object.prototype.hasOwnProperty.call(lifecycleRecord(row.definitionVersions, 'block definitionVersions'), `group:${groupKey}`)) {
    documents.push({ section: 'current', identity: `${String(row.entityType)}:${String(row.entityId)}`, entityType: String(row.entityType), entityId: String(row.entityId), version: row.version as string | number, document: lifecycleJson(row.document, 'block document'), definitionVersions: lifecycleJson(row.definitionVersions, 'block definitionVersions') })
  }
  for (const row of reusableDocuments) if (objectMentionsGroup(row.document, groupKey) || Object.prototype.hasOwnProperty.call(lifecycleRecord(row.definitionVersions, 'reusable definitionVersions'), `group:${groupKey}`)) {
    documents.push({ section: 'reusable', identity: String(row.id), entityType: 'reusableBlock', entityId: null, version: row.version as string | number, document: lifecycleJson(row.document, 'reusable document'), definitionVersions: lifecycleJson(row.definitionVersions, 'reusable definitionVersions') })
  }
  documents.sort((a,b)=>a.section.localeCompare(b.section)||a.identity.localeCompare(b.identity))
  const entityTypes = new Set<string>(nodes.map((row)=>row.entityType))
  for (const row of revisions) entityTypes.add(row.entityType)
  for (const row of documents) entityTypes.add(row.entityType)
  const entities = new Set(nodes.map((row)=>`${row.entityType}\u0000${row.entityId}`))
  const counts = Object.freeze({ nodes: nodes.length, entities: entities.size, revisions: revisions.length, documents: documents.length })
  const identity = {
    nodes: nodes.map((row)=>({nodeId:row.nodeId,entityType:row.entityType,entityId:row.entityId,path:row.path,revision:Number(row.definitionRevision)})),
    revisions: revisions.map((row)=>row.id),
    documents: documents.map((row)=>({section:row.section,identity:row.identity,version:row.version})),
  }
  const version = await fieldsRequestHash(identity)
  const hash = await fieldsRequestHash({nodes,revisions,documents})
  return Object.freeze({ version, hash, nodes:Object.freeze(nodes), revisions:Object.freeze(revisions), documents:Object.freeze(documents), entityTypes:Object.freeze([...entityTypes].sort()), counts })
}
function groupPaths(fields: readonly AnyFieldDefinition[], prefix: readonly (string|number)[] = []): ReadonlyMap<string, AnyFieldDefinition> {
  const output = new Map<string, AnyFieldDefinition>()
  const visit = (definitions: readonly AnyFieldDefinition[], parent: readonly (string|number)[]) => {
    for (const definition of definitions) {
      const path = [...parent, definition.key]
      output.set(JSON.stringify(path), definition)
      if (definition.type === 'group' || definition.type === 'repeater') visit((definition.settings as {fields:readonly AnyFieldDefinition[]}).fields,path)
      if (definition.type === 'flexible') for (const layout of (definition.settings as {layouts:readonly {key:string;fields:readonly AnyFieldDefinition[]}[]}).layouts) visit(layout.fields,[...path,layout.key])
    }
  }
  visit(fields,prefix)
  return output
}
async function groupIncompatibilities(current: FieldGroup, next: FieldGroup, corpus: GroupCorpus): Promise<readonly string[]> {
  if (corpus.nodes.length===0 && corpus.documents.length===0 && corpus.revisions.length===0) return Object.freeze([])
  const before=groupPaths(current.fields), after=groupPaths(next.fields), output:string[]=[]
  if (current.active && !next.active) output.push('deactivate-with-data')
  for(const [path,definition] of before){
    const replacement=after.get(path)
    if(!replacement) output.push(`removed:${path}`)
    else if(await fieldsRequestHash(definition)!==await fieldsRequestHash(replacement)) output.push(`changed:${path}`)
  }
  return Object.freeze([...new Set(output)].sort())
}
function groupTokenEncode(payload: Record<string,FieldStorageValue>):Promise<string>{
  const bytes=new TextEncoder().encode(JSON.stringify(payload)),hex=Array.from(bytes,b=>b.toString(16).padStart(2,'0')).join('')
  return fieldsRequestHash(payload).then(hash=>`${hex}.${hash}`)
}
async function groupTokenDecode(token:string):Promise<Record<string,unknown>>{
  const match=/^([a-f0-9]+)\.([a-f0-9]{64})$/i.exec(token)
  if(!match||match[1]!.length%2||match[1]!.length>128000)throw new FieldsLifecycleError('validation','field-group impact token is invalid')
  let value:unknown
  try{value=JSON.parse(new TextDecoder().decode(new Uint8Array(match[1]!.match(/../g)!.map(pair=>Number.parseInt(pair,16))))) as unknown}catch{throw new FieldsLifecycleError('validation','field-group impact token is invalid')}
  if(typeof value!=='object'||value===null||Array.isArray(value)||await fieldsRequestHash(value)!==match[2]!.toLowerCase())throw new FieldsLifecycleError('validation','field-group impact token integrity check failed')
  return value as Record<string,unknown>
}
function normalizeGroupStrategy(strategy:FieldGroupChangeStrategy):FieldGroupChangeStrategy{
  if(strategy.kind==='reject'||strategy.kind==='deactivate'||strategy.kind==='retainValues')return Object.freeze({kind:strategy.kind})
  if(strategy.kind==='purgeValues'){
    if(!strategy.backupId||strategy.backupId.length>512)throw new FieldsLifecycleError('validation','backupId is required','strategy.backupId')
    return Object.freeze({kind:'purgeValues',backupId:strategy.backupId})
  }
  if(strategy.kind==='migrateValues'){
    if(!Array.isArray(strategy.mappings)||strategy.mappings.length>500)throw new FieldsLifecycleError('validation','field mappings must be bounded','strategy.mappings')
    const seen=new Set<string>(),mappings=strategy.mappings.map((mapping,index)=>{
      if(!Array.isArray(mapping.from)||mapping.from.length===0||mapping.from.length>64)throw new FieldsLifecycleError('validation','mapping source path is invalid',`strategy.mappings.${index}.from`)
      if(mapping.to!==null&&(!Array.isArray(mapping.to)||mapping.to.length===0||mapping.to.length>64))throw new FieldsLifecycleError('validation','mapping destination path is invalid',`strategy.mappings.${index}.to`)
      const identity=JSON.stringify(mapping.from)
      if(seen.has(identity))throw new FieldsLifecycleError('validation','mapping source path is duplicated',`strategy.mappings.${index}.from`)
      seen.add(identity)
      return Object.freeze({from:Object.freeze([...mapping.from]),to:mapping.to===null?null:Object.freeze([...mapping.to])})
    })
    return Object.freeze({kind:'migrateValues',mappings:Object.freeze(mappings)})
  }
  throw new FieldsLifecycleError('validation','unknown field-group change strategy')
}

function groupDefinitionRef(key:string):EntityRef{return Object.freeze({entityType:'fields-group-definition',entityId:key})}
export async function createFieldGroup(
  tx:FieldsTransaction,
  definition:FieldGroup,
  context:FieldsContext,
  input:{codeGroups?:readonly CodeFinalFieldGroup[];operationId:string},
):Promise<ResolvedFinalFieldGroup>{
  assertActiveFieldsTransaction(tx)
  const normalized=await normalizeFinalGroup(definition),key=normalizedGroupKey(normalized.key)
  const authorized=await assertFieldsAuthorized(context,'manageDefinitions',groupDefinitionRef(key))
  if((input.codeGroups??[]).some(group=>group.key===key))throw new FieldsLifecycleError('validation','effective field-group key is owned by code',key)
  const canonicalHash=await canonicalFieldGroupHash(normalized)
  const requestHash=await fieldsRequestHash({kind:'create-field-group',canonicalHash,codeKeys:(input.codeGroups??[]).map(group=>group.key).sort()})
  const replay=await readFieldsReceipt(tx,input.operationId)
  if(replay){assertReceiptReplay(replay,{kind:'fields:create-field-group',context:authorized,requestHash});const row=await finalGroupRow(tx,key);if(!row)throw new FieldsLifecycleError('integrity','committed group receipt has no definition');return resolvedFinalGroup(row)}
  if(await finalGroupRow(tx,key))throw new FieldsLifecycleError('validation','field-group key already exists',key)
  const now=new Date().toISOString()
  await tx.execute(sql`INSERT INTO field_group_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES (${key},'db',1,${normalized.active},1,${canonicalHash},${JSON.stringify(normalized)},${now},${now})`)
  await tx.execute(sql`INSERT INTO field_definition_versions (group_key,revision,canonical_hash,definition,origin,created_at) VALUES (${key},1,${canonicalHash},${JSON.stringify(normalized)},'db',${now})`)
  const result:ResolvedFinalFieldGroup=Object.freeze({...normalized,origin:'db',version:1,revision:1})
  await writeFieldsReceipt(tx,{operationId:input.operationId,kind:'fields:create-field-group',context:authorized,requestHash,result:Object.freeze({key,version:1,revision:1})})
  return result
}

export async function previewFieldGroupChange(
  db:FieldsLifecycleReader,
  key:string,
  expectedVersion:number,
  patch:FieldGroupPatch,
  strategyInput:FieldGroupChangeStrategy,
  context:FieldsContext,
):Promise<FieldDefinitionImpact>{
  const groupKey=normalizedGroupKey(key)
  await assertFieldsAuthorized(context,'manageDefinitions',groupDefinitionRef(groupKey))
  const row=await finalGroupRow(db,groupKey)
  if(!row)throw new FieldsLifecycleError('not-found','field-group definition is unavailable')
  const current=resolvedFinalGroup(row)
  if(current.origin==='code')throw new FieldsLifecycleError('validation','code-owned field groups change only through registry reconciliation')
  if(current.version!==expectedVersion)throw new FieldsLifecycleError('stale-version','field-group version changed')
  const strategy=normalizeGroupStrategy(strategyInput)
  const next=parseFieldGroupDefinition({...codeGroupDefinition(current),...patch,key:groupKey,active:strategy.kind==='deactivate'?false:(patch.active??current.active)})
  const corpus=await groupCorpus(db,groupKey)
  const migration=await assertDefinitionMigration(context,corpus.entityTypes.length?corpus.entityTypes:['fields-definition'])
  const incompatibilities=await groupIncompatibilities(current,next,corpus)
  const payload:Record<string,FieldStorageValue>=Object.freeze({
    kind:'field-group-change',groupKey,expectedVersion,
    authorizationPolicyVersion:migration.policyVersion,
    valueCorpusVersion:corpus.version,valueCorpusHash:corpus.hash,
    affectedCounts:corpus.counts,
    nextDefinition:freezeStorage(next,'nextDefinition'),
    strategy:freezeStorage(strategy,'strategy'),
    incompatibilities:Object.freeze(incompatibilities),
    entityTypes:Object.freeze(corpus.entityTypes),
  })
  return Object.freeze({
    token:await groupTokenEncode(payload),groupKey,expectedVersion,
    authorizationPolicyVersion:migration.policyVersion,valueCorpusVersion:corpus.version,valueCorpusHash:corpus.hash,
    affectedCounts:corpus.counts,incompatibilities,
  })
}

function corpusNode(row:GroupNodeCorpusRow):FieldValueNodeRow{return Object.freeze({
  nodeId:row.nodeId,entityType:row.entityType,entityId:row.entityId,groupKey:row.groupKey,definitionRevision:Number(row.definitionRevision),fieldKey:row.fieldKey,path:row.path,parentNodeId:row.parentNodeId,rowId:row.rowId,layoutKey:row.layoutKey,nodeKind:row.nodeKind,ordinal:Number(row.ordinal),isNull:row.isNull===true||row.isNull===1,valueText:row.valueText,valueNumber:row.valueNumber===null?null:Number(row.valueNumber),valueBoolean:row.valueBoolean===null?null:row.valueBoolean===true||row.valueBoolean===1,valueDateTime:row.valueDateTime,valueRef:row.valueRef,valueJson:row.valueJson,
})}
function pathKey(path:FieldPath):string{return JSON.stringify(path)}
function deepClone(value:unknown):unknown{if(value===null||typeof value!=='object')return value;if(Array.isArray(value))return value.map(deepClone);const out:Record<string,unknown>={};for(const [key,child] of Object.entries(value as Record<string,unknown>))out[key]=deepClone(child);return out}
function pathGet(root:unknown,path:FieldPath):{present:boolean;value:unknown}{let current:unknown=root;for(const segment of path){if(typeof segment==='number'){if(!Array.isArray(current)||segment<0||segment>=current.length)return{present:false,value:undefined};current=current[segment]}else{if(typeof current!=='object'||current===null||Array.isArray(current)||!Object.prototype.hasOwnProperty.call(current,segment))return{present:false,value:undefined};current=(current as Record<string,unknown>)[segment]}}return{present:true,value:current}}
function pathDelete(root:unknown,path:FieldPath):void{if(path.length===0)return;let current:unknown=root;for(let i=0;i<path.length-1;i++){const segment=path[i]!;if(typeof segment==='number'){if(!Array.isArray(current)||segment<0||segment>=current.length)return;current=current[segment]}else{if(typeof current!=='object'||current===null||Array.isArray(current))return;current=(current as Record<string,unknown>)[segment]}}const last=path[path.length-1]!;if(typeof last==='number'){if(Array.isArray(current)&&last>=0&&last<current.length)current.splice(last,1)}else if(typeof current==='object'&&current!==null&&!Array.isArray(current))delete (current as Record<string,unknown>)[last]}
function pathSet(root:unknown,path:FieldPath,value:unknown):void{if(path.length===0)throw new FieldsLifecycleError('validation','mapping destination path must not be empty');let current:unknown=root;for(let i=0;i<path.length-1;i++){const segment=path[i]!,next=path[i+1]!;if(typeof segment==='number'){if(!Array.isArray(current)||segment<0)throw new FieldsLifecycleError('validation','mapping destination crosses a non-array value');while(current.length<=segment)current.push(typeof next==='number'?[]:{});if(current[segment]===null||typeof current[segment]!=='object')current[segment]=typeof next==='number'?[]:{};current=current[segment]}else{if(typeof current!=='object'||current===null||Array.isArray(current))throw new FieldsLifecycleError('validation','mapping destination crosses a non-object value');const record=current as Record<string,unknown>;if(record[segment]===undefined)record[segment]=typeof next==='number'?[]:{};if(record[segment]===null||typeof record[segment]!=='object')record[segment]=typeof next==='number'?[]:{};current=record[segment]}}const last=path[path.length-1]!;if(typeof last==='number'){if(!Array.isArray(current)||last<0)throw new FieldsLifecycleError('validation','mapping destination is not an array');while(current.length<last)current.push(null);current[last]=deepClone(value)}else{if(typeof current!=='object'||current===null||Array.isArray(current))throw new FieldsLifecycleError('validation','mapping destination is not an object');(current as Record<string,unknown>)[last]=deepClone(value)}}
function applyMappings(values:FieldValueMap,mappings:readonly FieldPathMapping[]):FieldValueMap{const output=deepClone(values) as Record<string,unknown>;for(const mapping of mappings){const found=pathGet(values,mapping.from);if(!found.present)continue;pathDelete(output,mapping.from);if(mapping.to!==null)pathSet(output,mapping.to,found.value)}return Object.freeze(output) as FieldValueMap}
function ensureMappings(current:FieldGroup,next:FieldGroup,strategy:Extract<FieldGroupChangeStrategy,{kind:'migrateValues'}>,incompatibilities:readonly string[]):void{
  const before=groupPaths(current.fields),after=groupPaths(next.fields),mappingBySource=new Map(strategy.mappings.map(mapping=>[pathKey(mapping.from),mapping]))
  for(const issue of incompatibilities){const raw=issue.slice(issue.indexOf(':')+1),mapping=mappingBySource.get(raw);if(!mapping)throw new FieldsLifecycleError('validation',`migration does not cover ${issue}`);if(mapping.to!==null&&!after.has(pathKey(mapping.to)))throw new FieldsLifecycleError('validation',`migration target for ${issue} does not exist`)}
  for(const mapping of strategy.mappings){if(!before.has(pathKey(mapping.from)))throw new FieldsLifecycleError('validation','migration source path does not exist',pathKey(mapping.from));if(mapping.to!==null&&!after.has(pathKey(mapping.to)))throw new FieldsLifecycleError('validation','migration target path does not exist',pathKey(mapping.to))}
}
async function writeMigratedNodes(tx:FieldsTransaction,groupKey:string,next:FieldGroup,nextRevision:number,corpus:GroupCorpus,mappings:readonly FieldPathMapping[],context:FieldsContext):Promise<void>{
  const buckets=new Map<string,GroupNodeCorpusRow[]>();for(const row of corpus.nodes){const id=`${row.entityType}\u0000${row.entityId}`;const list=buckets.get(id)??[];list.push(row);buckets.set(id,list)}
  for(const [identity,rows] of buckets){const revisions=[...new Set(rows.map(row=>Number(row.definitionRevision)))];if(revisions.length!==1)throw new FieldsLifecycleError('integrity','current group value tree mixes immutable definition revisions');const oldRevision=revisions[0]!,oldDefinition=(await resolveFieldDefinitionVersion(tx,groupKey,oldRevision)).definition;const decoded=decodeFieldValueNodes(oldDefinition,rows.map(corpusNode),{definitionRevision:oldRevision,...(context.cloneResolver?{cloneResolver:{resolve:(definition)=>context.cloneResolver!.resolve(definition)}}:{})});const migrated=applyMappings(decoded,mappings);const [entityType,entityId]=identity.split('\u0000') as [string,string];const ids=new Map<string,string>(),nodeIdForPath=(path:string)=>{let id=ids.get(path);if(!id){id=crypto.randomUUID();ids.set(path,id)}return id};const encoded=encodeFieldValueNodes(next,migrated,{entityType,entityId,definitionRevision:nextRevision,nodeIdForPath,...(context.cloneResolver?{cloneResolver:{resolve:(definition)=>context.cloneResolver!.resolve(definition)}}:{})});await tx.execute(sql`DELETE FROM field_value_nodes WHERE entity_type=${entityType} AND entity_id=${entityId} AND group_key=${groupKey}`);for(const row of encoded)await tx.execute(sql`INSERT INTO field_value_nodes (node_id,entity_type,entity_id,group_key,definition_revision,field_key,path,parent_node_id,row_id,layout_key,node_kind,ordinal,is_null,value_text,value_number,value_boolean,value_date_time,value_ref,value_json,created_at,updated_at) VALUES (${row.nodeId},${row.entityType},${row.entityId},${row.groupKey},${row.definitionRevision},${row.fieldKey},${row.path},${row.parentNodeId},${row.rowId},${row.layoutKey},${row.nodeKind},${row.ordinal},${row.isNull},${row.valueText},${row.valueNumber},${row.valueBoolean},${row.valueDateTime},${row.valueRef},${row.valueJson},${new Date().toISOString()},${new Date().toISOString()})`)}
}
function migrateGroupInDocument(value:unknown,groupKey:string,nextRevision:number,mappings:readonly FieldPathMapping[]):unknown{const walk=(item:unknown):unknown=>{if(Array.isArray(item))return item.map(walk);if(typeof item!=='object'||item===null)return item;const record=item as Record<string,unknown>,out:Record<string,unknown>={};for(const [key,value] of Object.entries(record))out[key]=walk(value);if(record.groupKey===groupKey&&typeof record.values==='object'&&record.values!==null&&!Array.isArray(record.values)){out.definitionRevision=nextRevision;out.values=applyMappings(record.values as FieldValueMap,mappings)}return out};return walk(lifecycleJson(value,'block document'))}
function definitionVersionsWithGroup(value:unknown,groupKey:string,revision:number):Readonly<Record<string,unknown>>{const source=lifecycleRecord(value,'definitionVersions'),out={...source};if(Object.prototype.hasOwnProperty.call(out,`group:${groupKey}`))out[`group:${groupKey}`]=revision;if(Object.prototype.hasOwnProperty.call(out,groupKey))out[groupKey]=revision;return Object.freeze(out)}
async function migrateCurrentDocuments(tx:FieldsTransaction,groupKey:string,nextRevision:number,corpus:GroupCorpus,mappings:readonly FieldPathMapping[]):Promise<void>{for(const entry of corpus.documents){const document=migrateGroupInDocument(entry.document,groupKey,nextRevision,mappings),versions=definitionVersionsWithGroup(entry.definitionVersions,groupKey,nextRevision);if(entry.section==='current')await tx.execute(sql`UPDATE field_block_documents SET document=${JSON.stringify(document)},definition_versions=${JSON.stringify(versions)},version=version+1,updated_at=${new Date().toISOString()} WHERE entity_type=${entry.entityType} AND entity_id=${entry.entityId}`);else await tx.execute(sql`UPDATE field_reusable_blocks SET document=${JSON.stringify(document)},definition_versions=${JSON.stringify(versions)},version=version+1,updated_at=${new Date().toISOString()} WHERE id=${entry.identity}`)}}
async function purgeGroupCorpus(tx:FieldsTransaction,groupKey:string,corpus:GroupCorpus):Promise<void>{await tx.execute(sql`DELETE FROM field_value_nodes WHERE group_key=${groupKey}`);for(const revision of corpus.revisions)await tx.execute(sql`DELETE FROM field_revisions WHERE id=${revision.id}`);for(const document of corpus.documents){if(document.section==='current')await tx.execute(sql`DELETE FROM field_block_documents WHERE entity_type=${document.entityType} AND entity_id=${document.entityId}`);else await tx.execute(sql`DELETE FROM field_reusable_blocks WHERE id=${document.identity}`)}}

function groupCountsEqual(left:Readonly<Record<string,number>>,right:unknown):boolean{if(typeof right!=='object'||right===null||Array.isArray(right))return false;const normalized:Record<string,number>={};for(const [key,value] of Object.entries(right as Record<string,unknown>)){const number=Number(value);if(!Number.isSafeInteger(number)||number<0)return false;normalized[key]=number}return JSON.stringify(Object.entries(left).sort())===JSON.stringify(Object.entries(normalized).sort())}
function parseTokenGroupDefinition(value:unknown,groupKey:string):FieldGroup{const definition=parseFieldGroupDefinition(value);if(definition.key!==groupKey)throw new FieldsLifecycleError('validation','impact token field-group key mismatch');return definition}
function groupStrategyEqual(left:FieldGroupChangeStrategy,right:unknown):boolean{try{return JSON.stringify(freezeStorage(left,'strategy'))===JSON.stringify(freezeStorage(right,'strategy'))}catch{return false}}

export async function applyFieldGroupChange(
  tx:FieldsTransaction,
  impactToken:string,
  strategyInput:FieldGroupChangeStrategy,
  operationId:string,
  context:FieldsContext,
  destructive?:FieldsDestructiveDependencies,
):Promise<ResolvedFinalFieldGroup|null>{
  assertActiveFieldsTransaction(tx)
  const token=await groupTokenDecode(impactToken)
  if(token.kind!=='field-group-change'||typeof token.groupKey!=='string')throw new FieldsLifecycleError('validation','field-group impact token has wrong operation kind')
  const groupKey=normalizedGroupKey(token.groupKey),strategy=normalizeGroupStrategy(strategyInput)
  if(!groupStrategyEqual(strategy,token.strategy))throw new FieldsLifecycleError('operation-conflict','field-group strategy differs from preview')
  const authorized=await assertFieldsAuthorized(context,'manageDefinitions',groupDefinitionRef(groupKey))
  const entityTypes=Array.isArray(token.entityTypes)?token.entityTypes.map((value,index)=>{if(typeof value!=='string'||!value||value.length>256)throw new FieldsLifecycleError('validation','impact token entity type is invalid',`entityTypes.${index}`);return value}):[]
  const migration=await assertDefinitionMigration(context,entityTypes.length?entityTypes:['fields-definition'])
  if(token.authorizationPolicyVersion!==migration.policyVersion)throw new FieldsLifecycleError('corpus-drift','authorization policy changed since preview')
  const requestHash=await fieldsRequestHash({impactToken,strategy})
  const replay=await readFieldsReceipt(tx,operationId)
  if(replay){assertReceiptReplay(replay,{kind:'fields:apply-field-group-change',context:authorized,requestHash});const row=await finalGroupRow(tx,groupKey);return row?resolvedFinalGroup(row):null}
  const row=await finalGroupRow(tx,groupKey)
  if(!row)throw new FieldsLifecycleError('not-found','field-group definition is unavailable')
  const current=resolvedFinalGroup(row)
  if(current.origin==='code')throw new FieldsLifecycleError('validation','code-owned field groups change only through registry reconciliation')
  if(Number(token.expectedVersion)!==current.version)throw new FieldsLifecycleError('stale-version','field-group version changed')
  const corpus=await groupCorpus(tx,groupKey)
  if(token.valueCorpusVersion!==corpus.version||token.valueCorpusHash!==corpus.hash||!groupCountsEqual(corpus.counts,token.affectedCounts))throw new FieldsLifecycleError('corpus-drift','field-group value corpus changed since preview')
  if(JSON.stringify([...corpus.entityTypes].sort())!==JSON.stringify([...entityTypes].sort()))throw new FieldsLifecycleError('corpus-drift','field-group authorization scope changed since preview')
  const next=parseTokenGroupDefinition(token.nextDefinition,groupKey)
  const incompatibilities=await groupIncompatibilities(current,next,corpus)
  const tokenIncompatibilities=Array.isArray(token.incompatibilities)?token.incompatibilities.map(String).sort():[]
  if(JSON.stringify([...incompatibilities].sort())!==JSON.stringify(tokenIncompatibilities))throw new FieldsLifecycleError('corpus-drift','field-group compatibility facts changed since preview')
  if(strategy.kind==='reject'&&incompatibilities.length>0)throw new FieldsLifecycleError('validation','reject strategy cannot apply an incompatible field-group change')
  if(strategy.kind==='migrateValues')ensureMappings(current,next,strategy,incompatibilities)

  const nextHash=await canonicalFieldGroupHash(next),definitionChanged=nextHash!==row.canonicalHash
  const nextRevision=definitionChanged?current.revision+1:current.revision
  const nextVersion=current.version+1
  if(definitionChanged)await tx.execute(sql`INSERT INTO field_definition_versions (group_key,revision,canonical_hash,definition,origin,created_at) VALUES (${groupKey},${nextRevision},${nextHash},${JSON.stringify(next)},'db',${new Date().toISOString()})`)

  if(strategy.kind==='migrateValues'){
    await writeMigratedNodes(tx,groupKey,next,nextRevision,corpus,strategy.mappings,context)
    await migrateCurrentDocuments(tx,groupKey,nextRevision,corpus,strategy.mappings)
  }else if(strategy.kind==='purgeValues'){
    if(!destructive)throw new FieldsLifecycleError('backup-invalid','destructive dependencies are required')
    const scopeHash=await fieldsRequestHash({definitionKind:'group',definitionKey:groupKey,strategy,authorizationPolicyVersion:migration.policyVersion,corpusVersion:corpus.version,corpusHash:corpus.hash,previewToken:impactToken,affectedCounts:corpus.counts})
    const backup=await verifyFieldsBackup(tx,{backupId:strategy.backupId,expectedScopeHash:scopeHash,corpusVersion:corpus.version,corpusHash:corpus.hash,counts:corpus.counts},destructive.backups)
    const event:FieldsDestructiveEvent=Object.freeze({operationId,principalId:authorized.principal.id,definitionKind:'group',definitionKey:groupKey,strategy:freezeStorage(strategy,'strategy'),authorizationPolicyVersion:migration.policyVersion,corpusVersion:corpus.version,corpusHash:corpus.hash,previewToken:impactToken,affectedCounts:corpus.counts,backup})
    await destructive.effects.audit.record(tx,event)
    await destructive.effects.outbox.enqueue(tx,event)
    await purgeGroupCorpus(tx,groupKey,corpus)
  }

  await tx.execute(sql`UPDATE field_group_definitions SET version=${nextVersion},active=${next.active},current_revision=${nextRevision},canonical_hash=${nextHash},definition=${JSON.stringify(next)},updated_at=${new Date().toISOString()} WHERE key=${groupKey} AND version=${current.version}`)
  const result:ResolvedFinalFieldGroup=Object.freeze({...next,origin:'db',version:nextVersion,revision:nextRevision,...(current.shadowedDbVersion===undefined?{}:{shadowedDbVersion:current.shadowedDbVersion})})
  await writeFieldsReceipt(tx,{operationId,kind:'fields:apply-field-group-change',context:authorized,requestHash,result:Object.freeze({key:groupKey,version:nextVersion,revision:nextRevision})})
  return result
}

export interface CodeFieldsRegistrySnapshot {
  readonly version: string
  readonly canonicalHash: string
  readonly groups: readonly CodeFinalFieldGroup[]
  readonly optionsPages: readonly CodeOptionsPage[]
  readonly blocks: readonly CodeFieldBlock[]
}
export interface FieldsCodeReconciliationPlan {
  readonly token: string
  readonly fromVersion: string
  readonly nextCanonicalHash: string
  readonly groupImpacts: readonly FieldDefinitionImpact[]
  readonly optionsImpacts: readonly OptionsPageImpact[]
  readonly blockImpacts: readonly FieldBlockDefinitionImpact[]
}
interface ReconciliationImpactMeta {
  readonly kind: 'group'|'optionsPage'|'block'
  readonly key: string
  readonly expectedVersion: number
  readonly policyVersion: string
  readonly corpusVersion: string
  readonly corpusHash: string
  readonly counts: Readonly<Record<string,number>>
  readonly incompatibilities: readonly string[]
  readonly entityTypes: readonly string[]
}
interface StoredFieldsCodePlan {
  readonly kind: 'fields-code-reconciliation'
  readonly fromVersion: string
  readonly nextCanonicalHash: string
  readonly next: FieldStorageValue
  readonly groupStrategies: FieldStorageValue
  readonly optionsStrategies: FieldStorageValue
  readonly blockStrategies: FieldStorageValue
  readonly impacts: readonly FieldStorageValue[]
  readonly authorityTypes: readonly string[]
}
async function normalizeCodeSnapshot(input:CodeFieldsRegistrySnapshot):Promise<CodeFieldsRegistrySnapshot>{
  if(typeof input.version!=='string'||input.version.length===0||input.version.length>256)throw new FieldsLifecycleError('validation','code registry version must be bounded','version')
  const groups:CodeFinalFieldGroup[]=[],groupKeys=new Set<string>()
  for(const raw of input.groups){const group=defineFinalFieldGroup(raw);if(groupKeys.has(group.key))throw new FieldsLifecycleError('validation','duplicate code field-group key',group.key);groupKeys.add(group.key);groups.push(group)}
  const optionsModule=await import('./options.js'),optionsPages:CodeOptionsPage[]=[],optionKeys=new Set<string>()
  for(const raw of input.optionsPages){const page=optionsModule.defineOptionsPage(raw);if(optionKeys.has(page.key))throw new FieldsLifecycleError('validation','duplicate code options-page key',page.key);optionKeys.add(page.key);optionsPages.push(page)}
  const blocksModule=await import('./blocks.js'),blocks:CodeFieldBlock[]=[],blockKeys=new Set<string>()
  for(const raw of input.blocks){const block=blocksModule.defineFieldBlock(raw);if(blockKeys.has(block.key))throw new FieldsLifecycleError('validation','duplicate code block key',block.key);blockKeys.add(block.key);blocks.push(block)}
  const normalized=Object.freeze({version:input.version,canonicalHash:'',groups:Object.freeze(groups.sort((a,b)=>a.key.localeCompare(b.key))),optionsPages:Object.freeze(optionsPages.sort((a,b)=>a.key.localeCompare(b.key))),blocks:Object.freeze(blocks.sort((a,b)=>a.key.localeCompare(b.key)))})
  const canonicalHash=await fieldsRequestHash({version:normalized.version,groups:normalized.groups,optionsPages:normalized.optionsPages,blocks:normalized.blocks})
  if(input.canonicalHash!==canonicalHash)throw new FieldsLifecycleError('validation','code registry canonicalHash does not match normalized bytes','canonicalHash')
  return Object.freeze({...normalized,canonicalHash})
}
async function currentFieldsCodeRegistryVersion(db:FieldsLifecycleReader):Promise<string>{
  const groups=(await finalGroupRows(db)).filter(row=>row.origin==='code').map(row=>({key:row.key,version:Number(row.version),revision:Number(row.currentRevision),hash:row.canonicalHash,active:lifecycleBool(row.active)}))
  const options=await lifecycleExecute<Record<string,unknown>>(db,sql`SELECT key,version,current_revision AS revision,canonical_hash AS hash,active FROM field_options_definitions WHERE origin='code' ORDER BY key`)
  const blocks=await lifecycleExecute<Record<string,unknown>>(db,sql`SELECT key,version,current_revision AS revision,canonical_hash AS hash,active FROM field_block_definitions WHERE origin='code' ORDER BY key`)
  return fieldsRequestHash({groups,options,blocks})
}
function strategyMapStorage(value:unknown):FieldStorageValue{return freezeStorage(value,'strategies')}
function impactMetaStorage(value:ReconciliationImpactMeta):FieldStorageValue{return freezeStorage(value,'impact')}
function codePlanEncode(plan:StoredFieldsCodePlan):Promise<string>{const bytes=new TextEncoder().encode(JSON.stringify(plan));if(bytes.byteLength>1024*1024)throw new FieldsLifecycleError('validation','code reconciliation plan exceeds token limit');const hex=Array.from(bytes,b=>b.toString(16).padStart(2,'0')).join('');return fieldsRequestHash(plan).then(hash=>`${hex}.${hash}`)}
async function codePlanDecode(token:string):Promise<StoredFieldsCodePlan>{const match=/^([a-f0-9]+)\.([a-f0-9]{64})$/i.exec(token);if(!match||match[1]!.length%2||match[1]!.length>2*1024*1024)throw new FieldsLifecycleError('validation','code reconciliation token is invalid');let value:unknown;try{value=JSON.parse(new TextDecoder().decode(new Uint8Array(match[1]!.match(/../g)!.map(pair=>Number.parseInt(pair,16))))) as unknown}catch{throw new FieldsLifecycleError('validation','code reconciliation token is invalid')}if(typeof value!=='object'||value===null||Array.isArray(value)||await fieldsRequestHash(value)!==match[2]!.toLowerCase())throw new FieldsLifecycleError('validation','code reconciliation token integrity check failed');const plan=value as StoredFieldsCodePlan;if(plan.kind!=='fields-code-reconciliation')throw new FieldsLifecycleError('validation','code reconciliation token has wrong kind');return plan}

function numericCounts(value: Readonly<Record<string, number>>): number {
  return Object.values(value).reduce((sum, count) => sum + count, 0)
}
function metaPublicGroup(meta: ReconciliationImpactMeta, token: string): FieldDefinitionImpact {
  return Object.freeze({
    token,
    groupKey: meta.key,
    expectedVersion: meta.expectedVersion,
    authorizationPolicyVersion: meta.policyVersion,
    valueCorpusVersion: meta.corpusVersion,
    valueCorpusHash: meta.corpusHash,
    affectedCounts: meta.counts,
    incompatibilities: meta.incompatibilities,
  })
}
function metaPublicOptions(meta: ReconciliationImpactMeta, token: string): OptionsPageImpact {
  return Object.freeze({
    token,
    pageKey: meta.key,
    expectedVersion: meta.expectedVersion,
    authorizationPolicyVersion: meta.policyVersion,
    valueCorpusVersion: meta.corpusVersion,
    valueCorpusHash: meta.corpusHash,
    affectedCounts: meta.counts,
  })
}
function metaPublicBlock(meta: ReconciliationImpactMeta, token: string): FieldBlockDefinitionImpact {
  return Object.freeze({
    token,
    blockKey: meta.key,
    expectedVersion: meta.expectedVersion,
    authorizationPolicyVersion: meta.policyVersion,
    documentCorpusVersion: meta.corpusVersion,
    documentCorpusHash: meta.corpusHash,
    affectedDocuments: meta.counts.documents ?? 0,
    affectedDefinitionVersions: meta.counts.definitionVersions ?? 0,
    incompatibilities: meta.incompatibilities,
  })
}
async function reconciliationImpactToken(meta: ReconciliationImpactMeta, strategy: unknown, nextDefinition: unknown): Promise<string> {
  return fieldsRequestHash({ kind: 'fields-code-definition-impact', meta, strategy, nextDefinition })
}

export async function previewFieldsCodeReconciliation(
  db: FieldsLifecycleReader,
  context: FieldsContext,
  input: {
    expectedCurrentVersion: string
    next: CodeFieldsRegistrySnapshot
    groupStrategies: Readonly<Record<string, FieldGroupChangeStrategy>>
    optionsStrategies: Readonly<Record<string, OptionsPageDeactivateStrategy>>
    blockStrategies: Readonly<Record<string, FieldBlockChangeStrategy>>
  },
): Promise<FieldsCodeReconciliationPlan> {
  await assertFieldsAuthorized(context, 'manageDefinitions', { entityType: 'fields-definition', entityId: '*' })
  const fromVersion = await currentFieldsCodeRegistryVersion(db)
  if (input.expectedCurrentVersion !== fromVersion) throw new FieldsLifecycleError('stale-version', 'code fields registry version changed')
  const next = await normalizeCodeSnapshot(input.next)
  const nextGroups = new Map(next.groups.map((group) => [group.key, group]))
  const nextOptions = new Map(next.optionsPages.map((page) => [page.key, page]))
  const nextBlocks = new Map(next.blocks.map((block) => [block.key, block]))
  const currentGroups = await finalGroupRows(db)
  const optionsModule = await import('./options.js')
  const blocksModule = await import('./blocks.js')
  const currentOptions = await optionsModule.__optionsLifecycleInternals.readOptionRows(db)
  const currentBlocks = await blocksModule.__fieldBlockLifecycleInternals.readCurrentRows(db)

  // Validate the complete effective next dependency graph, not only changed keys.
  const effectiveGroups = new Set<string>()
  for (const row of currentGroups) if (lifecycleBool(row.active) && row.origin !== 'code' && !nextGroups.has(row.key)) effectiveGroups.add(row.key)
  for (const group of next.groups) {
    if (group.active !== true) throw new FieldsLifecycleError('validation', 'code field groups must be active', group.key)
    effectiveGroups.add(group.key)
  }
  for (const block of next.blocks) for (const groupKey of block.fieldGroupKeys) {
    if (!effectiveGroups.has(groupKey)) throw new FieldsLifecycleError('validation', 'code block references missing effective field group', `${block.key}:${groupKey}`)
  }
  const effectiveOptionRows = currentOptions.filter((row) => row.origin !== 'code' && !nextOptions.has(row.key))
  optionsModule.__optionsLifecycleInternals.mergePages(effectiveOptionRows, next.optionsPages)
  const effectiveBlockRows = currentBlocks.filter((row) => row.origin !== 'code' && !nextBlocks.has(row.key))
  const mergedBlocks = blocksModule.__fieldBlockLifecycleInternals.mergeCurrent(effectiveBlockRows, next.blocks)
  blocksModule.__fieldBlockLifecycleInternals.validateDefinitionGraph(mergedBlocks.blocks)

  const groupImpacts: FieldDefinitionImpact[] = []
  const optionsImpacts: OptionsPageImpact[] = []
  const blockImpacts: FieldBlockDefinitionImpact[] = []
  const metas: ReconciliationImpactMeta[] = []
  const authorityTypes = new Set<string>()

  for (const row of currentGroups) {
    const nextDefinition = nextGroups.get(row.key)
    const removing = row.origin === 'code' && nextDefinition === undefined
    const nextHash = nextDefinition ? await canonicalFieldGroupHash(codeGroupDefinition(nextDefinition)) : undefined
    const changing = nextDefinition !== undefined && (row.origin !== 'code' || row.canonicalHash !== nextHash || !lifecycleBool(row.active))
    if (!removing && !changing) continue
    const current = resolvedFinalGroup(row)
    const proposed = nextDefinition ? codeGroupDefinition(nextDefinition) : parseFieldGroupDefinition({ ...codeGroupDefinition(current), active: false })
    const corpus = await groupCorpus(db, row.key)
    const migration = await assertDefinitionMigration(context, corpus.entityTypes.length ? corpus.entityTypes : ['fields-definition'])
    for (const type of corpus.entityTypes) authorityTypes.add(type)
    const incompatibilities = await groupIncompatibilities(current, proposed, corpus)
    const strategy = normalizeGroupStrategy(input.groupStrategies[row.key] ?? { kind: 'reject' })
    if (incompatibilities.length > 0 && input.groupStrategies[row.key] === undefined) {
      throw new FieldsLifecycleError('validation', `code field group ${row.key} requires an explicit migration strategy`)
    }
    if (strategy.kind === 'reject' && incompatibilities.length > 0) {
      // Preview is allowed to return the incompatibilities; apply will reject until strategy changes.
    }
    const meta: ReconciliationImpactMeta = Object.freeze({ kind: 'group', key: row.key, expectedVersion: Number(row.version), policyVersion: migration.policyVersion, corpusVersion: corpus.version, corpusHash: corpus.hash, counts: corpus.counts, incompatibilities, entityTypes: corpus.entityTypes })
    metas.push(meta)
    groupImpacts.push(metaPublicGroup(meta, await reconciliationImpactToken(meta, strategy, proposed)))
  }

  for (const row of currentOptions) {
    const nextDefinition = nextOptions.get(row.key)
    const removing = row.origin === 'code' && nextDefinition === undefined
    const normalizedNext = nextDefinition ? optionsModule.__optionsLifecycleInternals.normalizeOptionsPage(nextDefinition) : null
    const nextHash = normalizedNext ? await fieldsRequestHash(normalizedNext) : undefined
    const changing = normalizedNext !== null && (row.origin !== 'code' || row.canonicalHash !== nextHash || !lifecycleBool(row.active))
    if (!removing && !changing) continue
    const corpus = await optionsModule.__optionsLifecycleInternals.optionsCorpus(db, row.key)
    const migration = await assertDefinitionMigration(context, ['options'])
    authorityTypes.add('options')
    const strategy = input.optionsStrategies[row.key] ?? { kind: 'retain' as const }
    if (strategy.kind !== 'retain' && strategy.kind !== 'purge') throw new FieldsLifecycleError('validation', 'unknown options reconciliation strategy', row.key)
    if (removing && numericCounts(corpus.counts) > 0 && input.optionsStrategies[row.key] === undefined) {
      throw new FieldsLifecycleError('validation', `code options page ${row.key} requires an explicit deactivation strategy`)
    }
    const incompatibilities = Object.freeze(removing && numericCounts(corpus.counts) > 0 ? ['options-page-in-use'] : [] as string[])
    const meta: ReconciliationImpactMeta = Object.freeze({ kind: 'optionsPage', key: row.key, expectedVersion: Number(row.version), policyVersion: migration.policyVersion, corpusVersion: corpus.version, corpusHash: corpus.hash, counts: corpus.counts, incompatibilities, entityTypes: Object.freeze(['options']) })
    metas.push(meta)
    optionsImpacts.push(metaPublicOptions(meta, await reconciliationImpactToken(meta, strategy, normalizedNext)))
  }

  for (const row of currentBlocks) {
    const nextDefinition = nextBlocks.get(row.key)
    const removing = row.origin === 'code' && nextDefinition === undefined
    const normalizedNext = nextDefinition ? blocksModule.__fieldBlockLifecycleInternals.normalizeDefinition(nextDefinition) : null
    const nextHash = normalizedNext ? await fieldsRequestHash(normalizedNext) : undefined
    const changing = normalizedNext !== null && (row.origin !== 'code' || row.canonicalHash !== nextHash || !lifecycleBool(row.active))
    if (!removing && !changing) continue
    const current = blocksModule.__fieldBlockLifecycleInternals.readCurrentBlock ? await blocksModule.__fieldBlockLifecycleInternals.readCurrentBlock(db, row.key) : null
    if (!current) throw new FieldsLifecycleError('integrity', 'current block disappeared during reconciliation preview')
    const corpus = await blocksModule.__fieldBlockLifecycleInternals.blockCorpus(db, row.key)
    const migration = await assertDefinitionMigration(context, corpus.entityTypes.length ? corpus.entityTypes : ['fields-definition'])
    for (const type of corpus.entityTypes) authorityTypes.add(type)
    const strategy = input.blockStrategies[row.key] ?? { kind: 'reject' as const }
    const replacement = strategy.kind === 'mapDefinition' ? await blocksModule.__fieldBlockLifecycleInternals.readCurrentBlock(db, strategy.replacementKey) : null
    const incompatibilities = [...blocksModule.__fieldBlockLifecycleInternals.blockChangeIncompatibilities(current.block, normalizedNext, corpus, strategy, removing, false, replacement?.block)]
    if (incompatibilities.length > 0 && input.blockStrategies[row.key] === undefined) {
      throw new FieldsLifecycleError('validation', `code block ${row.key} requires an explicit change strategy`)
    }
    const counts = Object.freeze({ documents: corpus.entries.length, definitionVersions: corpus.affectedDefinitionVersions })
    const meta: ReconciliationImpactMeta = Object.freeze({ kind: 'block', key: row.key, expectedVersion: Number(row.version), policyVersion: migration.policyVersion, corpusVersion: corpus.version, corpusHash: corpus.hash, counts, incompatibilities: Object.freeze([...new Set(incompatibilities)].sort()), entityTypes: corpus.entityTypes })
    metas.push(meta)
    blockImpacts.push(metaPublicBlock(meta, await reconciliationImpactToken(meta, strategy, normalizedNext)))
  }

  const plan: StoredFieldsCodePlan = Object.freeze({
    kind: 'fields-code-reconciliation',
    fromVersion,
    nextCanonicalHash: next.canonicalHash,
    next: freezeStorage(next, 'next'),
    groupStrategies: strategyMapStorage(input.groupStrategies),
    optionsStrategies: strategyMapStorage(input.optionsStrategies),
    blockStrategies: strategyMapStorage(input.blockStrategies),
    impacts: Object.freeze(metas.map(impactMetaStorage)),
    authorityTypes: Object.freeze([...authorityTypes].sort()),
  })
  return Object.freeze({ token: await codePlanEncode(plan), fromVersion, nextCanonicalHash: next.canonicalHash, groupImpacts: Object.freeze(groupImpacts), optionsImpacts: Object.freeze(optionsImpacts), blockImpacts: Object.freeze(blockImpacts) })
}

function parseImpactMeta(value: unknown): ReconciliationImpactMeta {
  const record = lifecycleRecord(value, 'reconciliation impact')
  if (record.kind !== 'group' && record.kind !== 'optionsPage' && record.kind !== 'block') throw new FieldsLifecycleError('integrity', 'reconciliation impact kind is invalid')
  const key = typeof record.key === 'string' ? record.key : ''
  const expectedVersion = Number(record.expectedVersion)
  const policyVersion = typeof record.policyVersion === 'string' ? record.policyVersion : ''
  const corpusVersion = typeof record.corpusVersion === 'string' ? record.corpusVersion : ''
  const corpusHash = typeof record.corpusHash === 'string' ? record.corpusHash : ''
  if (!key || key.length > 256 || !Number.isSafeInteger(expectedVersion) || expectedVersion < 1 || !policyVersion || !corpusVersion || !corpusHash) throw new FieldsLifecycleError('integrity', 'reconciliation impact identity is malformed')
  const countsRecord = lifecycleRecord(record.counts, 'reconciliation impact counts'), counts: Record<string, number> = {}
  for (const [name, raw] of Object.entries(countsRecord)) { const number = Number(raw); if (!Number.isSafeInteger(number) || number < 0) throw new FieldsLifecycleError('integrity', 'reconciliation impact count is invalid'); counts[name] = number }
  if (!Array.isArray(record.incompatibilities) || !record.incompatibilities.every((item) => typeof item === 'string')) throw new FieldsLifecycleError('integrity', 'reconciliation incompatibilities are malformed')
  if (!Array.isArray(record.entityTypes) || !record.entityTypes.every((item) => typeof item === 'string' && item.length > 0 && item.length <= 256)) throw new FieldsLifecycleError('integrity', 'reconciliation entity types are malformed')
  return Object.freeze({ kind: record.kind, key, expectedVersion, policyVersion, corpusVersion, corpusHash, counts: Object.freeze(counts), incompatibilities: Object.freeze([...record.incompatibilities] as string[]), entityTypes: Object.freeze([...record.entityTypes] as string[]) })
}

interface VerifiedGroupImpact {
  readonly meta: ReconciliationImpactMeta
  readonly row: FinalGroupRow
  readonly current: ResolvedFinalFieldGroup
  readonly next: FieldGroup | null
  readonly strategy: FieldGroupChangeStrategy
  readonly corpus: GroupCorpus
  readonly impactToken: string
  readonly backup?: FieldsBackupManifest
}
interface VerifiedOptionsImpact {
  readonly meta: ReconciliationImpactMeta
  readonly row: Record<string, unknown>
  readonly next: CodeOptionsPage | null
  readonly strategy: OptionsPageDeactivateStrategy
  readonly corpus: { readonly version: string; readonly hash: string; readonly counts: Readonly<Record<string, number>> }
  readonly impactToken: string
  readonly backup?: FieldsBackupManifest
}
interface VerifiedBlockImpact {
  readonly meta: ReconciliationImpactMeta
  readonly current: Awaited<ReturnType<(typeof import('./blocks.js'))['__fieldBlockLifecycleInternals']['readCurrentBlock']>> extends infer T ? NonNullable<T> : never
  readonly next: CodeFieldBlock | null
  readonly strategy: FieldBlockChangeStrategy
  readonly corpus: Awaited<ReturnType<(typeof import('./blocks.js'))['__fieldBlockLifecycleInternals']['blockCorpus']>>
  readonly replacement: Awaited<ReturnType<(typeof import('./blocks.js'))['__fieldBlockLifecycleInternals']['readCurrentBlock']>>
  readonly impactToken: string
  readonly backup?: FieldsBackupManifest
}

function optionsStrategyAt(map: Readonly<Record<string, OptionsPageDeactivateStrategy>>, key: string): OptionsPageDeactivateStrategy {
  const strategy = map[key] ?? { kind: 'retain' as const }
  if (strategy.kind !== 'retain' && strategy.kind !== 'purge') throw new FieldsLifecycleError('validation', 'unknown options reconciliation strategy', key)
  if (strategy.kind === 'purge' && (!strategy.backupId || strategy.backupId.length > 512)) throw new FieldsLifecycleError('validation', 'options purge backupId is invalid', key)
  return Object.freeze({ ...strategy })
}
function blockStrategyAt(map: Readonly<Record<string, FieldBlockChangeStrategy>>, key: string): FieldBlockChangeStrategy {
  const strategy = map[key] ?? { kind: 'reject' as const }
  if (strategy.kind === 'reject' || strategy.kind === 'retainUnknown') return Object.freeze({ kind: strategy.kind })
  if (strategy.kind === 'mapDefinition') {
    if (!/^[a-z][a-z0-9_-]*$/.test(strategy.replacementKey)) throw new FieldsLifecycleError('validation', 'block replacement key is invalid', key)
    return Object.freeze({ kind: 'mapDefinition', replacementKey: strategy.replacementKey })
  }
  if (strategy.kind === 'purgeWithBackup') {
    if (!strategy.backupId || strategy.backupId.length > 512) throw new FieldsLifecycleError('validation', 'block purge backupId is invalid', key)
    return Object.freeze({ kind: 'purgeWithBackup', backupId: strategy.backupId })
  }
  throw new FieldsLifecycleError('validation', 'unknown block reconciliation strategy', key)
}

export async function applyFieldsCodeReconciliation(
  tx: FieldsTransaction,
  context: FieldsContext,
  input: {
    planToken: string
    operationId: string
    next: CodeFieldsRegistrySnapshot
    groupStrategies: Readonly<Record<string, FieldGroupChangeStrategy>>
    optionsStrategies: Readonly<Record<string, OptionsPageDeactivateStrategy>>
    blockStrategies: Readonly<Record<string, FieldBlockChangeStrategy>>
  },
  effects: FieldsDefinitionEffects,
  destructive?: FieldsDestructiveDependencies,
): Promise<CodeFieldsRegistrySnapshot> {
  assertActiveFieldsTransaction(tx)
  const authorized = await assertFieldsAuthorized(context, 'manageDefinitions', { entityType: 'fields-definition', entityId: '*' })
  const plan = await codePlanDecode(input.planToken)
  const next = await normalizeCodeSnapshot(input.next)
  if (next.canonicalHash !== plan.nextCanonicalHash || await fieldsRequestHash(freezeStorage(next, 'next')) !== await fieldsRequestHash(plan.next)) throw new FieldsLifecycleError('operation-conflict', 'next code registry differs from preview')
  if (await fieldsRequestHash(strategyMapStorage(input.groupStrategies)) !== await fieldsRequestHash(plan.groupStrategies)
    || await fieldsRequestHash(strategyMapStorage(input.optionsStrategies)) !== await fieldsRequestHash(plan.optionsStrategies)
    || await fieldsRequestHash(strategyMapStorage(input.blockStrategies)) !== await fieldsRequestHash(plan.blockStrategies)) {
    throw new FieldsLifecycleError('operation-conflict', 'code reconciliation strategies differ from preview')
  }
  const requestHash = await fieldsRequestHash({ planToken: input.planToken, nextCanonicalHash: next.canonicalHash, groupStrategies: input.groupStrategies, optionsStrategies: input.optionsStrategies, blockStrategies: input.blockStrategies })
  const replay = await readFieldsReceipt(tx, input.operationId)
  if (replay) {
    assertReceiptReplay(replay, { kind: 'fields:reconcile-code', context: authorized, requestHash })
    return next
  }
  if (await currentFieldsCodeRegistryVersion(tx) !== plan.fromVersion) throw new FieldsLifecycleError('corpus-drift', 'code fields registry changed since preview')

  const nextGroups = new Map(next.groups.map((group) => [group.key, group]))
  const nextOptions = new Map(next.optionsPages.map((page) => [page.key, page]))
  const nextBlocks = new Map(next.blocks.map((block) => [block.key, block]))
  const metas = plan.impacts.map(parseImpactMeta)
  const optionsModule = await import('./options.js')
  const blocksModule = await import('./blocks.js')
  const plannedAuthorityTypes = [...new Set(metas.flatMap((meta) => meta.entityTypes))].sort()
  if (JSON.stringify(plannedAuthorityTypes) !== JSON.stringify([...plan.authorityTypes].sort())) throw new FieldsLifecycleError('integrity', 'code reconciliation authority scope is inconsistent')

  const graphGroups = await finalGroupRows(tx)
  const effectiveGroups = new Set<string>()
  for (const row of graphGroups) if (lifecycleBool(row.active) && row.origin !== 'code' && !nextGroups.has(row.key)) effectiveGroups.add(row.key)
  for (const group of next.groups) effectiveGroups.add(group.key)
  for (const block of next.blocks) for (const groupKey of block.fieldGroupKeys) if (!effectiveGroups.has(groupKey)) throw new FieldsLifecycleError('corpus-drift', `effective field group ${groupKey} disappeared after preview`)
  const graphOptions = await optionsModule.__optionsLifecycleInternals.readOptionRows(tx)
  optionsModule.__optionsLifecycleInternals.mergePages(graphOptions.filter((row) => row.origin !== 'code' && !nextOptions.has(row.key)), next.optionsPages)
  const graphBlocks = await blocksModule.__fieldBlockLifecycleInternals.readCurrentRows(tx)
  const graphMergedBlocks = blocksModule.__fieldBlockLifecycleInternals.mergeCurrent(graphBlocks.filter((row) => row.origin !== 'code' && !nextBlocks.has(row.key)), next.blocks)
  blocksModule.__fieldBlockLifecycleInternals.validateDefinitionGraph(graphMergedBlocks.blocks)

  const verifiedGroups: VerifiedGroupImpact[] = []
  const verifiedOptions: VerifiedOptionsImpact[] = []
  const verifiedBlocks: VerifiedBlockImpact[] = []
  const corpusHashes: string[] = []
  const affectedCounts: Record<string, number> = {}

  for (const meta of metas) {
    const migration = await assertDefinitionMigration(context, meta.entityTypes.length ? meta.entityTypes : ['fields-definition'])
    if (migration.policyVersion !== meta.policyVersion) throw new FieldsLifecycleError('corpus-drift', `authorization policy changed for ${meta.kind}:${meta.key}`)
    corpusHashes.push(meta.corpusHash)
    for (const [metric, count] of Object.entries(meta.counts)) affectedCounts[`${meta.kind}:${meta.key}:${metric}`] = count
    if (meta.kind === 'group') {
      const row = await finalGroupRow(tx, meta.key)
      if (!row || Number(row.version) !== meta.expectedVersion) throw new FieldsLifecycleError('corpus-drift', `field group ${meta.key} changed since preview`)
      const current = resolvedFinalGroup(row), nextCode = nextGroups.get(meta.key), nextDefinition = nextCode ? codeGroupDefinition(nextCode) : null
      const proposed = nextDefinition ?? parseFieldGroupDefinition({ ...codeGroupDefinition(current), active: false })
      const corpus = await groupCorpus(tx, meta.key)
      if (corpus.version !== meta.corpusVersion || corpus.hash !== meta.corpusHash || !groupCountsEqual(corpus.counts, meta.counts) || JSON.stringify(corpus.entityTypes) !== JSON.stringify(meta.entityTypes)) throw new FieldsLifecycleError('corpus-drift', `field group ${meta.key} corpus changed since preview`)
      const incompatibilities = await groupIncompatibilities(current, proposed, corpus)
      if (JSON.stringify([...incompatibilities].sort()) !== JSON.stringify([...meta.incompatibilities].sort())) throw new FieldsLifecycleError('corpus-drift', `field group ${meta.key} compatibility changed since preview`)
      const strategy = normalizeGroupStrategy(input.groupStrategies[meta.key] ?? { kind: 'reject' })
      if (strategy.kind === 'reject' && incompatibilities.length > 0) throw new FieldsLifecycleError('validation', `field group ${meta.key} has unresolved incompatibilities`)
      if (strategy.kind === 'migrateValues') ensureMappings(current, proposed, strategy, incompatibilities)
      const impactToken = await reconciliationImpactToken(meta, strategy, proposed)
      let backup: FieldsBackupManifest | undefined
      if (strategy.kind === 'purgeValues') {
        if (!destructive) throw new FieldsLifecycleError('backup-invalid', 'destructive dependencies are required')
        const scopeHash = await fieldsRequestHash({ definitionKind: 'group', definitionKey: meta.key, strategy, authorizationPolicyVersion: meta.policyVersion, corpusVersion: meta.corpusVersion, corpusHash: meta.corpusHash, previewToken: impactToken, affectedCounts: meta.counts })
        backup = await verifyFieldsBackup(tx, { backupId: strategy.backupId, expectedScopeHash: scopeHash, corpusVersion: meta.corpusVersion, corpusHash: meta.corpusHash, counts: meta.counts }, destructive.backups)
      }
      verifiedGroups.push(Object.freeze({ meta, row, current, next: nextDefinition, strategy, corpus, impactToken, ...(backup ? { backup } : {}) }))
    } else if (meta.kind === 'optionsPage') {
      const current = await optionsModule.__optionsLifecycleInternals.readDbPage(tx, meta.key)
      if (!current || current.page.version !== meta.expectedVersion) throw new FieldsLifecycleError('corpus-drift', `options page ${meta.key} changed since preview`)
      const corpus = await optionsModule.__optionsLifecycleInternals.optionsCorpus(tx, meta.key)
      if (corpus.version !== meta.corpusVersion || corpus.hash !== meta.corpusHash || !groupCountsEqual(corpus.counts, meta.counts)) throw new FieldsLifecycleError('corpus-drift', `options page ${meta.key} corpus changed since preview`)
      const nextCode = nextOptions.get(meta.key) ?? null
      const incompatibilities = Object.freeze(!nextCode && numericCounts(corpus.counts) > 0 ? ['options-page-in-use'] : [] as string[])
      if (JSON.stringify(incompatibilities) !== JSON.stringify(meta.incompatibilities)) throw new FieldsLifecycleError('corpus-drift', `options page ${meta.key} compatibility changed since preview`)
      const strategy = optionsStrategyAt(input.optionsStrategies, meta.key)
      const impactToken = await reconciliationImpactToken(meta, strategy, nextCode ? optionsModule.__optionsLifecycleInternals.normalizeOptionsPage(nextCode) : null)
      let backup: FieldsBackupManifest | undefined
      if (strategy.kind === 'purge') {
        if (!destructive) throw new FieldsLifecycleError('backup-invalid', 'destructive dependencies are required')
        const scopeHash = await fieldsRequestHash({ definitionKind: 'optionsPage', definitionKey: meta.key, strategy, authorizationPolicyVersion: meta.policyVersion, corpusVersion: meta.corpusVersion, corpusHash: meta.corpusHash, previewToken: impactToken, affectedCounts: meta.counts })
        backup = await verifyFieldsBackup(tx, { backupId: strategy.backupId, expectedScopeHash: scopeHash, corpusVersion: meta.corpusVersion, corpusHash: meta.corpusHash, counts: meta.counts }, destructive.backups)
      }
      verifiedOptions.push(Object.freeze({ meta, row: current.row, next: nextCode, strategy, corpus, impactToken, ...(backup ? { backup } : {}) }))
    } else {
      const current = await blocksModule.__fieldBlockLifecycleInternals.readCurrentBlock(tx, meta.key)
      if (!current || current.block.version !== meta.expectedVersion) throw new FieldsLifecycleError('corpus-drift', `block ${meta.key} changed since preview`)
      const corpus = await blocksModule.__fieldBlockLifecycleInternals.blockCorpus(tx, meta.key)
      if (corpus.version !== meta.corpusVersion || corpus.hash !== meta.corpusHash || (meta.counts.documents ?? 0) !== corpus.entries.length || (meta.counts.definitionVersions ?? 0) !== corpus.affectedDefinitionVersions || JSON.stringify(corpus.entityTypes) !== JSON.stringify(meta.entityTypes)) throw new FieldsLifecycleError('corpus-drift', `block ${meta.key} corpus changed since preview`)
      const nextCode = nextBlocks.get(meta.key) ?? null, strategy = blockStrategyAt(input.blockStrategies, meta.key)
      const replacement = strategy.kind === 'mapDefinition' ? await blocksModule.__fieldBlockLifecycleInternals.readCurrentBlock(tx, strategy.replacementKey) : null
      const nextDefinition = nextCode ? blocksModule.__fieldBlockLifecycleInternals.normalizeDefinition(nextCode) : null
      const incompatibilities = [...blocksModule.__fieldBlockLifecycleInternals.blockChangeIncompatibilities(current.block, nextDefinition, corpus, strategy, nextCode === null, false, replacement?.block)]
      if (strategy.kind === 'mapDefinition' && replacement) {
        if (JSON.stringify(replacement.block.parent ?? []) !== JSON.stringify(current.block.parent ?? [])) incompatibilities.push('replacement parent constraints differ from source')
        if (JSON.stringify(replacement.block.ancestor ?? []) !== JSON.stringify(current.block.ancestor ?? [])) incompatibilities.push('replacement ancestor constraints differ from source')
        if (replacement.block.supports?.innerBlocks !== current.block.supports?.innerBlocks) incompatibilities.push('replacement inner-block support differs from source')
      }
      const normalizedIncompatibilities = [...new Set(incompatibilities)].sort()
      if (JSON.stringify(normalizedIncompatibilities) !== JSON.stringify([...meta.incompatibilities].sort())) throw new FieldsLifecycleError('corpus-drift', `block ${meta.key} compatibility changed since preview`)
      if (normalizedIncompatibilities.length > 0) throw new FieldsLifecycleError('validation', `block ${meta.key} has unresolved incompatibilities: ${normalizedIncompatibilities.join('; ')}`)
      const impactToken = await reconciliationImpactToken(meta, strategy, nextDefinition)
      let backup: FieldsBackupManifest | undefined
      if (strategy.kind === 'purgeWithBackup') {
        if (!destructive) throw new FieldsLifecycleError('backup-invalid', 'destructive dependencies are required')
        const scopeHash = await fieldsRequestHash({ definitionKind: 'block', definitionKey: meta.key, strategy, authorizationPolicyVersion: meta.policyVersion, corpusVersion: meta.corpusVersion, corpusHash: meta.corpusHash, previewToken: impactToken, affectedCounts: meta.counts })
        backup = await verifyFieldsBackup(tx, { backupId: strategy.backupId, expectedScopeHash: scopeHash, corpusVersion: meta.corpusVersion, corpusHash: meta.corpusHash, counts: meta.counts }, destructive.backups)
      }
      verifiedBlocks.push(Object.freeze({ meta, current, next: nextCode, strategy, corpus, replacement, impactToken, ...(backup ? { backup } : {}) }))
    }
  }

  // All plan facts and destructive backups are verified above. Mutations begin here.
  for (const item of verifiedGroups) {
    const proposed = item.next ?? parseFieldGroupDefinition({ ...codeGroupDefinition(item.current), active: false })
    const nextHash = await canonicalFieldGroupHash(proposed)
    const nextRevision = item.next && (item.row.origin !== 'code' || item.row.canonicalHash !== nextHash)
      ? Number(item.row.currentRevision) + 1
      : Number(item.row.currentRevision)
    if (item.strategy.kind === 'migrateValues') {
      await writeMigratedNodes(tx, item.meta.key, proposed, nextRevision, item.corpus, item.strategy.mappings, context)
      await migrateCurrentDocuments(tx, item.meta.key, nextRevision, item.corpus, item.strategy.mappings)
    } else if (item.strategy.kind === 'purgeValues') {
      if (!destructive || !item.backup) throw new FieldsLifecycleError('backup-invalid', 'verified destructive dependencies disappeared')
      const event: FieldsDestructiveEvent = Object.freeze({ operationId: input.operationId, principalId: authorized.principal.id, definitionKind: 'group', definitionKey: item.meta.key, strategy: freezeStorage(item.strategy, 'strategy'), authorizationPolicyVersion: item.meta.policyVersion, corpusVersion: item.meta.corpusVersion, corpusHash: item.meta.corpusHash, previewToken: item.impactToken, affectedCounts: item.meta.counts, backup: item.backup })
      await destructive.effects.audit.record(tx, event)
      await destructive.effects.outbox.enqueue(tx, event)
      await purgeGroupCorpus(tx, item.meta.key, item.corpus)
    }
  }
  for (const item of verifiedOptions) {
    if (item.strategy.kind !== 'purge') continue
    if (!destructive || !item.backup) throw new FieldsLifecycleError('backup-invalid', 'verified destructive dependencies disappeared')
    const event: FieldsDestructiveEvent = Object.freeze({ operationId: input.operationId, principalId: authorized.principal.id, definitionKind: 'optionsPage', definitionKey: item.meta.key, strategy: freezeStorage(item.strategy, 'strategy'), authorizationPolicyVersion: item.meta.policyVersion, corpusVersion: item.meta.corpusVersion, corpusHash: item.meta.corpusHash, previewToken: item.impactToken, affectedCounts: item.meta.counts, backup: item.backup })
    await destructive.effects.audit.record(tx, event)
    await destructive.effects.outbox.enqueue(tx, event)
    await tx.execute(sql`DELETE FROM field_value_nodes WHERE entity_type='options' AND entity_id=${item.meta.key}`)
    await tx.execute(sql`DELETE FROM field_revisions WHERE entity_type='options' AND entity_id=${item.meta.key}`)
  }
  for (const item of verifiedBlocks) {
    if (item.strategy.kind === 'mapDefinition') {
      if (!item.replacement) throw new FieldsLifecycleError('validation', 'replacement block disappeared after verification')
      await blocksModule.__fieldBlockLifecycleInternals.applyBlockMap(tx, item.corpus, item.meta.key, item.replacement)
    } else if (item.strategy.kind === 'purgeWithBackup') {
      if (!destructive || !item.backup) throw new FieldsLifecycleError('backup-invalid', 'verified destructive dependencies disappeared')
      const event: FieldsDestructiveEvent = Object.freeze({ operationId: input.operationId, principalId: authorized.principal.id, definitionKind: 'block', definitionKey: item.meta.key, strategy: freezeStorage(item.strategy, 'strategy'), authorizationPolicyVersion: item.meta.policyVersion, corpusVersion: item.meta.corpusVersion, corpusHash: item.meta.corpusHash, previewToken: item.impactToken, affectedCounts: item.meta.counts, backup: item.backup })
      await destructive.effects.audit.record(tx, event)
      await destructive.effects.outbox.enqueue(tx, event)
      await blocksModule.__fieldBlockLifecycleInternals.purgeBlockCorpus(tx, item.corpus)
    }
  }

  const changedKeys: string[] = []
  const currentGroupRows = await finalGroupRows(tx)
  for (const code of next.groups) {
    const definition = codeGroupDefinition(code), canonicalHash = await canonicalFieldGroupHash(definition), existing = currentGroupRows.find((row) => row.key === definition.key), now = new Date().toISOString()
    if (!existing) {
      await tx.execute(sql`INSERT INTO field_group_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES (${definition.key},'code',1,true,1,${canonicalHash},${JSON.stringify(definition)},${now},${now})`)
      await tx.execute(sql`INSERT INTO field_definition_versions (group_key,revision,canonical_hash,definition,origin,created_at) VALUES (${definition.key},1,${canonicalHash},${JSON.stringify(definition)},'code',${now})`)
      changedKeys.push(`group:${definition.key}`)
    } else if (existing.origin !== 'code' || existing.canonicalHash !== canonicalHash || !lifecycleBool(existing.active)) {
      const version = Number(existing.version) + 1
      const revisionChanged = existing.origin !== 'code' || existing.canonicalHash !== canonicalHash
      const revision = Number(existing.currentRevision) + (revisionChanged ? 1 : 0)
      if (revisionChanged) await tx.execute(sql`INSERT INTO field_definition_versions (group_key,revision,canonical_hash,definition,origin,created_at) VALUES (${definition.key},${revision},${canonicalHash},${JSON.stringify(definition)},'code',${now})`)
      const shadowed = existing.origin === 'db' ? Number(existing.version) : existing.shadowedDbVersion
      await tx.execute(sql`UPDATE field_group_definitions SET origin='code',version=${version},active=true,current_revision=${revision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=${shadowed},updated_at=${now} WHERE key=${definition.key}`)
      changedKeys.push(`group:${definition.key}`)
    }
  }
  for (const existing of currentGroupRows.filter((row) => row.origin === 'code' && lifecycleBool(row.active) && !nextGroups.has(row.key))) {
    await tx.execute(sql`UPDATE field_group_definitions SET version=${Number(existing.version)+1},active=false,updated_at=${new Date().toISOString()} WHERE key=${existing.key} AND version=${Number(existing.version)}`)
    changedKeys.push(`group:${existing.key}`)
  }

  const currentOptionRows = await optionsModule.__optionsLifecycleInternals.readOptionRows(tx)
  for (const code of next.optionsPages) {
    const definition = optionsModule.__optionsLifecycleInternals.normalizeOptionsPage(code), canonicalHash = await fieldsRequestHash(definition), existing = currentOptionRows.find((row) => row.key === definition.key), now = new Date().toISOString()
    if (!existing) {
      await tx.execute(sql`INSERT INTO field_options_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES (${definition.key},'code',1,true,1,${canonicalHash},${JSON.stringify(definition)},${now},${now})`)
      await tx.execute(sql`INSERT INTO field_options_definition_versions (options_key,revision,canonical_hash,definition,origin,created_at) VALUES (${definition.key},1,${canonicalHash},${JSON.stringify(definition)},'code',${now})`)
      changedKeys.push(`options:${definition.key}`)
    } else if (existing.origin !== 'code' || existing.canonicalHash !== canonicalHash || !lifecycleBool(existing.active)) {
      const version = Number(existing.version) + 1, revisionChanged = existing.origin !== 'code' || existing.canonicalHash !== canonicalHash, revision = Number(existing.currentRevision) + (revisionChanged ? 1 : 0)
      if (revisionChanged) await tx.execute(sql`INSERT INTO field_options_definition_versions (options_key,revision,canonical_hash,definition,origin,created_at) VALUES (${definition.key},${revision},${canonicalHash},${JSON.stringify(definition)},'code',${now})`)
      const shadowed = existing.origin === 'db' ? Number(existing.version) : existing.shadowedDbVersion
      await tx.execute(sql`UPDATE field_options_definitions SET origin='code',version=${version},active=true,current_revision=${revision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=${shadowed},updated_at=${now} WHERE key=${definition.key}`)
      changedKeys.push(`options:${definition.key}`)
    }
  }
  for (const existing of currentOptionRows.filter((row) => row.origin === 'code' && lifecycleBool(row.active) && !nextOptions.has(row.key))) {
    await tx.execute(sql`UPDATE field_options_definitions SET version=${Number(existing.version)+1},active=false,updated_at=${new Date().toISOString()} WHERE key=${existing.key} AND version=${Number(existing.version)}`)
    changedKeys.push(`options:${existing.key}`)
  }

  const currentBlockRows = await blocksModule.__fieldBlockLifecycleInternals.readCurrentRows(tx)
  for (const code of next.blocks) {
    const definition = blocksModule.__fieldBlockLifecycleInternals.normalizeDefinition(code), canonicalHash = await fieldsRequestHash(definition), existing = currentBlockRows.find((row) => row.key === definition.key), now = new Date().toISOString()
    if (!existing) {
      await tx.execute(sql`INSERT INTO field_block_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES (${definition.key},'code',1,true,1,${canonicalHash},${JSON.stringify(definition)},${now},${now})`)
      await tx.execute(sql`INSERT INTO field_block_definition_versions (block_key,revision,canonical_hash,definition,origin,created_at) VALUES (${definition.key},1,${canonicalHash},${JSON.stringify(definition)},'code',${now})`)
      changedKeys.push(`block:${definition.key}`)
    } else if (existing.origin !== 'code' || existing.canonicalHash !== canonicalHash || !lifecycleBool(existing.active)) {
      const version = Number(existing.version) + 1, revisionChanged = existing.origin !== 'code' || existing.canonicalHash !== canonicalHash, revision = Number(existing.currentRevision) + (revisionChanged ? 1 : 0)
      if (revisionChanged) await tx.execute(sql`INSERT INTO field_block_definition_versions (block_key,revision,canonical_hash,definition,origin,created_at) VALUES (${definition.key},${revision},${canonicalHash},${JSON.stringify(definition)},'code',${now})`)
      const shadowed = existing.origin === 'db' ? Number(existing.version) : existing.shadowedDbVersion
      await tx.execute(sql`UPDATE field_block_definitions SET origin='code',version=${version},active=true,current_revision=${revision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=${shadowed},updated_at=${now} WHERE key=${definition.key}`)
      changedKeys.push(`block:${definition.key}`)
    }
  }
  for (const existing of currentBlockRows.filter((row) => row.origin === 'code' && lifecycleBool(row.active) && !nextBlocks.has(row.key))) {
    await tx.execute(sql`UPDATE field_block_definitions SET version=${Number(existing.version)+1},active=false,updated_at=${new Date().toISOString()} WHERE key=${existing.key} AND version=${Number(existing.version)}`)
    changedKeys.push(`block:${existing.key}`)
  }

  await writeFieldsReceipt(tx, { operationId: input.operationId, kind: 'fields:reconcile-code', context: authorized, requestHash, result: freezeStorage(next, 'result') })
  const event: FieldsDefinitionEvent = Object.freeze({
    id: crypto.randomUUID(), version: 1, kind: 'codeRegistryReconciled', operationId: input.operationId,
    principalId: authorized.principal.id, fromVersion: plan.fromVersion, toVersion: next.version,
    changedKeys: Object.freeze([...new Set(changedKeys)].sort()),
    strategiesHash: await fieldsRequestHash({ groupStrategies: input.groupStrategies, optionsStrategies: input.optionsStrategies, blockStrategies: input.blockStrategies }),
    corpusHashes: Object.freeze([...new Set(corpusHashes)].sort()), affectedCounts: Object.freeze(affectedCounts),
  })
  await effects.audit(tx, event)
  await effects.enqueue(tx, event)
  return next
}

export interface FieldsBackupGroupDefinitionSnapshot { readonly key: string; readonly definition: FieldGroup }
export interface FieldsBackupOptionsDefinitionSnapshot { readonly key: string; readonly definition: Omit<CodeOptionsPage, 'origin'>; readonly active: boolean }
export interface FieldsBackupBlockDefinitionSnapshot { readonly key: string; readonly definition: Omit<CodeFieldBlock, 'origin'>; readonly active: boolean }
export interface FieldsBackupValueSnapshot {
  readonly ref: EntityRef
  readonly groupKey: string
  readonly definitionRevision: number
  readonly values: FieldStorageValue
}
export interface FieldsBackupBlockDocumentSnapshot {
  readonly ref: EntityRef
  readonly document: FieldStorageValue
  readonly definitionVersions: Readonly<Record<string, number>>
  readonly parentRevisionId: string
}
export interface FieldsBackupReusableBlockSnapshot {
  readonly id: string
  readonly document: FieldStorageValue
  readonly definitionVersions: Readonly<Record<string, number>>
}
export interface FieldsBackupRevisionSnapshot {
  readonly id: string
  readonly ref: EntityRef
  readonly parentRevisionId: string
  readonly parentAutosaveId?: string
  readonly kind: 'revision' | 'autosave'
  readonly retentionClass?: string
  readonly definitionVersions: Readonly<Record<string, number>>
  readonly values: FieldStorageValue
  readonly blockDocument?: FieldStorageValue
  readonly createdAt: string
  readonly createdBy: string
  readonly sourceVersion: string
}
export interface FieldsBackupPayload {
  readonly version: 1
  readonly groupDefinitions?: readonly FieldsBackupGroupDefinitionSnapshot[]
  readonly optionsDefinitions?: readonly FieldsBackupOptionsDefinitionSnapshot[]
  readonly blockDefinitions?: readonly FieldsBackupBlockDefinitionSnapshot[]
  readonly values?: readonly FieldsBackupValueSnapshot[]
  readonly blockDocuments?: readonly FieldsBackupBlockDocumentSnapshot[]
  readonly reusableBlocks?: readonly FieldsBackupReusableBlockSnapshot[]
  readonly revisions?: readonly FieldsBackupRevisionSnapshot[]
}

function backupString(value: unknown, field: string, max = 512): string {
  if (typeof value !== 'string' || value.length === 0 || value.length > max) throw new FieldsLifecycleError('backup-invalid', `${field} must be a bounded non-empty string`)
  return value
}
function backupRef(value: unknown, field: string): EntityRef {
  const record = lifecycleRecord(value, field)
  return Object.freeze({ entityType: backupString(record.entityType, `${field}.entityType`, 256), entityId: backupString(record.entityId, `${field}.entityId`, 512) })
}
function backupVersions(value: unknown, field: string): Readonly<Record<string, number>> {
  const record = lifecycleRecord(value, field), output: Record<string, number> = {}
  for (const [key, raw] of Object.entries(record)) { const revision = Number(raw); if (!Number.isSafeInteger(revision) || revision < 1) throw new FieldsLifecycleError('backup-invalid', `${field}.${key} must be a positive revision`); output[key] = revision }
  return Object.freeze(output)
}
function backupArray(value: unknown, field: string, max = 5000): readonly unknown[] {
  if (value === undefined) return Object.freeze([])
  if (!Array.isArray(value) || value.length > max) throw new FieldsLifecycleError('backup-invalid', `${field} must be a bounded array`)
  return value
}
async function parseFieldsBackupPayload(value: FieldStorageValue): Promise<FieldsBackupPayload> {
  const root = lifecycleRecord(value, 'backup payload')
  if (root.version !== 1) throw new FieldsLifecycleError('backup-invalid', 'backup payload version must be 1')
  const groups: FieldsBackupGroupDefinitionSnapshot[] = [], groupKeys = new Set<string>()
  for (const [index, raw] of backupArray(root.groupDefinitions, 'groupDefinitions').entries()) {
    const item = lifecycleRecord(raw, `groupDefinitions.${index}`), definition = parseFieldGroupDefinition(item.definition), key = backupString(item.key, `groupDefinitions.${index}.key`, 128)
    if (definition.key !== key || groupKeys.has(key)) throw new FieldsLifecycleError('backup-invalid', 'backup group definition identity is invalid')
    groupKeys.add(key); groups.push(Object.freeze({ key, definition }))
  }
  const optionsModule = await import('./options.js'), options: FieldsBackupOptionsDefinitionSnapshot[] = [], optionKeys = new Set<string>()
  for (const [index, raw] of backupArray(root.optionsDefinitions, 'optionsDefinitions').entries()) {
    const item = lifecycleRecord(raw, `optionsDefinitions.${index}`), key = backupString(item.key, `optionsDefinitions.${index}.key`, 128)
    const definition = optionsModule.__optionsLifecycleInternals.normalizeOptionsPage(item.definition as never)
    if (definition.key !== key || optionKeys.has(key) || typeof item.active !== 'boolean') throw new FieldsLifecycleError('backup-invalid', 'backup options definition identity is invalid')
    optionKeys.add(key); options.push(Object.freeze({ key, definition, active: item.active }))
  }
  const blocksModule = await import('./blocks.js'), blocks: FieldsBackupBlockDefinitionSnapshot[] = [], blockKeys = new Set<string>()
  for (const [index, raw] of backupArray(root.blockDefinitions, 'blockDefinitions').entries()) {
    const item = lifecycleRecord(raw, `blockDefinitions.${index}`), key = backupString(item.key, `blockDefinitions.${index}.key`, 128)
    const definition = blocksModule.__fieldBlockLifecycleInternals.normalizeDefinition(item.definition as never)
    if (definition.key !== key || blockKeys.has(key) || typeof item.active !== 'boolean') throw new FieldsLifecycleError('backup-invalid', 'backup block definition identity is invalid')
    blockKeys.add(key); blocks.push(Object.freeze({ key, definition, active: item.active }))
  }
  const values: FieldsBackupValueSnapshot[] = [], valueKeys = new Set<string>()
  for (const [index, raw] of backupArray(root.values, 'values', 20_000).entries()) {
    const item = lifecycleRecord(raw, `values.${index}`), ref = backupRef(item.ref, `values.${index}.ref`), groupKey = normalizedGroupKey(item.groupKey), definitionRevision = Number(item.definitionRevision)
    if (!Number.isSafeInteger(definitionRevision) || definitionRevision < 1) throw new FieldsLifecycleError('backup-invalid', 'backup value definition revision is invalid')
    const identity = `${ref.entityType}\u0000${ref.entityId}\u0000${groupKey}`
    if (valueKeys.has(identity)) throw new FieldsLifecycleError('backup-invalid', 'backup contains duplicate value entity/group snapshot')
    valueKeys.add(identity); values.push(Object.freeze({ ref, groupKey, definitionRevision, values: freezeStorage(item.values, `values.${index}.values`) }))
  }
  const blockDocuments: FieldsBackupBlockDocumentSnapshot[] = [], documentKeys = new Set<string>()
  for (const [index, raw] of backupArray(root.blockDocuments, 'blockDocuments', 10_000).entries()) {
    const item = lifecycleRecord(raw, `blockDocuments.${index}`), ref = backupRef(item.ref, `blockDocuments.${index}.ref`), identity = `${ref.entityType}\u0000${ref.entityId}`
    if (documentKeys.has(identity)) throw new FieldsLifecycleError('backup-invalid', 'backup contains duplicate block document snapshot')
    documentKeys.add(identity); blockDocuments.push(Object.freeze({ ref, document: freezeStorage(item.document, `blockDocuments.${index}.document`), definitionVersions: backupVersions(item.definitionVersions, `blockDocuments.${index}.definitionVersions`), parentRevisionId: backupString(item.parentRevisionId, `blockDocuments.${index}.parentRevisionId`) }))
  }
  const reusableBlocks: FieldsBackupReusableBlockSnapshot[] = [], reusableIds = new Set<string>()
  for (const [index, raw] of backupArray(root.reusableBlocks, 'reusableBlocks', 10_000).entries()) {
    const item = lifecycleRecord(raw, `reusableBlocks.${index}`), id = backupString(item.id, `reusableBlocks.${index}.id`)
    if (reusableIds.has(id)) throw new FieldsLifecycleError('backup-invalid', 'backup contains duplicate reusable block')
    reusableIds.add(id); reusableBlocks.push(Object.freeze({ id, document: freezeStorage(item.document, `reusableBlocks.${index}.document`), definitionVersions: backupVersions(item.definitionVersions, `reusableBlocks.${index}.definitionVersions`) }))
  }
  const revisions: FieldsBackupRevisionSnapshot[] = [], revisionIds = new Set<string>()
  for (const [index, raw] of backupArray(root.revisions, 'revisions', 20_000).entries()) {
    const item = lifecycleRecord(raw, `revisions.${index}`), id = backupString(item.id, `revisions.${index}.id`), ref = backupRef(item.ref, `revisions.${index}.ref`)
    if (revisionIds.has(id) || (item.kind !== 'revision' && item.kind !== 'autosave')) throw new FieldsLifecycleError('backup-invalid', 'backup revision identity is invalid')
    revisionIds.add(id)
    const createdAt = backupString(item.createdAt, `revisions.${index}.createdAt`), date = new Date(createdAt)
    if (Number.isNaN(date.getTime())) throw new FieldsLifecycleError('backup-invalid', 'backup revision timestamp is invalid')
    revisions.push(Object.freeze({ id, ref, parentRevisionId: backupString(item.parentRevisionId, `revisions.${index}.parentRevisionId`), ...(item.parentAutosaveId === undefined ? {} : { parentAutosaveId: backupString(item.parentAutosaveId, `revisions.${index}.parentAutosaveId`) }), kind: item.kind, ...(item.retentionClass === undefined ? {} : { retentionClass: backupString(item.retentionClass, `revisions.${index}.retentionClass`, 128) }), definitionVersions: backupVersions(item.definitionVersions, `revisions.${index}.definitionVersions`), values: freezeStorage(item.values, `revisions.${index}.values`), ...(item.blockDocument === undefined || item.blockDocument === null ? {} : { blockDocument: freezeStorage(item.blockDocument, `revisions.${index}.blockDocument`) }), createdAt: date.toISOString(), createdBy: backupString(item.createdBy, `revisions.${index}.createdBy`), sourceVersion: backupString(item.sourceVersion, `revisions.${index}.sourceVersion`) }))
  }
  return Object.freeze({ version: 1, ...(groups.length ? { groupDefinitions: Object.freeze(groups) } : {}), ...(options.length ? { optionsDefinitions: Object.freeze(options) } : {}), ...(blocks.length ? { blockDefinitions: Object.freeze(blocks) } : {}), ...(values.length ? { values: Object.freeze(values) } : {}), ...(blockDocuments.length ? { blockDocuments: Object.freeze(blockDocuments) } : {}), ...(reusableBlocks.length ? { reusableBlocks: Object.freeze(reusableBlocks) } : {}), ...(revisions.length ? { revisions: Object.freeze(revisions) } : {}) })
}

function restoreTargetKey(ref: EntityRef, groupKey?: string): string {
  return `${ref.entityType}\u0000${ref.entityId}${groupKey === undefined ? '' : `\u0000${groupKey}`}`
}
async function fieldsBackupDestinationState(db: FieldsLifecycleReader, payload: FieldsBackupPayload): Promise<FieldStorageValue> {
  const groups: FieldStorageValue[] = []
  for (const item of [...(payload.groupDefinitions ?? [])].sort((a,b)=>a.key.localeCompare(b.key))) {
    const row = await finalGroupRow(db, item.key)
    groups.push(freezeStorage(row ? { key: row.key, origin: row.origin, version: Number(row.version), active: lifecycleBool(row.active), revision: Number(row.currentRevision), hash: row.canonicalHash } : { key: item.key, missing: true }, 'destination.group'))
  }
  const options: FieldStorageValue[] = []
  for (const item of [...(payload.optionsDefinitions ?? [])].sort((a,b)=>a.key.localeCompare(b.key))) {
    const rows = await lifecycleExecute<Record<string,unknown>>(db, sql`SELECT key,origin,version,active,current_revision AS revision,canonical_hash AS hash FROM field_options_definitions WHERE key=${item.key} LIMIT 1`)
    const row=rows[0]
    options.push(freezeStorage(row ? { key: row.key, origin: row.origin, version: Number(row.version), active: lifecycleBool(row.active), revision: Number(row.revision), hash: row.hash } : { key: item.key, missing: true }, 'destination.options'))
  }
  const blocks: FieldStorageValue[] = []
  for (const item of [...(payload.blockDefinitions ?? [])].sort((a,b)=>a.key.localeCompare(b.key))) {
    const rows = await lifecycleExecute<Record<string,unknown>>(db, sql`SELECT key,origin,version,active,current_revision AS revision,canonical_hash AS hash FROM field_block_definitions WHERE key=${item.key} LIMIT 1`)
    const row=rows[0]
    blocks.push(freezeStorage(row ? { key: row.key, origin: row.origin, version: Number(row.version), active: lifecycleBool(row.active), revision: Number(row.revision), hash: row.hash } : { key: item.key, missing: true }, 'destination.blocks'))
  }
  const values: FieldStorageValue[] = []
  for (const item of [...(payload.values ?? [])].sort((a,b)=>restoreTargetKey(a.ref,a.groupKey).localeCompare(restoreTargetKey(b.ref,b.groupKey)))) {
    const rows = await lifecycleExecute<Record<string,unknown>>(db, sql`SELECT node_id AS "nodeId",definition_revision AS "definitionRevision",field_key AS "fieldKey",path,parent_node_id AS "parentNodeId",row_id AS "rowId",layout_key AS "layoutKey",node_kind AS "nodeKind",ordinal,is_null AS "isNull",value_text AS "valueText",value_number AS "valueNumber",value_boolean AS "valueBoolean",value_date_time AS "valueDateTime",value_ref AS "valueRef",value_json AS "valueJson" FROM field_value_nodes WHERE entity_type=${item.ref.entityType} AND entity_id=${item.ref.entityId} AND group_key=${item.groupKey} ORDER BY path`)
    values.push(freezeStorage({ ref:item.ref, groupKey:item.groupKey, rows }, 'destination.values'))
  }
  const documents: FieldStorageValue[] = []
  for (const item of [...(payload.blockDocuments ?? [])].sort((a,b)=>restoreTargetKey(a.ref).localeCompare(restoreTargetKey(b.ref)))) {
    const rows = await lifecycleExecute<Record<string,unknown>>(db, sql`SELECT version,document,definition_versions AS "definitionVersions",parent_revision_id AS "parentRevisionId" FROM field_block_documents WHERE entity_type=${item.ref.entityType} AND entity_id=${item.ref.entityId} LIMIT 1`)
    documents.push(freezeStorage({ ref:item.ref, current: rows[0] ?? null }, 'destination.documents'))
  }
  const reusable: FieldStorageValue[] = []
  for (const item of [...(payload.reusableBlocks ?? [])].sort((a,b)=>a.id.localeCompare(b.id))) {
    const rows = await lifecycleExecute<Record<string,unknown>>(db, sql`SELECT id,version,document,definition_versions AS "definitionVersions",deleted_at AS "deletedAt" FROM field_reusable_blocks WHERE id=${item.id} LIMIT 1`)
    reusable.push(freezeStorage({ id:item.id, current: rows[0] ?? null }, 'destination.reusable'))
  }
  const revisions: FieldStorageValue[] = []
  for (const item of [...(payload.revisions ?? [])].sort((a,b)=>a.id.localeCompare(b.id))) {
    const rows = await lifecycleExecute<Record<string,unknown>>(db, sql`SELECT id,entity_type AS "entityType",entity_id AS "entityId",kind,parent_revision_id AS "parentRevisionId",parent_autosave_id AS "parentAutosaveId",definition_versions AS "definitionVersions",values,block_document AS "blockDocument",source_version AS "sourceVersion" FROM field_revisions WHERE id=${item.id} LIMIT 1`)
    revisions.push(freezeStorage({ id:item.id, current: rows[0] ?? null }, 'destination.revisions'))
  }
  return Object.freeze({ version:1, groups:Object.freeze(groups), options:Object.freeze(options), blocks:Object.freeze(blocks), values:Object.freeze(values), documents:Object.freeze(documents), reusable:Object.freeze(reusable), revisions:Object.freeze(revisions) })
}
export async function fieldsBackupDestinationHash(db: FieldsLifecycleReader, payload: FieldsBackupPayload): Promise<string> {
  return fieldsRequestHash(await fieldsBackupDestinationState(db,payload))
}
function manifestsEqual(left: FieldsBackupManifest, right: FieldsBackupManifest): boolean {
  return left.id===right.id&&left.scopeHash===right.scopeHash&&left.corpusVersion===right.corpusVersion&&left.corpusHash===right.corpusHash&&left.byteCount===right.byteCount&&left.immutable===true&&right.immutable===true&&JSON.stringify(Object.entries(left.itemCounts).sort())===JSON.stringify(Object.entries(right.itemCounts).sort())
}
function remapDefinitionVersions(value: Readonly<Record<string,number>>, groupRevisions: ReadonlyMap<string,number>, blockRevisions: ReadonlyMap<string,number>): Readonly<Record<string,number>> {
  const out: Record<string,number>={...value}
  for(const [key,revision] of groupRevisions){if(Object.prototype.hasOwnProperty.call(out,key))out[key]=revision;if(Object.prototype.hasOwnProperty.call(out,`group:${key}`))out[`group:${key}`]=revision}
  for(const [key,revision] of blockRevisions)if(Object.prototype.hasOwnProperty.call(out,`block:${key}`))out[`block:${key}`]=revision
  return Object.freeze(out)
}
function remapBlockDocument(value: unknown, groupRevisions: ReadonlyMap<string,number>, blockRevisions: ReadonlyMap<string,number>): unknown {
  const walk=(item:unknown):unknown=>{
    if(Array.isArray(item))return item.map(walk)
    if(typeof item!=='object'||item===null)return item
    const record=item as Record<string,unknown>,out:Record<string,unknown>={}
    for(const [key,child] of Object.entries(record))out[key]=walk(child)
    if(record.kind==='inline'&&typeof record.type==='string'&&blockRevisions.has(record.type))out.blockDefinitionRevision=blockRevisions.get(record.type)!
    if(typeof record.groupKey==='string'&&groupRevisions.has(record.groupKey))out.definitionRevision=groupRevisions.get(record.groupKey)!
    return out
  }
  return walk(value)
}

function fieldsContainPrivateCredential(fields: readonly AnyFieldDefinition[], context: FieldsContext): boolean {
  for (const field of fields) {
    if (field.type === 'password') return true
    if (field.type === 'group' || field.type === 'repeater') {
      if (fieldsContainPrivateCredential((field.settings as { fields: readonly AnyFieldDefinition[] }).fields, context)) return true
    }
    if (field.type === 'flexible') {
      for (const layout of (field.settings as { layouts: readonly { fields: readonly AnyFieldDefinition[] }[] }).layouts) {
        if (fieldsContainPrivateCredential(layout.fields, context)) return true
      }
    }
    if (field.type === 'clone') {
      if (!context.cloneResolver) return true
      if (fieldsContainPrivateCredential(context.cloneResolver.resolve(field), context)) return true
    }
  }
  return false
}
function backupPayloadEntityTypes(payload: FieldsBackupPayload): readonly string[] {
  const types = new Set<string>()
  for (const item of payload.values ?? []) types.add(item.ref.entityType)
  for (const item of payload.blockDocuments ?? []) types.add(item.ref.entityType)
  for (const item of payload.revisions ?? []) types.add(item.ref.entityType)
  if ((payload.optionsDefinitions?.length ?? 0) > 0) types.add('options')
  if ((payload.reusableBlocks?.length ?? 0) > 0) types.add('reusableBlock')
  if (types.size === 0) types.add('fields-definition')
  return Object.freeze([...types].sort())
}
function restoredPayloadCounts(payload: FieldsBackupPayload): Readonly<Record<string, number>> {
  return Object.freeze({
    groupDefinitions: payload.groupDefinitions?.length ?? 0,
    optionsDefinitions: payload.optionsDefinitions?.length ?? 0,
    blockDefinitions: payload.blockDefinitions?.length ?? 0,
    values: payload.values?.length ?? 0,
    blockDocuments: payload.blockDocuments?.length ?? 0,
    reusableBlocks: payload.reusableBlocks?.length ?? 0,
    revisions: payload.revisions?.length ?? 0,
  })
}
async function writeRestoredValueSnapshot(
  tx: FieldsTransaction,
  item: FieldsBackupValueSnapshot,
  definition: FieldGroup,
  definitionRevision: number,
  context: FieldsContext,
): Promise<void> {
  if (fieldsContainPrivateCredential(definition.fields, context)) {
    throw new FieldsLifecycleError('capability-unavailable', 'restoring current password-bearing values requires a private credential restore adapter')
  }
  const blocksModule = await import('./blocks.js')
  const values = await blocksModule.__fieldBlockLifecycleInternals.validateBlockFieldMap(definition.fields, item.values, context, `backup.values.${definition.key}`)
  const ids = new Map<string,string>()
  const rows = encodeFieldValueNodes(definition, values, {
    entityType: item.ref.entityType,
    entityId: item.ref.entityId,
    definitionRevision,
    nodeIdForPath: (path) => { let id=ids.get(path); if(!id){id=crypto.randomUUID();ids.set(path,id)} return id },
    ...(context.cloneResolver ? { cloneResolver: { resolve: (field) => context.cloneResolver!.resolve(field) } } : {}),
  })
  await tx.execute(sql`DELETE FROM field_value_nodes WHERE entity_type=${item.ref.entityType} AND entity_id=${item.ref.entityId} AND group_key=${item.groupKey}`)
  for (const row of rows) await tx.execute(sql`
    INSERT INTO field_value_nodes
      (node_id,entity_type,entity_id,group_key,definition_revision,field_key,path,parent_node_id,row_id,layout_key,node_kind,ordinal,is_null,value_text,value_number,value_boolean,value_date_time,value_ref,value_json,created_at,updated_at)
    VALUES
      (${row.nodeId},${row.entityType},${row.entityId},${row.groupKey},${row.definitionRevision},${row.fieldKey},${row.path},${row.parentNodeId},${row.rowId},${row.layoutKey},${row.nodeKind},${row.ordinal},${row.isNull},${row.valueText},${row.valueNumber},${row.valueBoolean},${row.valueDateTime},${row.valueRef},${row.valueJson},${new Date().toISOString()},${new Date().toISOString()})
  `)
}
async function requireRestoredDefinitionVersions(
  db: FieldsLifecycleReader,
  versions: Readonly<Record<string,number>>,
): Promise<void> {
  const blocksModule = await import('./blocks.js')
  for (const [identity, revision] of Object.entries(versions)) {
    if (identity.startsWith('block:')) {
      if (!await blocksModule.__fieldBlockLifecycleInternals.readBlockVersion(db, identity.slice(6), revision)) throw new FieldsLifecycleError('backup-invalid', `restored snapshot references missing block definition ${identity}@${revision}`)
    } else {
      const groupKey = identity.startsWith('group:') ? identity.slice(6) : identity
      try { await resolveFieldDefinitionVersion(db, groupKey, revision) }
      catch { throw new FieldsLifecycleError('backup-invalid', `restored snapshot references missing field definition ${groupKey}@${revision}`) }
    }
  }
}

export async function restoreFieldsBackup(
  tx: FieldsTransaction,
  manifest: FieldsBackupManifest,
  destinationCorpusHash: string,
  operationId: string,
  context: FieldsContext,
  effects: FieldsDestructiveEffects,
  backups: FieldsBackupStore,
): Promise<FieldsBackupRestoreResult> {
  assertActiveFieldsTransaction(tx)
  const authorized = await assertFieldsAuthorized(context, 'manageDefinitions', { entityType: 'fields-definition', entityId: manifest.id })
  let verified: FieldsBackupManifest
  try {
    verified = await backups.verify(tx, manifest.id, manifest.scopeHash, manifest.corpusVersion, manifest.corpusHash, manifest.itemCounts)
  } catch {
    throw new FieldsLifecycleError('backup-invalid', 'backup manifest verification failed')
  }
  if (!manifestsEqual(manifest, verified)) throw new FieldsLifecycleError('backup-invalid', 'backup manifest identity changed')
  let snapshot: FieldsBackupSnapshot
  try { snapshot = await backups.read(tx, verified) }
  catch { throw new FieldsLifecycleError('backup-invalid', 'backup payload could not be read') }
  if (!snapshot || !manifestsEqual(snapshot.manifest, verified)) throw new FieldsLifecycleError('backup-invalid', 'backup payload manifest does not match verified manifest')
  const payload = await parseFieldsBackupPayload(snapshot.payload)
  const entityTypes = backupPayloadEntityTypes(payload)
  const migration = await assertDefinitionMigration(context, entityTypes)
  const requestHash = await fieldsRequestHash({ manifestId: manifest.id, destinationCorpusHash })
  const replay = await readFieldsReceipt(tx, operationId)
  if (replay) {
    assertReceiptReplay(replay, { kind: 'fields:restore-backup', context: authorized, requestHash })
    const result = lifecycleRecord(replay.result, 'backup restore receipt')
    if (result.operationId !== operationId || result.manifestId !== manifest.id || result.destinationCorpusHash !== destinationCorpusHash) throw new FieldsLifecycleError('integrity', 'backup restore receipt is malformed')
    const restoredCounts = lifecycleRecord(result.restoredCounts, 'backup restore counts'), counts: Record<string,number> = {}
    for (const [key,value] of Object.entries(restoredCounts)) { const number=Number(value); if(!Number.isSafeInteger(number)||number<0) throw new FieldsLifecycleError('integrity','backup restore receipt count is invalid'); counts[key]=number }
    return Object.freeze({ operationId, manifestId: manifest.id, destinationCorpusHash, restoredCounts: Object.freeze(counts) })
  }
  const actualDestinationHash = await fieldsBackupDestinationHash(tx,payload)
  if (actualDestinationHash !== destinationCorpusHash) throw new FieldsLifecycleError('corpus-drift', 'backup restore destination changed')

  // Authorize every parent entity before the first state mutation.
  const refs = new Map<string,EntityRef>()
  for (const item of payload.values ?? []) refs.set(restoreTargetKey(item.ref),item.ref)
  for (const item of payload.blockDocuments ?? []) refs.set(restoreTargetKey(item.ref),item.ref)
  for (const item of payload.revisions ?? []) refs.set(restoreTargetKey(item.ref),item.ref)
  for (const ref of refs.values()) await assertFieldsAuthorized(context, 'write', ref)
  for (const item of payload.reusableBlocks ?? []) await assertFieldsAuthorized(context,'write',{entityType:'reusableBlock',entityId:item.id})

  // Validate dependency identities before mutation using the destination plus payload definitions.
  const currentGroupRows = await finalGroupRows(tx)
  const availableGroups = new Set(currentGroupRows.filter((row)=>lifecycleBool(row.active)).map((row)=>row.key))
  for (const item of payload.groupDefinitions ?? []) availableGroups.add(item.key)
  const currentBlockRows = await lifecycleExecute<Record<string,unknown>>(tx,sql`SELECT key,active FROM field_block_definitions`)
  const availableBlocks = new Set(currentBlockRows.filter((row)=>lifecycleBool(row.active)).map((row)=>String(row.key)))
  for (const item of payload.blockDefinitions ?? []) availableBlocks.add(item.key)
  for (const item of payload.blockDefinitions ?? []) {
    for (const groupKey of item.definition.fieldGroupKeys) if(!availableGroups.has(groupKey)) throw new FieldsLifecycleError('backup-invalid',`backup block ${item.key} references missing field group ${groupKey}`)
    for (const parent of item.definition.parent ?? []) if(!availableBlocks.has(parent)) throw new FieldsLifecycleError('backup-invalid',`backup block ${item.key} references missing parent ${parent}`)
    for (const ancestor of item.definition.ancestor ?? []) if(!availableBlocks.has(ancestor)) throw new FieldsLifecycleError('backup-invalid',`backup block ${item.key} references missing ancestor ${ancestor}`)
  }
  const currentOptionsRows = await lifecycleExecute<Record<string,unknown>>(tx,sql`SELECT key FROM field_options_definitions WHERE active=true`), availableOptions = new Set(currentOptionsRows.map((row)=>String(row.key)))
  for (const item of payload.optionsDefinitions ?? []) availableOptions.add(item.key)
  for (const item of payload.optionsDefinitions ?? []) if(item.definition.parentKey&&!availableOptions.has(item.definition.parentKey)) throw new FieldsLifecycleError('backup-invalid',`backup options page ${item.key} references missing parent ${item.definition.parentKey}`)

  const groupRevisions = new Map<string,number>(), blockRevisions = new Map<string,number>()
  for (const item of payload.groupDefinitions ?? []) {
    const existing=await finalGroupRow(tx,item.key),canonicalHash=await canonicalFieldGroupHash(item.definition),revision=existing?Number(existing.currentRevision)+1:1,version=existing?Number(existing.version)+1:1,now=new Date().toISOString()
    await tx.execute(sql`INSERT INTO field_definition_versions (group_key,revision,canonical_hash,definition,origin,created_at) VALUES (${item.key},${revision},${canonicalHash},${JSON.stringify(item.definition)},'db',${now})`)
    if(existing) await tx.execute(sql`UPDATE field_group_definitions SET origin='db',version=${version},active=${item.definition.active},current_revision=${revision},canonical_hash=${canonicalHash},definition=${JSON.stringify(item.definition)},shadowed_db_version=NULL,updated_at=${now} WHERE key=${item.key}`)
    else await tx.execute(sql`INSERT INTO field_group_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES (${item.key},'db',1,${item.definition.active},1,${canonicalHash},${JSON.stringify(item.definition)},${now},${now})`)
    groupRevisions.set(item.key,revision)
  }

  const optionsModule = await import('./options.js')
  for (const item of payload.optionsDefinitions ?? []) {
    const definition=optionsModule.__optionsLifecycleInternals.normalizeOptionsPage(item.definition),canonicalHash=await fieldsRequestHash(definition),current=await optionsModule.__optionsLifecycleInternals.readDbPage(tx,item.key),revision=current?Number(current.row.currentRevision)+1:1,version=current?Number(current.row.version)+1:1,now=new Date().toISOString()
    await tx.execute(sql`INSERT INTO field_options_definition_versions (options_key,revision,canonical_hash,definition,origin,created_at) VALUES (${item.key},${revision},${canonicalHash},${JSON.stringify(definition)},'db',${now})`)
    if(current) await tx.execute(sql`UPDATE field_options_definitions SET origin='db',version=${version},active=${item.active},current_revision=${revision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=NULL,updated_at=${now} WHERE key=${item.key}`)
    else await tx.execute(sql`INSERT INTO field_options_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES (${item.key},'db',1,${item.active},1,${canonicalHash},${JSON.stringify(definition)},${now},${now})`)
  }

  const blocksModule = await import('./blocks.js')
  for (const item of payload.blockDefinitions ?? []) {
    const definition=blocksModule.__fieldBlockLifecycleInternals.normalizeDefinition(item.definition),canonicalHash=await fieldsRequestHash(definition),current=await blocksModule.__fieldBlockLifecycleInternals.readCurrentBlock(tx,item.key),revision=current?Number(current.row.currentRevision)+1:1,version=current?Number(current.row.version)+1:1,now=new Date().toISOString()
    await tx.execute(sql`INSERT INTO field_block_definition_versions (block_key,revision,canonical_hash,definition,origin,created_at) VALUES (${item.key},${revision},${canonicalHash},${JSON.stringify(definition)},'db',${now})`)
    if(current) await tx.execute(sql`UPDATE field_block_definitions SET origin='db',version=${version},active=${item.active},current_revision=${revision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=NULL,updated_at=${now} WHERE key=${item.key}`)
    else await tx.execute(sql`INSERT INTO field_block_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES (${item.key},'db',1,${item.active},1,${canonicalHash},${JSON.stringify(definition)},${now},${now})`)
    blockRevisions.set(item.key,revision)
  }

  for (const item of payload.values ?? []) {
    const restoredRevision = groupRevisions.get(item.groupKey) ?? item.definitionRevision
    const definition = groupRevisions.has(item.groupKey)
      ? (payload.groupDefinitions ?? []).find((candidate)=>candidate.key===item.groupKey)?.definition
      : (await resolveFieldDefinitionVersion(tx,item.groupKey,item.definitionRevision)).definition
    if(!definition) throw new FieldsLifecycleError('backup-invalid',`backup value references missing field group ${item.groupKey}`)
    await writeRestoredValueSnapshot(tx,item,definition,restoredRevision,context)
  }

  const remappedReusable = new Map<string,{document: import('./blocks.js').BlockDocument;version:number}>()
  for (const item of payload.reusableBlocks ?? []) {
    const document = remapBlockDocument(item.document,groupRevisions,blockRevisions) as import('./blocks.js').BlockDocument
    remappedReusable.set(item.id,{document,version:1})
  }
  const persistedReusableResolver=blocksModule.createReusableBlockResolver(tx)
  const reusableResolver: import('./blocks.js').ReusableBlockResolver={resolve:async(id:string,ctx:FieldsContext)=>remappedReusable.get(id)??persistedReusableResolver.resolve(id,ctx)}
  const registry=await blocksModule.resolveFieldBlocks(tx)

  for (const item of payload.blockDocuments ?? []) {
    const document=remapBlockDocument(item.document,groupRevisions,blockRevisions) as import('./blocks.js').BlockDocument
    const validated=await blocksModule.validateBlockDocument(document,registry,reusableResolver,context,blocksModule.DEFAULT_BLOCK_POLICY)
    const versions=remapDefinitionVersions(item.definitionVersions,groupRevisions,blockRevisions)
    const current=await lifecycleExecute<Record<string,unknown>>(tx,sql`SELECT version FROM field_block_documents WHERE entity_type=${item.ref.entityType} AND entity_id=${item.ref.entityId} LIMIT 1`),version=Number(current[0]?.version??0)+1,now=new Date().toISOString()
    await tx.execute(sql`INSERT INTO field_block_documents (entity_type,entity_id,version,document,definition_versions,parent_revision_id,updated_by,updated_at) VALUES (${item.ref.entityType},${item.ref.entityId},${version},${blocksModule.serializeBlockDocument(validated)},${JSON.stringify(versions)},${item.parentRevisionId},${authorized.principal.id},${now}) ON CONFLICT (entity_type,entity_id) DO UPDATE SET version=EXCLUDED.version,document=EXCLUDED.document,definition_versions=EXCLUDED.definition_versions,parent_revision_id=EXCLUDED.parent_revision_id,updated_by=EXCLUDED.updated_by,updated_at=EXCLUDED.updated_at`)
  }
  for (const item of payload.reusableBlocks ?? []) {
    const document=remappedReusable.get(item.id)!.document
    const validated=await blocksModule.validateBlockDocument(document,registry,reusableResolver,context,blocksModule.DEFAULT_BLOCK_POLICY)
    const versions=remapDefinitionVersions(item.definitionVersions,groupRevisions,blockRevisions)
    const current=await lifecycleExecute<Record<string,unknown>>(tx,sql`SELECT version FROM field_reusable_blocks WHERE id=${item.id} LIMIT 1`),version=Number(current[0]?.version??0)+1,now=new Date().toISOString()
    await tx.execute(sql`INSERT INTO field_reusable_blocks (id,version,document,definition_versions,deleted_at,updated_by,updated_at) VALUES (${item.id},${version},${blocksModule.serializeBlockDocument(validated)},${JSON.stringify(versions)},NULL,${authorized.principal.id},${now}) ON CONFLICT (id) DO UPDATE SET version=EXCLUDED.version,document=EXCLUDED.document,definition_versions=EXCLUDED.definition_versions,deleted_at=NULL,updated_by=EXCLUDED.updated_by,updated_at=EXCLUDED.updated_at`)
  }

  for (const item of payload.revisions ?? []) {
    if ((await lifecycleExecute<Record<string,unknown>>(tx,sql`SELECT id FROM field_revisions WHERE id=${item.id} LIMIT 1`)).length>0) throw new FieldsLifecycleError('corpus-drift',`backup revision ${item.id} already exists at destination`)
    const versions=remapDefinitionVersions(item.definitionVersions,groupRevisions,blockRevisions)
    await requireRestoredDefinitionVersions(tx,versions)
    const blockDocument=item.blockDocument===undefined?null:remapBlockDocument(item.blockDocument,groupRevisions,blockRevisions)
    if(blockDocument!==null){
      await blocksModule.validateBlockDocument(blockDocument as import('./blocks.js').BlockDocument,registry,reusableResolver,context,blocksModule.DEFAULT_BLOCK_POLICY)
    }
    await tx.execute(sql`INSERT INTO field_revisions (id,entity_type,entity_id,parent_revision_id,parent_autosave_id,kind,retention_class,definition_versions,values,block_document,created_at,created_by,source_version) VALUES (${item.id},${item.ref.entityType},${item.ref.entityId},${item.parentRevisionId},${item.parentAutosaveId??null},${item.kind},${item.retentionClass??null},${JSON.stringify(versions)},${JSON.stringify(item.values)},${blockDocument===null?null:JSON.stringify(blockDocument)},${item.createdAt},${item.createdBy},${item.sourceVersion})`)
  }

  const restoredCounts=restoredPayloadCounts(payload)
  const result:FieldsBackupRestoreResult=Object.freeze({operationId,manifestId:manifest.id,destinationCorpusHash,restoredCounts})
  const event:FieldsBackupRestoreEvent=Object.freeze({kind:'backupRestored',operationId,principalId:authorized.principal.id,authorizationPolicyVersion:migration.policyVersion,manifest:verified,destinationCorpusHash,restoredCounts})
  await effects.audit.record(tx,event)
  await effects.outbox.enqueue(tx,event)
  await writeFieldsReceipt(tx,{operationId,kind:'fields:restore-backup',context:authorized,requestHash,result:freezeStorage(result,'restore result')})
  return result
}
