import { sql, type SQLWrapper } from 'drizzle-orm'
import type { TransactionIdentity } from '@platform-modules/db'
import { resolveFieldDefinitionVersion } from './definitions.js'
import {
  assertActiveFieldsTransaction,
  assertDefinitionMigration,
  assertFieldsAuthorized,
  assertPrincipalCapability,
  assertReceiptReplay,
  fieldsRequestHash,
  readFieldsReceipt,
  verifyFieldsBackup,
  writeFieldsReceipt,
  FieldsLifecycleError,
  type FieldsDestructiveDependencies,
} from './definition-lifecycle.js'
import { createLocationResolverRegistry } from './location.js'
import type { EntityRef } from './model.js'
import type { AnyFieldDefinition, FieldGroup, FieldStorageValue, FieldValueNodeRow, FieldsTransaction, OptionsPageDefinitionSnapshot } from './schema.js'
import { createFieldTypeRegistry, type FieldsContext } from './types.js'
import { decodeFieldValueNodes, encodeFieldValueNodes, validateFieldValue, type FieldValueMap } from './values.js'

export interface OptionsPageDefinition extends OptionsPageDefinitionSnapshot {}
export interface CodeOptionsPage extends OptionsPageDefinition { readonly origin: 'code' }
export interface ResolvedOptionsPage extends OptionsPageDefinition {
  readonly origin: 'code' | 'db'
  readonly version: number
  readonly active: boolean
  readonly shadowedDbVersion?: number
}
export interface OptionsPageImpact {
  readonly token: string
  readonly pageKey: string
  readonly expectedVersion: number
  readonly authorizationPolicyVersion: string
  readonly valueCorpusVersion: string
  readonly valueCorpusHash: string
  readonly affectedCounts: Readonly<Record<string, number>>
}
export type OptionsPageDeactivateStrategy = { readonly kind: 'retain' } | { readonly kind: 'purge'; readonly backupId: string }
export type OptionsPagePatch = Partial<Omit<OptionsPageDefinition, 'key'>>
export type EntityFieldValueMap = Readonly<Record<string, FieldValueMap>>
export type EntityFieldWriteMap = Readonly<Record<string, FieldValueMap>>
export type FieldsValueVersion = string
export interface FieldsReadResult<V> { readonly values: V; readonly version: FieldsValueVersion; readonly definitionVersions: Readonly<Record<string, number>> }
export interface FieldsMutationResult { readonly version: FieldsValueVersion; readonly changedPaths: readonly (readonly (string | number)[])[] }
export interface FieldsMutationOptions { readonly expectedVersion: FieldsValueVersion; readonly operationId: string; readonly expectedTransactionIdentity?: TransactionIdentity }

export interface FieldsReader { execute(query: SQLWrapper): Promise<unknown> }
interface OptionsRow 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 GroupRow extends Record<string, unknown> { key: string; currentRevision: number | string; definition: unknown }
interface NodeRow 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
}

function rowsOf<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 executeRows<Row extends Record<string, unknown>>(db: FieldsReader, query: SQLWrapper): Promise<readonly Row[]> { return rowsOf<Row>(await db.execute(query)) }
function parseJson(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 bool(value: unknown): boolean { return value === true || value === 1 }
function boundedString(value: unknown, field: string, max = 4096): string {
  if (typeof value !== 'string' || value.length === 0 || value.length > max) throw new FieldsLifecycleError('validation', 'must be a bounded non-empty string', field)
  return value
}
function normalizedKey(value: unknown, field: string): string {
  const key = boundedString(value, field, 128)
  if (!/^[a-z][a-z0-9_-]*$/.test(key)) throw new FieldsLifecycleError('validation', 'must be a normalized key', field)
  return key
}
function normalizeOptionsPage(input: OptionsPageDefinition): OptionsPageDefinition {
  const key = normalizedKey(input.key, 'key')
  const title = boundedString(input.title, 'title')
  const readCapability = boundedString(input.readCapability, 'readCapability', 256)
  const writeCapability = boundedString(input.writeCapability, 'writeCapability', 256)
  const manageCapability = boundedString(input.manageCapability, 'manageCapability', 256)
  const parentKey = input.parentKey === undefined ? undefined : normalizedKey(input.parentKey, 'parentKey')
  if (parentKey === key) throw new FieldsLifecycleError('validation', 'page cannot parent itself', 'parentKey')
  if (input.position !== undefined && (!Number.isSafeInteger(input.position) || Math.abs(input.position) > 1_000_000)) throw new FieldsLifecycleError('validation', 'must be a bounded integer', 'position')
  const out: OptionsPageDefinition = Object.freeze({
    key,
    title,
    ...(input.menuTitle === undefined ? {} : { menuTitle: boundedString(input.menuTitle, 'menuTitle') }),
    ...(parentKey === undefined ? {} : { parentKey }),
    readCapability,
    writeCapability,
    manageCapability,
    ...(input.position === undefined ? {} : { position: input.position }),
    ...(input.icon === undefined ? {} : { icon: boundedString(input.icon, 'icon', 512) }),
    ...(input.redirectToFirstChild === undefined ? {} : { redirectToFirstChild: Boolean(input.redirectToFirstChild) }),
    ...(input.autoload === undefined ? {} : { autoload: Boolean(input.autoload) }),
  })
  return out
}
function parseOptionsDefinition(value: unknown): OptionsPageDefinition {
  const raw = parseJson(value, 'options definition')
  if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) throw new FieldsLifecycleError('integrity', 'options definition is malformed')
  return normalizeOptionsPage(raw as unknown as OptionsPageDefinition)
}
function resolvedDb(row: OptionsRow): ResolvedOptionsPage {
  const definition = parseOptionsDefinition(row.definition)
  if (definition.key !== row.key) throw new FieldsLifecycleError('integrity', 'options definition key does not match persisted key')
  return Object.freeze({ ...definition, origin: row.origin === 'code' ? 'code' : 'db', version: Number(row.version), active: bool(row.active), ...(row.shadowedDbVersion === null ? {} : { shadowedDbVersion: Number(row.shadowedDbVersion) }) })
}
async function readOptionRows(db: FieldsReader): Promise<readonly OptionsRow[]> {
  return executeRows<OptionsRow>(db, sql`SELECT key, origin, version, active, current_revision AS "currentRevision", canonical_hash AS "canonicalHash", definition, shadowed_db_version AS "shadowedDbVersion" FROM field_options_definitions ORDER BY key`)
}
function mergePages(rows: readonly OptionsRow[], codePages: readonly CodeOptionsPage[]): readonly ResolvedOptionsPage[] {
  const db = new Map(rows.map((row) => [row.key, resolvedDb(row)]))
  const code = new Map<string, CodeOptionsPage>()
  for (const raw of codePages) {
    const normalized = Object.freeze({ ...normalizeOptionsPage(raw), origin: 'code' as const })
    if (code.has(normalized.key)) throw new FieldsLifecycleError('validation', 'duplicate code options key', normalized.key)
    code.set(normalized.key, normalized)
  }
  const merged = new Map<string, ResolvedOptionsPage>()
  for (const page of db.values()) merged.set(page.key, page)
  for (const page of code.values()) {
    const previous = db.get(page.key)
    merged.set(page.key, Object.freeze({ ...page, version: previous?.version ?? 1, active: true, ...(previous?.origin === 'db' ? { shadowedDbVersion: previous.version } : previous?.shadowedDbVersion === undefined ? {} : { shadowedDbVersion: previous.shadowedDbVersion }) }))
  }
  validatePageTree([...merged.values()].filter((page) => page.active))
  return Object.freeze([...merged.values()].sort((a, b) => (a.parentKey ?? '').localeCompare(b.parentKey ?? '') || (a.position ?? 0) - (b.position ?? 0) || a.key.localeCompare(b.key)))
}
function validatePageTree(pages: readonly ResolvedOptionsPage[]): void {
  const byKey = new Map(pages.map((page) => [page.key, page]))
  const siblingPositions = new Set<string>()
  for (const page of pages) {
    if (page.parentKey && !byKey.has(page.parentKey)) throw new FieldsLifecycleError('validation', 'parent page does not resolve in effective registry', page.key)
    if (page.position !== undefined) {
      const identity = `${page.parentKey ?? ''}:${page.position}`
      if (siblingPositions.has(identity)) throw new FieldsLifecycleError('validation', 'duplicate sibling position', page.key)
      siblingPositions.add(identity)
    }
    const seen = new Set<string>([page.key]); let cursor = page.parentKey
    while (cursor) { if (seen.has(cursor)) throw new FieldsLifecycleError('validation', 'options page parent cycle', page.key); seen.add(cursor); cursor = byKey.get(cursor)?.parentKey }
  }
}

export function defineOptionsPage(definition: OptionsPageDefinition): CodeOptionsPage { return Object.freeze({ ...normalizeOptionsPage(definition), origin: 'code' }) }
export async function resolveOptionsPages(db: FieldsReader, input: { codePages?: readonly CodeOptionsPage[] } = {}): Promise<ResolvedOptionsPage[]> { return [...mergePages(await readOptionRows(db), input.codePages ?? [])] }
async function readDbPage(db: FieldsReader, key: string): Promise<{ row: OptionsRow; page: ResolvedOptionsPage } | null> {
  const rows = await executeRows<OptionsRow>(db, sql`SELECT key, origin, version, active, current_revision AS "currentRevision", canonical_hash AS "canonicalHash", definition, shadowed_db_version AS "shadowedDbVersion" FROM field_options_definitions WHERE key = ${key} LIMIT 1`)
  return rows[0] ? { row: rows[0], page: resolvedDb(rows[0]) } : null
}
function definitionRef(key: string): EntityRef { return Object.freeze({ entityType: 'fields-options-definition', entityId: key }) }
function valueRef(key: string): EntityRef { return Object.freeze({ entityType: 'options', entityId: key }) }

export async function createOptionsPage(tx: FieldsTransaction, definition: OptionsPageDefinition, context: FieldsContext, input: { codePages?: readonly CodeOptionsPage[]; operationId: string }): Promise<ResolvedOptionsPage> {
  assertActiveFieldsTransaction(tx)
  const normalized = normalizeOptionsPage(definition)
  const authorized = await assertFieldsAuthorized(context, 'manageDefinitions', definitionRef(normalized.key))
  assertPrincipalCapability(authorized, normalized.manageCapability)
  const codePages = input.codePages ?? []
  if (codePages.some((page) => page.key === normalized.key)) throw new FieldsLifecycleError('validation', 'effective key is owned by code', normalized.key)
  const canonicalHash = await fieldsRequestHash(normalized)
  const requestHash = await fieldsRequestHash({ kind: 'create-options-page', canonicalHash, codeKeys: codePages.map((page) => page.key).sort() })
  const receipt = await readFieldsReceipt(tx, input.operationId)
  if (receipt) {
    assertReceiptReplay(receipt, { kind: 'fields:create-options-page', context: authorized, requestHash })
    const replay = await readDbPage(tx, normalized.key)
    if (!replay) throw new FieldsLifecycleError('integrity', 'committed options receipt has no page')
    return replay.page
  }
  if (await readDbPage(tx, normalized.key)) throw new FieldsLifecycleError('validation', 'options page key already exists', normalized.key)
  const existing = await readOptionRows(tx)
  validatePageTree([...mergePages(existing, codePages), Object.freeze({ ...normalized, origin: 'db' as const, version: 1, active: true })])
  const now = new Date().toISOString()
  await tx.execute(sql`INSERT INTO field_options_definitions (key, origin, version, active, current_revision, canonical_hash, definition, created_at, updated_at) VALUES (${normalized.key}, 'db', 1, true, 1, ${canonicalHash}, ${JSON.stringify(normalized)}, ${now}, ${now})`)
  await tx.execute(sql`INSERT INTO field_options_definition_versions (options_key, revision, canonical_hash, definition, origin, created_at) VALUES (${normalized.key}, 1, ${canonicalHash}, ${JSON.stringify(normalized)}, 'db', ${now})`)
  const result: ResolvedOptionsPage = Object.freeze({ ...normalized, origin: 'db', version: 1, active: true })
  await writeFieldsReceipt(tx, { operationId: input.operationId, kind: 'fields:create-options-page', context: authorized, requestHash, result: Object.freeze({ key: normalized.key, version: 1 }) })
  return result
}

export async function updateOptionsPage(tx: FieldsTransaction, key: string, patch: OptionsPagePatch, expectedVersion: number, context: FieldsContext, input: { codePages?: readonly CodeOptionsPage[]; operationId: string }): Promise<ResolvedOptionsPage> {
  assertActiveFieldsTransaction(tx)
  const normalizedKeyValue = normalizedKey(key, 'key')
  const current = await readDbPage(tx, normalizedKeyValue)
  if (!current) throw new FieldsLifecycleError('not-found', 'options page is unavailable')
  const authorized = await assertFieldsAuthorized(context, 'manageDefinitions', definitionRef(normalizedKeyValue))
  assertPrincipalCapability(authorized, current.page.manageCapability)
  if (current.page.origin === 'code' || (input.codePages ?? []).some((page) => page.key === normalizedKeyValue)) throw new FieldsLifecycleError('validation', 'effective key is owned by code', normalizedKeyValue)
  const requestHash = await fieldsRequestHash({ kind: 'update-options-page', key: normalizedKeyValue, expectedVersion, patch })
  const receipt = await readFieldsReceipt(tx, input.operationId)
  if (receipt) { assertReceiptReplay(receipt, { kind: 'fields:update-options-page', context: authorized, requestHash }); const replay = await readDbPage(tx, normalizedKeyValue); if (!replay) throw new FieldsLifecycleError('integrity', 'committed options receipt has no page'); return replay.page }
  if (current.page.version !== expectedVersion) throw new FieldsLifecycleError('stale-version', 'options page version changed')
  const next = normalizeOptionsPage({ ...current.page, ...patch, key: normalizedKeyValue })
  const canonicalHash = await fieldsRequestHash(next)
  const allRows = await readOptionRows(tx)
  const replacement: OptionsRow = { ...current.row, version: expectedVersion + 1, definition: next, canonicalHash, currentRevision: Number(current.row.currentRevision) + (canonicalHash === current.row.canonicalHash ? 0 : 1) }
  validatePageTree(mergePages(allRows.map((row) => row.key === normalizedKeyValue ? replacement : row), input.codePages ?? []).filter((page) => page.active))
  if (canonicalHash === current.row.canonicalHash) {
    await writeFieldsReceipt(tx, { operationId: input.operationId, kind: 'fields:update-options-page', context: authorized, requestHash, result: Object.freeze({ key: normalizedKeyValue, version: current.page.version }) })
    return current.page
  }
  const revision = Number(current.row.currentRevision) + 1, version = expectedVersion + 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 (${normalizedKeyValue}, ${revision}, ${canonicalHash}, ${JSON.stringify(next)}, 'db', ${now})`)
  await tx.execute(sql`UPDATE field_options_definitions SET version = ${version}, current_revision = ${revision}, canonical_hash = ${canonicalHash}, definition = ${JSON.stringify(next)}, updated_at = ${now} WHERE key = ${normalizedKeyValue} AND version = ${expectedVersion}`)
  const result: ResolvedOptionsPage = Object.freeze({ ...next, origin: 'db', version, active: current.page.active, ...(current.page.shadowedDbVersion === undefined ? {} : { shadowedDbVersion: current.page.shadowedDbVersion }) })
  await writeFieldsReceipt(tx, { operationId: input.operationId, kind: 'fields:update-options-page', context: authorized, requestHash, result: Object.freeze({ key: normalizedKeyValue, version }) })
  return result
}

interface OptionsCorpus {
  readonly version: string
  readonly hash: string
  readonly counts: Readonly<Record<string, number>>
}
async function optionsCorpus(db: FieldsReader, key: string): Promise<OptionsCorpus> {
  const valueRows = await executeRows<Record<string, unknown>>(db, sql`SELECT node_id AS "nodeId", group_key AS "groupKey", definition_revision AS "definitionRevision", field_key AS "fieldKey", path, 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 = 'options' AND entity_id = ${key} ORDER BY group_key, path`)
  const revisions = await executeRows<Record<string, unknown>>(db, sql`SELECT id, parent_revision_id AS "parentRevisionId", parent_autosave_id AS "parentAutosaveId", kind, retention_class AS "retentionClass", definition_versions AS "definitionVersions", values, block_document AS "blockDocument", created_at AS "createdAt", created_by AS "createdBy", source_version AS "sourceVersion" FROM field_revisions WHERE entity_type = 'options' AND entity_id = ${key} ORDER BY created_at, id`)
  const counts = Object.freeze({ values: valueRows.length, revisions: revisions.length })
  const version = await fieldsRequestHash({ valueIds: valueRows.map((row) => row.nodeId), revisionIds: revisions.map((row) => row.id) })
  const hash = await fieldsRequestHash({ valueRows, revisions })
  return Object.freeze({ version, hash, counts })
}
function tokenEncode(payload: Record<string, FieldStorageValue>): Promise<string> {
  const bytes = new TextEncoder().encode(JSON.stringify(payload))
  const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')
  return fieldsRequestHash(payload).then((digest) => `${hex}.${digest}`)
}
async function tokenDecode(token: string): Promise<Record<string, unknown>> {
  const match = /^([a-f0-9]+)\.([a-f0-9]{64})$/i.exec(token)
  if (!match || match[1]!.length % 2 !== 0 || match[1]!.length > 64_000) throw new FieldsLifecycleError('validation', 'impact token is invalid')
  const bytes = new Uint8Array(match[1]!.match(/../g)!.map((pair) => Number.parseInt(pair, 16)))
  let value: unknown
  try { value = JSON.parse(new TextDecoder().decode(bytes)) as unknown } catch { throw new FieldsLifecycleError('validation', 'impact token is invalid') }
  if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new FieldsLifecycleError('validation', 'impact token is invalid')
  if (await fieldsRequestHash(value) !== match[2]!.toLowerCase()) throw new FieldsLifecycleError('validation', 'impact token integrity check failed')
  return value as Record<string, unknown>
}
function countsEqual(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())
}

export async function previewOptionsPageDeactivation(db: FieldsReader, key: string, expectedVersion: number, context: FieldsContext): Promise<OptionsPageImpact> {
  const normalized = normalizedKey(key, 'key')
  const current = await readDbPage(db, normalized)
  if (!current) throw new FieldsLifecycleError('not-found', 'options page is unavailable')
  const authorized = await assertFieldsAuthorized(context, 'manageDefinitions', definitionRef(normalized))
  assertPrincipalCapability(authorized, current.page.manageCapability)
  if (current.page.origin === 'code') throw new FieldsLifecycleError('validation', 'code-owned options page changes require registry reconciliation')
  if (current.page.version !== expectedVersion) throw new FieldsLifecycleError('stale-version', 'options page version changed')
  const migration = await assertDefinitionMigration(context, ['options'])
  const corpus = await optionsCorpus(db, normalized)
  const payload: Record<string, FieldStorageValue> = Object.freeze({
    kind: 'options-page-deactivation', pageKey: normalized, expectedVersion,
    authorizationPolicyVersion: migration.policyVersion,
    valueCorpusVersion: corpus.version, valueCorpusHash: corpus.hash,
    affectedCounts: corpus.counts,
  })
  return Object.freeze({ token: await tokenEncode(payload), pageKey: normalized, expectedVersion, authorizationPolicyVersion: migration.policyVersion, valueCorpusVersion: corpus.version, valueCorpusHash: corpus.hash, affectedCounts: corpus.counts })
}

export async function applyOptionsPageDeactivation(tx: FieldsTransaction, impactToken: string, strategy: OptionsPageDeactivateStrategy, operationId: string, context: FieldsContext, destructive?: FieldsDestructiveDependencies): Promise<ResolvedOptionsPage> {
  assertActiveFieldsTransaction(tx)
  const payload = await tokenDecode(impactToken)
  if (payload.kind !== 'options-page-deactivation' || typeof payload.pageKey !== 'string') throw new FieldsLifecycleError('validation', 'impact token has wrong operation kind')
  const key = normalizedKey(payload.pageKey, 'pageKey')
  const current = await readDbPage(tx, key)
  if (!current) throw new FieldsLifecycleError('not-found', 'options page is unavailable')
  const authorized = await assertFieldsAuthorized(context, 'manageDefinitions', definitionRef(key))
  assertPrincipalCapability(authorized, current.page.manageCapability)
  if (current.page.origin === 'code') throw new FieldsLifecycleError('validation', 'code-owned options page changes require registry reconciliation')
  if (strategy.kind !== 'retain' && strategy.kind !== 'purge') throw new FieldsLifecycleError('validation', 'unknown options deactivation strategy')
  const migration = await assertDefinitionMigration(context, ['options'])
  if (payload.authorizationPolicyVersion !== migration.policyVersion) throw new FieldsLifecycleError('corpus-drift', 'authorization policy changed since preview')
  const requestHash = await fieldsRequestHash({ impactToken, strategy })
  const receipt = await readFieldsReceipt(tx, operationId)
  if (receipt) { assertReceiptReplay(receipt, { kind: 'fields:deactivate-options-page', context: authorized, requestHash }); const replay = await readDbPage(tx, key); if (!replay) throw new FieldsLifecycleError('integrity', 'committed options receipt has no page'); return replay.page }
  if (Number(payload.expectedVersion) !== current.page.version) throw new FieldsLifecycleError('stale-version', 'options page version changed')
  const corpus = await optionsCorpus(tx, key)
  if (payload.valueCorpusVersion !== corpus.version || payload.valueCorpusHash !== corpus.hash || !countsEqual(corpus.counts, payload.affectedCounts)) throw new FieldsLifecycleError('corpus-drift', 'options value corpus changed since preview')

  if (strategy.kind === 'purge') {
    if (!destructive) throw new FieldsLifecycleError('backup-invalid', 'destructive dependencies are required')
    const scopeHash = await fieldsRequestHash({ definitionKind: 'optionsPage', definitionKey: key, 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 = Object.freeze({ operationId, principalId: authorized.principal.id, definitionKind: 'optionsPage' as const, definitionKey: key, strategy: Object.freeze({ ...strategy }) as unknown as FieldStorageValue, 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 tx.execute(sql`DELETE FROM field_value_nodes WHERE entity_type = 'options' AND entity_id = ${key}`)
    await tx.execute(sql`DELETE FROM field_revisions WHERE entity_type = 'options' AND entity_id = ${key}`)
  }
  const nextVersion = current.page.version + 1, now = new Date().toISOString()
  await tx.execute(sql`UPDATE field_options_definitions SET version = ${nextVersion}, active = false, updated_at = ${now} WHERE key = ${key} AND version = ${current.page.version}`)
  const result: ResolvedOptionsPage = Object.freeze({ ...current.page, version: nextVersion, active: false })
  await writeFieldsReceipt(tx, { operationId, kind: 'fields:deactivate-options-page', context: authorized, requestHash, result: Object.freeze({ key, version: nextVersion }) })
  return result
}

function nodeFromRow(row: NodeRow): 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 })
}
async function matchingOptionGroups(db: FieldsReader, key: string): Promise<readonly { group: FieldGroup; revision: number }[]> {
  const rows = await executeRows<GroupRow>(db, sql`SELECT key, current_revision AS "currentRevision", definition FROM field_group_definitions WHERE active = true ORDER BY key`)
  const registry = createLocationResolverRegistry(), output: { group: FieldGroup; revision: number }[] = []
  for (const row of rows) {
    const revision = Number(row.currentRevision)
    const version = await resolveFieldDefinitionVersion(db as Parameters<typeof resolveFieldDefinitionVersion>[0], row.key, revision)
    if (await registry.evaluate(version.definition.location, { entityType: 'options', surface: 'options', optionsPage: key, entityId: key })) output.push({ group: version.definition, revision })
  }
  return Object.freeze(output)
}
async function readOptionValuesSnapshot(db: FieldsReader, key: string, context: FieldsContext): Promise<FieldsReadResult<EntityFieldValueMap>> {
  const groups = await matchingOptionGroups(db, key)
  const rows = await executeRows<NodeRow>(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 entity_type = 'options' AND entity_id = ${key} ORDER BY group_key, path`)
  const byGroup = new Map<string, NodeRow[]>(); for (const row of rows) { const list = byGroup.get(row.groupKey) ?? []; list.push(row); byGroup.set(row.groupKey, list) }
  const values: Record<string, FieldValueMap> = {}, definitionVersions: Record<string, number> = {}
  for (const current of groups) {
    const nodes = byGroup.get(current.group.key) ?? []
    const revisions = [...new Set(nodes.map((row) => Number(row.definitionRevision)))]
    const revision = revisions.length === 0 ? current.revision : revisions.length === 1 ? revisions[0]! : (() => { throw new FieldsLifecycleError('integrity', 'options values mix immutable group revisions') })()
    const version = revision === current.revision ? current.group : (await resolveFieldDefinitionVersion(db as Parameters<typeof resolveFieldDefinitionVersion>[0], current.group.key, revision)).definition
    values[current.group.key] = decodeFieldValueNodes(version, nodes.map(nodeFromRow), { definitionRevision: revision, ...(context.cloneResolver ? { cloneResolver: { resolve: (definition) => context.cloneResolver!.resolve(definition) } } : {}) })
    definitionVersions[current.group.key] = revision
  }
  const version = await fieldsRequestHash({ definitionVersions, values })
  return Object.freeze({ values: Object.freeze(values), version, definitionVersions: Object.freeze(definitionVersions) })
}

export async function getOptionsValues(db: FieldsReader, key: string, context: FieldsContext): Promise<FieldsReadResult<EntityFieldValueMap>> {
  const normalized = normalizedKey(key, 'key')
  const page = await readDbPage(db, normalized)
  if (!page || !page.page.active) throw new FieldsLifecycleError('not-found', 'options page is unavailable')
  const authorized = await assertFieldsAuthorized(context, 'read', valueRef(normalized)); assertPrincipalCapability(authorized, page.page.readCapability)
  return readOptionValuesSnapshot(db, normalized, context)
}

function containsPassword(fields: readonly AnyFieldDefinition[]): boolean {
  for (const field of fields) {
    if (field.type === 'password') return true
    if (field.type === 'group' || field.type === 'repeater') {
      if (containsPassword((field.settings as { fields: readonly AnyFieldDefinition[] }).fields)) return true
    }
    if (field.type === 'flexible') {
      for (const layout of (field.settings as { layouts: readonly { fields: readonly AnyFieldDefinition[] }[] }).layouts) if (containsPassword(layout.fields)) return true
    }
    if (field.type === 'clone') return true
  }
  return false
}
async function validateGroupWrite(group: FieldGroup, input: FieldValueMap, context: FieldsContext): Promise<FieldValueMap> {
  if (containsPassword(group.fields)) throw new FieldsLifecycleError('capability-unavailable', 'options groups containing password or unresolved clone fields require a private credential adapter')
  const registry = context.fieldTypeRegistry ?? createFieldTypeRegistry(), output: Record<string, unknown> = {}
  const allowed = new Set(group.fields.map((field) => field.key))
  for (const key of Object.keys(input)) if (!allowed.has(key)) throw new FieldsLifecycleError('validation', 'value names a field outside the resolved group', `${group.key}.${key}`)
  for (const field of group.fields) if (Object.prototype.hasOwnProperty.call(input, field.key)) output[field.key] = await validateFieldValue(field as AnyFieldDefinition, input[field.key], registry, {
    sanitizer: context.sanitizer,
    ...(context.cloneResolver ? { cloneResolver: { resolve: (definition) => context.cloneResolver!.resolve(definition) } } : {}),
  })
  return Object.freeze(output) as FieldValueMap
}
async function writeNodes(tx: FieldsTransaction, key: string, group: FieldGroup, revision: number, values: FieldValueMap, context: FieldsContext): Promise<void> {
  await tx.execute(sql`DELETE FROM field_value_nodes WHERE entity_type = 'options' AND entity_id = ${key} AND group_key = ${group.key}`)
  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 rows = encodeFieldValueNodes(group, values, { entityType: 'options', entityId: key, definitionRevision: revision, nodeIdForPath, ...(context.cloneResolver ? { cloneResolver: { resolve: (definition) => context.cloneResolver!.resolve(definition) } } : {}) })
  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()})`)
}

export async function setOptionsValues(tx: FieldsTransaction, key: string, values: EntityFieldWriteMap, mutation: FieldsMutationOptions, context: FieldsContext): Promise<FieldsMutationResult> {
  if (mutation.expectedTransactionIdentity !== undefined) assertActiveFieldsTransaction(tx, mutation.expectedTransactionIdentity); else assertActiveFieldsTransaction(tx)
  const normalized = normalizedKey(key, 'key')
  const page = await readDbPage(tx, normalized)
  if (!page || !page.page.active) throw new FieldsLifecycleError('not-found', 'options page is unavailable')
  const authorized = await assertFieldsAuthorized(context, 'write', valueRef(normalized)); assertPrincipalCapability(authorized, page.page.writeCapability)
  const requestHash = await fieldsRequestHash({ key: normalized, expectedVersion: mutation.expectedVersion, values })
  const receipt = await readFieldsReceipt(tx, mutation.operationId)
  if (receipt) { assertReceiptReplay(receipt, { kind: 'fields:set-options-values', context: authorized, requestHash }); const replay = receipt.result as Record<string, unknown>; return Object.freeze({ version: String(replay.version), changedPaths: Object.freeze([]) }) }
  const current = await readOptionValuesSnapshot(tx, normalized, context)
  if (current.version !== mutation.expectedVersion) throw new FieldsLifecycleError('stale-version', 'options values changed')
  const groups = await matchingOptionGroups(tx, normalized), byKey = new Map(groups.map((item) => [item.group.key, item]))
  for (const groupKey of Object.keys(values)) if (!byKey.has(groupKey)) throw new FieldsLifecycleError('validation', 'value names a group outside the resolved options page', groupKey)
  const changedPaths: (readonly (string | number)[])[] = []
  for (const [groupKey, input] of Object.entries(values)) {
    const resolved = byKey.get(groupKey)!
    const validated = await validateGroupWrite(resolved.group, input, context)
    await writeNodes(tx, normalized, resolved.group, resolved.revision, validated, context)
    for (const fieldKey of Object.keys(input).sort()) changedPaths.push(Object.freeze([groupKey, fieldKey]))
  }
  const next = await readOptionValuesSnapshot(tx, normalized, context)
  await writeFieldsReceipt(tx, { operationId: mutation.operationId, kind: 'fields:set-options-values', context: authorized, requestHash, result: Object.freeze({ version: next.version }) })
  return Object.freeze({ version: next.version, changedPaths: Object.freeze(changedPaths) })
}

/** Internal composition seam for definition reconciliation; intentionally not re-exported from package index. */
export const __optionsLifecycleInternals = Object.freeze({
  normalizeOptionsPage,
  readOptionRows,
  readDbPage,
  mergePages,
  optionsCorpus,
})
