import { sql, type SQLWrapper } from 'drizzle-orm'
import type { TransactionIdentity } from '@platform-modules/db'
import {
  canonicalFieldGroupHash,
  parseFieldGroupDefinition,
  type AnyFieldDefinition,
  type FieldGroup,
  type FieldPath,
  type FieldStorageValue,
  type FieldsTransaction,
} from './schema.js'
import {
  FIELD_DEFINITION_LIMITS,
  requireAuthorizedFieldsContext,
  type FieldsContext,
  type FieldTypeRegistry,
  type ResolvedAnyFieldDefinition,
} from './types.js'
import type { EntityRef } from './model.js'
import type { EntityFieldValueMap, OptionsPageDefinition, FieldsReader } from './options.js'
import { __optionsLifecycleInternals } from './options.js'
import type { BlockDocument, FieldBlockDefinition, FieldBlockDefinitionVersion } from './blocks.js'
import { DEFAULT_BLOCK_POLICY, __fieldBlockLifecycleInternals, createReusableBlockResolver, parseBlockDocument, resolveFieldBlocks, serializeBlockDocument, validateBlockDocument } from './blocks.js'
import { encodeFieldValueNodes, type MediaFieldRef, type TermFieldRef } from './values.js'
import {
  FieldsLifecycleError,
  assertActiveFieldsTransaction,
  assertDefinitionMigration,
  assertFieldsAuthorized,
  assertReceiptReplay,
  fieldsRequestHash,
  readFieldsReceipt,
  writeFieldsReceipt,
  type CodeFieldsRegistrySnapshot,
} from './definition-lifecycle.js'

const IMPORT_SESSION_UUID_RE=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
function importSessionId(value:unknown):string{const id=boundedString(value,'staging.hostSessionId',128);if(!IMPORT_SESSION_UUID_RE.test(id))fail('validation','staging.hostSessionId must be a UUID');return id}

export interface FieldsRevisionExport {
  readonly sourceRevisionKey: string
  readonly sourceRef: EntityRef
  readonly parentRevisionKey: string
  readonly definitionVersions: Readonly<Record<string, number>>
  readonly values: EntityFieldValueMap
  readonly blockDocument?: BlockDocument
  readonly createdAt: string
  readonly createdBySourceId: string
  readonly sourceVersion: number
}
export interface FieldDefinitionVersionExport {
  readonly groupKey: string
  readonly revision: number
  readonly canonicalHash: string
  readonly definition: FieldGroup
  readonly createdAt: string
  readonly origin: 'code' | 'db' | 'import'
}
export interface FieldBlockDefinitionVersionExport extends Omit<FieldBlockDefinitionVersion, 'createdAt'> { readonly createdAt: string }
export interface FieldsExportManifest {
  readonly version: 1
  readonly definitions: {
    readonly groups: readonly FieldGroup[]
    readonly versions: readonly FieldDefinitionVersionExport[]
    readonly optionsPages: readonly OptionsPageDefinition[]
    readonly blocks: readonly FieldBlockDefinition[]
    readonly blockVersions: readonly FieldBlockDefinitionVersionExport[]
  }
  readonly entities?: readonly { readonly sourceRef: EntityRef; readonly values: EntityFieldValueMap; readonly definitionVersions: Readonly<Record<string, number>> }[]
  readonly revisions?: readonly FieldsRevisionExport[]
  readonly options?: readonly { readonly pageKey: string; readonly values: EntityFieldValueMap; readonly definitionVersions: Readonly<Record<string, number>>; readonly revision: number }[]
  readonly blockDocuments?: readonly { readonly sourceRef: EntityRef; readonly document: BlockDocument; readonly version: string; readonly definitionVersions: Readonly<Record<string, number>> }[]
  readonly omissions: readonly { readonly sourceRef: EntityRef; readonly fieldPath: FieldPath; readonly reason: 'sensitive' | 'unauthorized' }[]
}
export interface FieldsImportLimits { readonly maxBytes: number; readonly maxDefinitions: number; readonly maxEntities: number; readonly maxRevisions: number; readonly maxBlockNodes: number; readonly maxDepth: number }
export interface FieldsImportConflict { readonly section: 'definitions' | 'entities' | 'revisions' | 'options' | 'blockDocuments'; readonly sourceKey: string; readonly kind: 'exists' | 'missingDependency' | 'incompatibleVersion' | 'unauthorized' }
export type FieldsImportConflictPolicy = 'reject' | 'preserveDestination' | 'replaceMapped'
export interface FieldsImportRemappers {
  entity(ref: EntityRef): Promise<EntityRef | null>
  user(id: string): Promise<string | null>
  media(ref: MediaFieldRef): Promise<MediaFieldRef | null>
  term(ref: TermFieldRef): Promise<TermFieldRef | null>
  reusableBlock(id: string): Promise<string | null>
}
export interface ParsedFieldsImport { readonly manifestHash: string; readonly registryVersion: string; readonly manifest: FieldsExportManifest }
export interface FieldsImportScope { readonly tenantKey: string; readonly entityTypes: readonly string[] }
export interface FieldsImportRequiredAuthority { readonly entityType: string; readonly branches: readonly ('manageDefinitions' | 'migrateDefinitions' | 'read' | 'write')[] }
export interface FieldsImportPlan {
  readonly planId: string
  readonly manifestHash: string
  readonly registryVersion: string
  readonly codeRegistryVersion: string
  readonly codeRegistryHash: string
  readonly valueCorpusVersion: string
  readonly authorizationPolicyVersion: string
  readonly scope: FieldsImportScope
  readonly requiredAuthority: readonly FieldsImportRequiredAuthority[]
  readonly mappings: Readonly<Record<string, string>>
  readonly counts: Readonly<Record<'definitions' | 'entities' | 'revisions' | 'options' | 'blockDocuments', number>>
  readonly conflicts: readonly FieldsImportConflict[]
  readonly omissions: FieldsExportManifest['omissions']
  /** Immutable normalized bytes required by start/resume; never contains executable code. */
  readonly manifest: FieldsExportManifest
  readonly policy: FieldsImportConflictPolicy
}
export interface FieldsImportStaging { readonly hostSessionId: string; readonly visibility: 'hiddenUntilPublish' }
export type FieldsImportSection = 'definitions' | 'optionsPages' | 'blocks' | 'entities' | 'revisions' | 'options' | 'blockDocuments'
export interface FieldsImportJournal {
  readonly importId: string
  readonly version: string
  readonly planId: string
  readonly manifestHash: string
  readonly codeRegistryVersion: string
  readonly codeRegistryHash: string
  readonly staging: FieldsImportStaging
  readonly scope: FieldsImportScope
  readonly authorizationPolicyVersion: string
  readonly state: 'planned' | 'applying' | 'staged' | 'published' | 'rollingBack' | 'rolledBack' | 'reversing' | 'reversed' | 'failed'
  readonly nextSection: FieldsImportSection | null
  readonly nextOffset: number
}
export interface FieldsImportCommand {
  readonly operationId: string
  readonly expectedPlanId: string
  readonly expectedManifestHash: string
  readonly scope: FieldsImportScope
  readonly expectedJournalVersion: string | null
  readonly expectedSection: FieldsImportJournal['nextSection']
  readonly expectedOffset: number | null
}
export interface FieldsPublishedReversePlan {
  readonly reversePlanId: string
  readonly importId: string
  readonly originalPlanId: string
  readonly manifestHash: string
  readonly publishedValueCorpusVersion: string
  readonly beforeImageHash: string
  readonly counts: FieldsImportPlan['counts']
  readonly scope: FieldsImportScope
  readonly requiredAuthority: readonly FieldsImportRequiredAuthority[]
  readonly authorizationPolicyVersion: string
}
export interface FieldsPublishedReverseCommand extends FieldsImportCommand { readonly expectedReversePlanId: string; readonly expectedPublishedValueCorpusVersion: string }
export interface FieldsImportEvent { readonly kind: 'started'|'batchStaged'|'published'|'rolledBack'|'reversed'; readonly operationId: string; readonly importId: string; readonly principalId: string; readonly planId: string; readonly manifestHash: string; readonly state: FieldsImportJournal['state']; readonly version: string; readonly nextSection: FieldsImportSection|null; readonly nextOffset: number; readonly counts: FieldsImportPlan['counts'] }
export interface FieldsImportEffects { audit(tx: FieldsTransaction,event: FieldsImportEvent):Promise<void>; enqueue(tx: FieldsTransaction,event: FieldsImportEvent):Promise<void> }

export type FieldsImportErrorCode = 'validation'|'access-denied'|'stale-plan'|'stale-journal'|'operation-conflict'|'integrity'|'not-found'
export class FieldsImportError extends Error { override readonly name='FieldsImportError'; constructor(readonly code:FieldsImportErrorCode,message:string,readonly field?:string){super(message)} }
export function isFieldsImportError(error:unknown):error is FieldsImportError{return error instanceof FieldsImportError}

const SECTIONS: readonly FieldsImportSection[] = Object.freeze(['definitions','optionsPages','blocks','entities','revisions','options','blockDocuments'])
const DEFAULT_IMPORT_LIMITS: FieldsImportLimits = Object.freeze({ maxBytes: 8*1024*1024, maxDefinitions: 2000, maxEntities: 10000, maxRevisions: 20000, maxBlockNodes: 20000, maxDepth: 24 })

function fail(code: FieldsImportErrorCode|'stale-version', detail: string, field?: string): never { throw new FieldsImportError(code==='stale-version'?'stale-plan':code,detail,field) }
function isImportUniqueViolation(error:unknown):boolean{let current:unknown=error;while(current){const code=(current as {code?:unknown}).code,message=current instanceof Error?current.message:String(current);if(code==='23505'||/unique constraint|duplicate key|fields_import_journal_session_uq/i.test(message))return true;current=current instanceof Error?(current as Error&{cause?:unknown}).cause:undefined}return false}
function plain(value: unknown, field: string): Record<string,unknown> { if(typeof value!=='object'||value===null||Array.isArray(value)||Object.getPrototypeOf(value)!==Object.prototype) fail('validation',`${field} must be a plain object`); return value as Record<string,unknown> }
function boundedString(value:unknown,field:string,max=4096):string{if(typeof value!=='string'||value.length===0||value.length>max)fail('validation',`${field} must be a bounded non-empty string`);return value}
function positive(value:unknown,field:string):number{const n=Number(value);if(!Number.isSafeInteger(n)||n<1)fail('validation',`${field} must be a positive safe integer`);return n}
function bool(value:unknown,field:string):boolean{if(typeof value!=='boolean')fail('validation',`${field} must be boolean`);return value}
function array(value:unknown,field:string,max:number):readonly unknown[]{if(!Array.isArray(value)||value.length>max)fail('validation',`${field} must be a bounded array`);return value}
function jsonStorage(value:unknown,field='value',depth=0,budget={nodes:0,bytes:0}):FieldStorageValue{
  if(depth>64||++budget.nodes>100000)fail('validation',`${field} exceeds structural bounds`)
  if(value===null||typeof value==='boolean')return value
  if(typeof value==='number'){if(!Number.isFinite(value))fail('validation',`${field} contains non-finite number`);budget.bytes+=8;return value}
  if(typeof value==='string'){budget.bytes+=new TextEncoder().encode(value).byteLength;if(budget.bytes>16*1024*1024)fail('validation',`${field} exceeds byte bounds`);return value}
  if(Array.isArray(value))return Object.freeze(value.map((item,index)=>jsonStorage(item,`${field}.${index}`,depth+1,budget)))
  const source=plain(value,field),out:Record<string,FieldStorageValue>={}
  for(const key of Object.keys(source).sort()){if(key==='__proto__'||key==='constructor'||key==='prototype')fail('validation',`${field} contains forbidden key`);out[key]=jsonStorage(source[key],`${field}.${key}`,depth+1,budget)}
  return Object.freeze(out)
}
function preflight(value:unknown,limits:FieldsImportLimits,depth=0,budget={nodes:0}):void{
  if(depth>limits.maxDepth)fail('validation','manifest exceeds maximum depth')
  if(value===null||['string','number','boolean'].includes(typeof value))return
  if(typeof value!=='object'||value===null)fail('validation','manifest contains unsupported value')
  if(++budget.nodes>Math.max(1000,limits.maxDefinitions*20+limits.maxEntities*20+limits.maxRevisions*10+limits.maxBlockNodes*10))fail('validation','manifest exceeds node budget')
  if(Array.isArray(value)){for(const child of value)preflight(child,limits,depth+1,budget);return}
  if(Object.getPrototypeOf(value)!==Object.prototype)fail('validation','manifest must contain plain JSON objects')
  for(const [key,child] of Object.entries(value as Record<string,unknown>)){if(key==='__proto__'||key==='constructor'||key==='prototype')fail('validation','manifest contains forbidden key');preflight(child,limits,depth+1,budget)}
}
function ref(value:unknown,field:string):EntityRef{const row=plain(value,field);return Object.freeze({entityType:boundedString(row.entityType,`${field}.entityType`,256),entityId:boundedString(row.entityId,`${field}.entityId`,512)})}
function revisionsMap(value:unknown,field:string):Readonly<Record<string,number>>{const row=plain(value,field),out:Record<string,number>={};for(const [key,v] of Object.entries(row))out[boundedString(key,field,256)]=positive(v,`${field}.${key}`);return Object.freeze(out)}
function fieldPath(value:unknown,field:string):FieldPath{const parts=array(value,field,64);if(parts.length===0)fail('validation',`${field} must not be empty`);return Object.freeze(parts.map((item,index)=>typeof item==='number'&&Number.isSafeInteger(item)&&item>=0?item:boundedString(item,`${field}.${index}`,256)))}
function dateString(value:unknown,field:string):string{const raw=boundedString(value,field,128),date=new Date(raw);if(Number.isNaN(date.getTime()))fail('validation',`${field} must be an ISO timestamp`);return date.toISOString()}

function sanitizeCustomChildren(value:unknown,registry:FieldTypeRegistry):unknown{
  const row=plain(value,'field'),type=boundedString(row.type,'field.type',256),copy:Record<string,unknown>={...row}
  if(!registry.has(type))fail('validation',`unregistered field type ${type}`,'field.type')
  const settings=typeof row.settings==='object'&&row.settings!==null&&!Array.isArray(row.settings)?{...(row.settings as Record<string,unknown>)}:row.settings
  if(type==='group'||type==='repeater'){
    const s=plain(settings,'field.settings');copy.settings={...s,fields:array(s.fields,'field.settings.fields',1000).map((child)=>sanitizeCustomChildren(child,registry))}
  }else if(type==='flexible'){
    const s=plain(settings,'field.settings');copy.settings={...s,layouts:array(s.layouts,'field.settings.layouts',1000).map((layout)=>{const l=plain(layout,'layout');return {...l,fields:array(l.fields,'layout.fields',1000).map((child)=>sanitizeCustomChildren(child,registry))}})}
  }
  if(!registry.builtInTypes.has(type as never)) return {...copy,type:'text',settings:{}}
  return copy
}
function validateFieldTree(value:unknown,registry:FieldTypeRegistry,depth=0):ResolvedAnyFieldDefinition{
  if(depth>12)fail('validation','field definition nesting exceeds maximum depth')
  const raw=plain(value,'field'),type=boundedString(raw.type,'field.type',256),descriptor=registry.resolve(type)
  if(!descriptor)fail('validation',`unregistered field type ${type}`,'field.type')
  const parsed=descriptor.parseDefinition(registry.builtInTypes.has(type as never)?sanitizeCustomChildren(raw,registry):raw,FIELD_DEFINITION_LIMITS)
  if(parsed.type!==type)fail('validation','field descriptor changed type identity')
  const settings=raw.settings
  if(type==='group'||type==='repeater')for(const child of array(plain(settings,'field.settings').fields,'field.settings.fields',1000))validateFieldTree(child,registry,depth+1)
  if(type==='flexible')for(const layout of array(plain(settings,'field.settings').layouts,'field.settings.layouts',1000))for(const child of array(plain(layout,'layout').fields,'layout.fields',1000))validateFieldTree(child,registry,depth+1)
  if(!registry.builtInTypes.has(type as never)) registry.parseDefinition(raw,FIELD_DEFINITION_LIMITS)
  return Object.freeze({...raw}) as unknown as ResolvedAnyFieldDefinition
}
function parseImportGroup(value:unknown,registry:FieldTypeRegistry):FieldGroup{
  const raw=plain(value,'group'),fields=array(raw.fields,'group.fields',1000)
  for(const field of fields)validateFieldTree(field,registry)
  const sanitized={...raw,fields:fields.map((field)=>sanitizeCustomChildren(field,registry))}
  const validated=parseFieldGroupDefinition(sanitized)
  return Object.freeze({...validated,fields:Object.freeze(fields.map((field)=>Object.freeze({...plain(field,'field')})))}) as unknown as FieldGroup
}
function fieldHasPassword(field:AnyFieldDefinition):boolean{if(field.type==='password')return true;if(field.type==='group'||field.type==='repeater')return (field.settings as {fields:readonly AnyFieldDefinition[]}).fields.some(fieldHasPassword);if(field.type==='flexible')return (field.settings as {layouts:readonly {fields:readonly AnyFieldDefinition[]}[]}).layouts.some((layout)=>layout.fields.some(fieldHasPassword));return false}
function assertNoPasswordValue(group:FieldGroup,value:unknown,field:string):void{
  const map=plain(value,field)
  const walk=(definitions:readonly AnyFieldDefinition[],source:Record<string,unknown>,path:string)=>{for(const definition of definitions){if(definition.type==='password'&&Object.prototype.hasOwnProperty.call(source,definition.key))fail('validation',`imported password value is forbidden at ${path}.${definition.key}`);const child=source[definition.key];if(child===undefined)continue;if(definition.type==='group'&&typeof child==='object'&&child!==null&&!Array.isArray(child))walk((definition.settings as {fields:readonly AnyFieldDefinition[]}).fields,child as Record<string,unknown>,`${path}.${definition.key}`);if(definition.type==='repeater'&&Array.isArray(child))for(const [index,row] of child.entries()){const r=plain(row,`${path}.${definition.key}.${index}`);if(r.values&&typeof r.values==='object')walk((definition.settings as {fields:readonly AnyFieldDefinition[]}).fields,r.values as Record<string,unknown>,`${path}.${definition.key}.${index}`)}if(definition.type==='flexible'&&Array.isArray(child))for(const [index,row] of child.entries()){const r=plain(row,`${path}.${definition.key}.${index}`),layout=(definition.settings as {layouts:readonly {key:string;fields:readonly AnyFieldDefinition[]}[]}).layouts.find((item)=>item.key===r.layoutKey);if(layout&&r.values&&typeof r.values==='object')walk(layout.fields,r.values as Record<string,unknown>,`${path}.${definition.key}.${index}`)}}}
  walk(group.fields,map,field)
}

function entityValues(value:unknown,field:string,groups:ReadonlyMap<string,FieldGroup>):EntityFieldValueMap{
  const source=plain(value,field),out:Record<string,Readonly<Record<string,unknown>>>={}
  for(const [groupKey,raw] of Object.entries(source)){
    const group=groups.get(groupKey);if(!group)fail('validation',`${field} references unknown group ${groupKey}`)
    assertNoPasswordValue(group,raw,`${field}.${groupKey}`)
    out[groupKey]=Object.freeze({...plain(raw,`${field}.${groupKey}`)})
  }
  return Object.freeze(out)
}
function countBlockNodes(document:BlockDocument):number{let count=0;const walk=(nodes:BlockDocument['roots'],depth=0)=>{if(depth>64)fail('validation','block document exceeds parser depth');for(const node of nodes){count++;if(node.kind==='inline'&&node.children)walk(node.children,depth+1)}};walk(document.roots);return count}
function blockDocument(value:unknown,limits:FieldsImportLimits,field:string):BlockDocument{
  const serialized=JSON.stringify(jsonStorage(value,field));const document=parseBlockDocument(serialized,{...DEFAULT_BLOCK_POLICY,maxBytes:Math.min(DEFAULT_BLOCK_POLICY.maxBytes,limits.maxBytes),maxNodes:limits.maxBlockNodes,maxDepth:Math.min(DEFAULT_BLOCK_POLICY.maxDepth,limits.maxDepth)})
  if(countBlockNodes(document)>limits.maxBlockNodes)fail('validation',`${field} exceeds block node limit`)
  return document
}
function sourceKey(refValue:EntityRef):string{return `${refValue.entityType}:${refValue.entityId}`}

export async function parseFieldsImport(input:Uint8Array,limitsInput:FieldsImportLimits,registry:FieldTypeRegistry):Promise<ParsedFieldsImport>{
  const limits={...DEFAULT_IMPORT_LIMITS,...limitsInput}
  for(const [name,value] of Object.entries(limits))if(!Number.isSafeInteger(value)||value<1)fail('validation',`limits.${name} must be a positive safe integer`)
  if(input.byteLength>limits.maxBytes)fail('validation','import exceeds maximum byte size')
  let text:string
  try{text=new TextDecoder('utf-8',{fatal:true}).decode(input)}catch{fail('validation','import is not valid UTF-8')}
  let raw:unknown
  try{raw=JSON.parse(text) as unknown}catch{fail('validation','import is not valid JSON')}
  preflight(raw,limits)
  const root=plain(raw,'manifest')
  if(root.version!==1)fail('validation','manifest.version must be 1')
  const definitions=plain(root.definitions,'manifest.definitions')
  const groupRows=array(definitions.groups,'manifest.definitions.groups',limits.maxDefinitions)
  const groups:FieldGroup[]=[],groupByKey=new Map<string,FieldGroup>()
  for(const item of groupRows){const group=parseImportGroup(item,registry);if(groupByKey.has(group.key))fail('validation',`duplicate group key ${group.key}`);groupByKey.set(group.key,group);groups.push(group)}
  const versionRows=array(definitions.versions,'manifest.definitions.versions',limits.maxDefinitions)
  const versions:FieldDefinitionVersionExport[]=[],versionIds=new Set<string>()
  for(const [index,item] of versionRows.entries()){
    const row=plain(item,`definitions.versions.${index}`),definition=parseImportGroup(row.definition,registry),groupKey=boundedString(row.groupKey,`definitions.versions.${index}.groupKey`,128),revision=positive(row.revision,`definitions.versions.${index}.revision`),canonicalHash=boundedString(row.canonicalHash,`definitions.versions.${index}.canonicalHash`,128),origin=row.origin
    if(definition.key!==groupKey||!['code','db','import'].includes(String(origin)))fail('validation','definition version identity is invalid')
    const actualHash=await canonicalFieldGroupHash(definition);if(actualHash!==canonicalHash)fail('validation',`definition version hash mismatch for ${groupKey}@${revision}`)
    const id=`${groupKey}@${revision}`;if(versionIds.has(id))fail('validation',`duplicate definition version ${id}`);versionIds.add(id)
    versions.push(Object.freeze({groupKey,revision,canonicalHash,definition,createdAt:dateString(row.createdAt,`definitions.versions.${index}.createdAt`),origin:origin as FieldDefinitionVersionExport['origin']}))
  }
  for(const group of groups){const candidates=versions.filter((version)=>version.groupKey===group.key);if(candidates.length===0)fail('validation',`group ${group.key} has no immutable definition version`);const currentHash=await canonicalFieldGroupHash(group);if(!candidates.some((version)=>version.canonicalHash===currentHash))fail('validation',`group ${group.key} current bytes are absent from immutable versions`)}

  const optionRows=array(definitions.optionsPages,'manifest.definitions.optionsPages',limits.maxDefinitions)
  const optionsPages:OptionsPageDefinition[]=[],optionKeys=new Set<string>()
  for(const item of optionRows){const page=__optionsLifecycleInternals.normalizeOptionsPage(item as never);if(optionKeys.has(page.key))fail('validation',`duplicate options page ${page.key}`);optionKeys.add(page.key);optionsPages.push(page)}
  for(const page of optionsPages)if(page.parentKey&&!optionKeys.has(page.parentKey))fail('validation',`options page ${page.key} references missing parent ${page.parentKey}`)

  const blockRows=array(definitions.blocks,'manifest.definitions.blocks',limits.maxDefinitions)
  const blocks:FieldBlockDefinition[]=[],blockKeys=new Set<string>()
  for(const item of blockRows){const block=__fieldBlockLifecycleInternals.normalizeDefinition(item as never);if(blockKeys.has(block.key))fail('validation',`duplicate block ${block.key}`);for(const groupKey of block.fieldGroupKeys)if(!groupByKey.has(groupKey))fail('validation',`block ${block.key} references missing group ${groupKey}`);blockKeys.add(block.key);blocks.push(block)}
  const blockVersionRows=array(definitions.blockVersions,'manifest.definitions.blockVersions',limits.maxDefinitions)
  const blockVersions:FieldBlockDefinitionVersionExport[]=[],blockVersionIds=new Set<string>()
  for(const [index,item] of blockVersionRows.entries()){
    const row=plain(item,`definitions.blockVersions.${index}`),definition=__fieldBlockLifecycleInternals.normalizeDefinition(row.definition as never),blockKey=boundedString(row.blockKey,`definitions.blockVersions.${index}.blockKey`,128),revision=positive(row.revision,`definitions.blockVersions.${index}.revision`),canonicalHash=boundedString(row.canonicalHash,`definitions.blockVersions.${index}.canonicalHash`,128),origin=row.origin
    if(definition.key!==blockKey||!['code','db','import'].includes(String(origin)))fail('validation','block definition version identity is invalid')
    if(await fieldsRequestHash(definition)!==canonicalHash)fail('validation',`block definition version hash mismatch for ${blockKey}@${revision}`)
    const id=`${blockKey}@${revision}`;if(blockVersionIds.has(id))fail('validation',`duplicate block definition version ${id}`);blockVersionIds.add(id)
    blockVersions.push(Object.freeze({blockKey,revision,canonicalHash,definition,createdAt:dateString(row.createdAt,`definitions.blockVersions.${index}.createdAt`),origin:origin as FieldBlockDefinitionVersionExport['origin']}))
  }
  for(const block of blocks){const candidates=blockVersions.filter((version)=>version.blockKey===block.key);if(candidates.length===0)fail('validation',`block ${block.key} has no immutable definition version`);const currentHash=await fieldsRequestHash(block);if(!candidates.some((version)=>version.canonicalHash===currentHash))fail('validation',`block ${block.key} current bytes are absent from immutable versions`)}

  const entityRows=array(root.entities??[],'manifest.entities',limits.maxEntities),entities:NonNullable<FieldsExportManifest['entities']>[number][]=[],entityKeys=new Set<string>()
  for(const [index,item] of entityRows.entries()){
    const row=plain(item,`entities.${index}`),sourceRef=ref(row.sourceRef,`entities.${index}.sourceRef`),definitionVersions=revisionsMap(row.definitionVersions,`entities.${index}.definitionVersions`),pinned=new Map<string,FieldGroup>()
    for(const [groupKey,revision] of Object.entries(definitionVersions)){const version=versions.find((candidate)=>candidate.groupKey===groupKey&&candidate.revision===revision);if(!version)fail('validation',`entity references missing definition ${groupKey}@${revision}`);pinned.set(groupKey,version.definition)}
    const key=sourceKey(sourceRef);if(entityKeys.has(key))fail('validation',`duplicate entity ${key}`);entityKeys.add(key)
    entities.push(Object.freeze({sourceRef,values:entityValues(row.values,`entities.${index}.values`,pinned),definitionVersions}))
  }

  const revisionRows=array(root.revisions??[],'manifest.revisions',limits.maxRevisions),revisions:FieldsRevisionExport[]=[],revisionKeys=new Set<string>()
  for(const [index,item] of revisionRows.entries()){
    const row=plain(item,`revisions.${index}`),sourceRevisionKey=boundedString(row.sourceRevisionKey,`revisions.${index}.sourceRevisionKey`,512),sourceRef=ref(row.sourceRef,`revisions.${index}.sourceRef`),definitionVersions=revisionsMap(row.definitionVersions,`revisions.${index}.definitionVersions`),pinned=new Map<string,FieldGroup>()
    if(revisionKeys.has(sourceRevisionKey))fail('validation',`duplicate revision ${sourceRevisionKey}`);revisionKeys.add(sourceRevisionKey)
    for(const [groupKey,revision] of Object.entries(definitionVersions)){const version=versions.find((candidate)=>candidate.groupKey===groupKey&&candidate.revision===revision);if(!version)fail('validation',`revision references missing definition ${groupKey}@${revision}`);pinned.set(groupKey,version.definition)}
    const block= row.blockDocument===undefined?undefined:blockDocument(row.blockDocument,limits,`revisions.${index}.blockDocument`)
    revisions.push(Object.freeze({sourceRevisionKey,sourceRef,parentRevisionKey:boundedString(row.parentRevisionKey,`revisions.${index}.parentRevisionKey`,512),definitionVersions,values:entityValues(row.values,`revisions.${index}.values`,pinned),...(block?{blockDocument:block}:{}),createdAt:dateString(row.createdAt,`revisions.${index}.createdAt`),createdBySourceId:boundedString(row.createdBySourceId,`revisions.${index}.createdBySourceId`,512),sourceVersion:positive(row.sourceVersion,`revisions.${index}.sourceVersion`)}))
  }

  const optionsRows=array(root.options??[],'manifest.options',limits.maxEntities),options:NonNullable<FieldsExportManifest['options']>[number][]=[],optionValueKeys=new Set<string>()
  for(const [index,item] of optionsRows.entries()){
    const row=plain(item,`options.${index}`),pageKey=boundedString(row.pageKey,`options.${index}.pageKey`,128),definitionVersions=revisionsMap(row.definitionVersions,`options.${index}.definitionVersions`),pinned=new Map<string,FieldGroup>()
    if(optionValueKeys.has(pageKey)||!optionKeys.has(pageKey))fail('validation',`invalid or duplicate options values page ${pageKey}`);optionValueKeys.add(pageKey)
    for(const [groupKey,revision] of Object.entries(definitionVersions)){const version=versions.find((candidate)=>candidate.groupKey===groupKey&&candidate.revision===revision);if(!version)fail('validation',`options values reference missing definition ${groupKey}@${revision}`);pinned.set(groupKey,version.definition)}
    options.push(Object.freeze({pageKey,values:entityValues(row.values,`options.${index}.values`,pinned),definitionVersions,revision:positive(row.revision,`options.${index}.revision`)}))
  }

  const blockDocumentRows=array(root.blockDocuments??[],'manifest.blockDocuments',limits.maxEntities),blockDocuments:NonNullable<FieldsExportManifest['blockDocuments']>[number][]=[],blockDocumentKeys=new Set<string>(),blockBudget={nodes:0}
  for(const [index,item] of blockDocumentRows.entries()){
    const row=plain(item,`blockDocuments.${index}`),sourceRef=ref(row.sourceRef,`blockDocuments.${index}.sourceRef`),document=blockDocument(row.document,limits,`blockDocuments.${index}.document`),definitionVersions=revisionsMap(row.definitionVersions,`blockDocuments.${index}.definitionVersions`),key=sourceKey(sourceRef)
    if(blockDocumentKeys.has(key))fail('validation',`duplicate block document ${key}`);blockDocumentKeys.add(key);blockBudget.nodes+=countBlockNodes(document);if(blockBudget.nodes>limits.maxBlockNodes)fail('validation','manifest exceeds total block node limit')
    blockDocuments.push(Object.freeze({sourceRef,document,version:boundedString(row.version,`blockDocuments.${index}.version`,128),definitionVersions}))
  }

  const omissionRows=array(root.omissions??[],'manifest.omissions',limits.maxEntities+limits.maxRevisions),omissions:FieldsExportManifest['omissions'][number][]=[]
  for(const [index,item] of omissionRows.entries()){const row=plain(item,`omissions.${index}`),reason=row.reason;if(reason!=='sensitive'&&reason!=='unauthorized')fail('validation','omission reason is invalid');omissions.push(Object.freeze({sourceRef:ref(row.sourceRef,`omissions.${index}.sourceRef`),fieldPath:fieldPath(row.fieldPath,`omissions.${index}.fieldPath`),reason}))}

  const normalized:FieldsExportManifest=Object.freeze({version:1,definitions:Object.freeze({groups:Object.freeze(groups),versions:Object.freeze(versions),optionsPages:Object.freeze(optionsPages),blocks:Object.freeze(blocks),blockVersions:Object.freeze(blockVersions)}),...(entities.length?{entities:Object.freeze(entities)}:{}),...(revisions.length?{revisions:Object.freeze(revisions)}:{}),...(options.length?{options:Object.freeze(options)}:{}),...(blockDocuments.length?{blockDocuments:Object.freeze(blockDocuments)}:{}),omissions:Object.freeze(omissions)})
  return Object.freeze({manifestHash:await fieldsRequestHash(normalized),registryVersion:registry.version,manifest:normalized})
}

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 invalid rows')}
async function execRows<Row extends Record<string,unknown>>(db:FieldsReader,query:SQLWrapper):Promise<readonly Row[]>{return rowsOf<Row>(await db.execute(query))}
function normalizeScope(scope:FieldsImportScope):FieldsImportScope{
  const tenantKey=boundedString(scope.tenantKey,'scope.tenantKey',256),types=[...scope.entityTypes]
  if(types.length===0||types.length>256)fail('validation','scope.entityTypes must be bounded and non-empty')
  const normalized=types.map((type,index)=>boundedString(type,`scope.entityTypes.${index}`,256)).sort()
  if(new Set(normalized).size!==normalized.length)fail('validation','scope.entityTypes contains duplicates')
  return Object.freeze({tenantKey,entityTypes:Object.freeze(normalized)})
}
async function assertScopeAuthorized(context:FieldsContext,scope:FieldsImportScope):Promise<string>{
  await assertFieldsAuthorized(context,'manageDefinitions',{entityType:'fields-import-scope',entityId:scope.tenantKey})
  const migration=await assertDefinitionMigration(context,scope.entityTypes)
  for(const entityType of scope.entityTypes)await assertFieldsAuthorized(context,'write',{entityType,entityId:'*'})
  return migration.policyVersion
}
async function assertCodeRegistry(codeRegistry:CodeFieldsRegistrySnapshot):Promise<void>{
  const actual=await fieldsRequestHash({version:codeRegistry.version,groups:codeRegistry.groups,optionsPages:codeRegistry.optionsPages,blocks:codeRegistry.blocks})
  if(actual!==codeRegistry.canonicalHash)fail('validation','code registry canonical hash mismatch')
}
function mappingKey(kind:string,value:string):string{return `${kind}:${value}`}

function hashUuid(hex:string):string{const clean=hex.slice(0,32).padEnd(32,'0').split('');clean[12]='4';clean[16]=(['8','9','a','b'][Number.parseInt(clean[16]??'0',16)%4])!;const value=clean.join('');return `${value.slice(0,8)}-${value.slice(8,12)}-${value.slice(12,16)}-${value.slice(16,20)}-${value.slice(20,32)}`}
async function deterministicRevisionId(manifestHash:string,sourceRevisionKey:string):Promise<string>{return hashUuid(await fieldsRequestHash({kind:'fields-import-revision',manifestHash,sourceRevisionKey}))}

async function remapFieldMap(group:FieldGroup,input:Readonly<Record<string,unknown>>,remappers:FieldsImportRemappers,mappings:Record<string,string>,conflicts:FieldsImportConflict[],sourceKeyValue:string):Promise<Readonly<Record<string,unknown>>>{
  const out:Record<string,unknown>={...input}
  const remapFields=async(definitions:readonly AnyFieldDefinition[],source:Record<string,unknown>,target:Record<string,unknown>,path:string):Promise<void>=>{
    for(const definition of definitions){
      const value=source[definition.key];if(value===undefined)continue
      const fieldPathValue=`${path}.${definition.key}`
      if(definition.type==='password')fail('validation',`password import is forbidden at ${fieldPathValue}`)
      if(definition.type==='group'&&value&&typeof value==='object'&&!Array.isArray(value)){const child={...(value as Record<string,unknown>)};await remapFields((definition.settings as {fields:readonly AnyFieldDefinition[]}).fields,value as Record<string,unknown>,child,fieldPathValue);target[definition.key]=Object.freeze(child);continue}
      if(definition.type==='repeater'&&Array.isArray(value)){const rows=[];for(const [index,rowRaw] of value.entries()){const row=plain(rowRaw,`${fieldPathValue}.${index}`),values=plain(row.values,`${fieldPathValue}.${index}.values`),mapped={...values};await remapFields((definition.settings as {fields:readonly AnyFieldDefinition[]}).fields,values,mapped,`${fieldPathValue}.${index}.values`);rows.push(Object.freeze({...row,values:Object.freeze(mapped)}))}target[definition.key]=Object.freeze(rows);continue}
      if(definition.type==='flexible'&&Array.isArray(value)){const rows=[];for(const [index,rowRaw] of value.entries()){const row=plain(rowRaw,`${fieldPathValue}.${index}`),layout=(definition.settings as {layouts:readonly {key:string;fields:readonly AnyFieldDefinition[]}[]}).layouts.find((item)=>item.key===row.layoutKey);if(!layout)fail('validation',`unknown flexible layout at ${fieldPathValue}.${index}`);const values=plain(row.values,`${fieldPathValue}.${index}.values`),mapped={...values};await remapFields(layout.fields,values,mapped,`${fieldPathValue}.${index}.values`);rows.push(Object.freeze({...row,values:Object.freeze(mapped)}))}target[definition.key]=Object.freeze(rows);continue}
      if(definition.type==='clone')continue
      if(definition.type==='entity'||definition.type==='entityLink'){
        if(value===null){target[definition.key]=null;continue}
        const sourceRef=ref(value,fieldPathValue),mapped=await remappers.entity(sourceRef)
        if(!mapped){conflicts.push(Object.freeze({section:'entities',sourceKey:sourceKeyValue,kind:'missingDependency'}));continue}
        mappings[mappingKey('entity',sourceKey(sourceRef))]=sourceKey(mapped);target[definition.key]=mapped;continue
      }
      if(definition.type==='relationship'){
        if(!Array.isArray(value))fail('validation',`${fieldPathValue} must be an array`);const mappedValues=[]
        for(const item of value){const sourceRef=ref(item,fieldPathValue),mapped=await remappers.entity(sourceRef);if(!mapped){conflicts.push(Object.freeze({section:'entities',sourceKey:sourceKeyValue,kind:'missingDependency'}));continue}mappings[mappingKey('entity',sourceKey(sourceRef))]=sourceKey(mapped);mappedValues.push(mapped)}target[definition.key]=Object.freeze(mappedValues);continue
      }
      if(definition.type==='image'||definition.type==='file'){
        if(value===null){target[definition.key]=null;continue}const media=plain(value,fieldPathValue) as unknown as MediaFieldRef,mapped=await remappers.media(media);if(!mapped){conflicts.push(Object.freeze({section:'entities',sourceKey:sourceKeyValue,kind:'missingDependency'}));continue}mappings[mappingKey('media',media.id)]=mapped.id;target[definition.key]=mapped;continue
      }
      if(definition.type==='gallery'){
        if(!Array.isArray(value))fail('validation',`${fieldPathValue} must be an array`);const mappedValues=[];for(const item of value){const media=plain(item,fieldPathValue) as unknown as MediaFieldRef,mapped=await remappers.media(media);if(!mapped){conflicts.push(Object.freeze({section:'entities',sourceKey:sourceKeyValue,kind:'missingDependency'}));continue}mappings[mappingKey('media',media.id)]=mapped.id;mappedValues.push(mapped)}target[definition.key]=Object.freeze(mappedValues);continue
      }
      if(definition.type==='taxonomy'){
        if(value===null){target[definition.key]=null;continue}const items=Array.isArray(value)?value:[value],mappedValues=[];for(const item of items){const term=plain(item,fieldPathValue) as unknown as TermFieldRef,mapped=await remappers.term(term);if(!mapped){conflicts.push(Object.freeze({section:'entities',sourceKey:sourceKeyValue,kind:'missingDependency'}));continue}mappings[mappingKey('term',`${term.taxonomy}:${term.id}`)]=`${mapped.taxonomy}:${mapped.id}`;mappedValues.push(mapped)}target[definition.key]=Array.isArray(value)?Object.freeze(mappedValues):(mappedValues[0]??null);continue
      }
      if(definition.type==='user'){
        if(value===null){target[definition.key]=null;continue}const items=Array.isArray(value)?value:[value],mappedValues=[];for(const item of items){const user=plain(item,fieldPathValue),id=boundedString(user.id,`${fieldPathValue}.id`,512),mapped=await remappers.user(id);if(!mapped){conflicts.push(Object.freeze({section:'entities',sourceKey:sourceKeyValue,kind:'missingDependency'}));continue}mappings[mappingKey('user',id)]=mapped;mappedValues.push(Object.freeze({id:mapped}))}target[definition.key]=Array.isArray(value)?Object.freeze(mappedValues):(mappedValues[0]??null);continue
      }
    }
  }
  await remapFields(group.fields,input,out,group.key)
  return Object.freeze(out)
}
async function remapEntityValues(values:EntityFieldValueMap,versions:Readonly<Record<string,number>>,manifest:FieldsExportManifest,remappers:FieldsImportRemappers,mappings:Record<string,string>,conflicts:FieldsImportConflict[],sourceKeyValue:string):Promise<EntityFieldValueMap>{
  const out:Record<string,Readonly<Record<string,unknown>>>={}
  for(const [groupKey,map] of Object.entries(values)){const revision=versions[groupKey];if(!revision)fail('validation',`missing pinned version for ${groupKey}`);const definition=manifest.definitions.versions.find((item)=>item.groupKey===groupKey&&item.revision===revision)?.definition;if(!definition)fail('integrity',`missing manifest definition ${groupKey}@${revision}`);out[groupKey]=await remapFieldMap(definition,map,remappers,mappings,conflicts,sourceKeyValue)}
  return Object.freeze(out)
}
async function remapBlockReusableIds(document:BlockDocument,remappers:FieldsImportRemappers,mappings:Record<string,string>,conflicts:FieldsImportConflict[],sourceKeyValue:string):Promise<BlockDocument>{
  const walk=async(nodes:BlockDocument['roots']):Promise<BlockDocument['roots']>=>Object.freeze(await Promise.all(nodes.map(async(node)=>{if(node.kind==='reusable'){const mapped=await remappers.reusableBlock(node.reusableId);if(!mapped){conflicts.push(Object.freeze({section:'blockDocuments',sourceKey:sourceKeyValue,kind:'missingDependency'}));return node}mappings[mappingKey('reusable',node.reusableId)]=mapped;return Object.freeze({...node,reusableId:mapped})}return Object.freeze({...node,...(node.children?{children:await walk(node.children)}:{})})})))
  return Object.freeze({version:1,roots:await walk(document.roots)})
}

async function valueCorpusVersion(db:FieldsReader,scope:FieldsImportScope):Promise<string>{
  const types=scope.entityTypes
  const typeSql=sql.join(types.map((type)=>sql`${type}`),sql`, `)
  const nodes=await execRows<Record<string,unknown>>(db,sql`SELECT entity_type AS "entityType",entity_id AS "entityId",group_key AS "groupKey",definition_revision AS "definitionRevision",field_key AS "fieldKey",path,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",host_session_id AS "hostSessionId" FROM field_value_nodes WHERE entity_type IN (${typeSql}) OR entity_type='options' ORDER BY entity_type,entity_id,group_key,path`)
  const documents=await execRows<Record<string,unknown>>(db,sql`SELECT entity_type AS "entityType",entity_id AS "entityId",version,document,definition_versions AS "definitionVersions",host_session_id AS "hostSessionId" FROM field_block_documents WHERE entity_type IN (${typeSql}) ORDER BY entity_type,entity_id`)
  const revisions=await execRows<Record<string,unknown>>(db,sql`SELECT id,entity_type AS "entityType",entity_id AS "entityId",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",host_session_id AS "hostSessionId" FROM field_revisions WHERE entity_type IN (${typeSql}) ORDER BY entity_type,entity_id,created_at,id`)
  const groups=await execRows<Record<string,unknown>>(db,sql`SELECT key,origin,version,active,current_revision AS "currentRevision",canonical_hash AS "canonicalHash",host_session_id AS "hostSessionId" FROM field_group_definitions ORDER BY key`)
  const groupVersions=await execRows<Record<string,unknown>>(db,sql`SELECT group_key AS "groupKey",revision,canonical_hash AS "canonicalHash",origin,host_session_id AS "hostSessionId" FROM field_definition_versions ORDER BY group_key,revision`)
  const options=await execRows<Record<string,unknown>>(db,sql`SELECT key,origin,version,active,current_revision AS "currentRevision",canonical_hash AS "canonicalHash",host_session_id AS "hostSessionId" FROM field_options_definitions ORDER BY key`)
  const optionVersions=await execRows<Record<string,unknown>>(db,sql`SELECT options_key AS "optionsKey",revision,canonical_hash AS "canonicalHash",origin,host_session_id AS "hostSessionId" FROM field_options_definition_versions ORDER BY options_key,revision`)
  const blocks=await execRows<Record<string,unknown>>(db,sql`SELECT key,origin,version,active,current_revision AS "currentRevision",canonical_hash AS "canonicalHash",host_session_id AS "hostSessionId" FROM field_block_definitions ORDER BY key`)
  const blockVersions=await execRows<Record<string,unknown>>(db,sql`SELECT block_key AS "blockKey",revision,canonical_hash AS "canonicalHash",origin,host_session_id AS "hostSessionId" FROM field_block_definition_versions ORDER BY block_key,revision`)
  return fieldsRequestHash({scope,nodes,documents,revisions,groups,groupVersions,options,optionVersions,blocks,blockVersions})
}

export async function planFieldsImport(db:FieldsReader,scopeInput:FieldsImportScope,parsed:ParsedFieldsImport,policy:FieldsImportConflictPolicy,remappers:FieldsImportRemappers,registry:FieldTypeRegistry,codeRegistry:CodeFieldsRegistrySnapshot,context:FieldsContext):Promise<FieldsImportPlan>{
  const scope=normalizeScope(scopeInput)
  if(!['reject','preserveDestination','replaceMapped'].includes(policy))fail('validation','invalid import conflict policy')
  const authorizationPolicyVersion=await assertScopeAuthorized(context,scope)
  if(parsed.registryVersion!==registry.version)fail('stale-version','field type registry changed since parse')
  await assertCodeRegistry(codeRegistry)
  const mappings:Record<string,string>={},conflicts:FieldsImportConflict[]=[]
  const entityOut:NonNullable<FieldsExportManifest['entities']>[number][]=[]
  for(const item of parsed.manifest.entities??[]){
    if(!scope.entityTypes.includes(item.sourceRef.entityType))fail('access-denied','import scope is unavailable')
    const mapped=await remappers.entity(item.sourceRef);if(!mapped){conflicts.push(Object.freeze({section:'entities',sourceKey:sourceKey(item.sourceRef),kind:'missingDependency'}));continue}
    if(!scope.entityTypes.includes(mapped.entityType))fail('access-denied','mapped entity falls outside import scope')
    await assertFieldsAuthorized(context,'write',mapped)
    mappings[mappingKey('entity',sourceKey(item.sourceRef))]=sourceKey(mapped)
    entityOut.push(Object.freeze({sourceRef:mapped,values:await remapEntityValues(item.values,item.definitionVersions,parsed.manifest,remappers,mappings,conflicts,sourceKey(item.sourceRef)),definitionVersions:item.definitionVersions}))
  }
  const revisionOut:FieldsRevisionExport[]=[]
  for(const item of parsed.manifest.revisions??[]){const mapped=await remappers.entity(item.sourceRef);if(!mapped){conflicts.push(Object.freeze({section:'revisions',sourceKey:item.sourceRevisionKey,kind:'missingDependency'}));continue}if(!scope.entityTypes.includes(mapped.entityType))fail('access-denied','mapped revision falls outside import scope');await assertFieldsAuthorized(context,'write',mapped);const user=await remappers.user(item.createdBySourceId);if(!user){conflicts.push(Object.freeze({section:'revisions',sourceKey:item.sourceRevisionKey,kind:'missingDependency'}));continue}mappings[mappingKey('user',item.createdBySourceId)]=user;revisionOut.push(Object.freeze({...item,sourceRef:mapped,createdBySourceId:user,values:await remapEntityValues(item.values,item.definitionVersions,parsed.manifest,remappers,mappings,conflicts,item.sourceRevisionKey),...(item.blockDocument?{blockDocument:await remapBlockReusableIds(item.blockDocument,remappers,mappings,conflicts,item.sourceRevisionKey)}:{})}))}
  const optionOut:NonNullable<FieldsExportManifest['options']>[number][]=[]
  for(const item of parsed.manifest.options??[]){await assertFieldsAuthorized(context,'write',{entityType:'options',entityId:item.pageKey});optionOut.push(Object.freeze({...item,values:await remapEntityValues(item.values,item.definitionVersions,parsed.manifest,remappers,mappings,conflicts,`options:${item.pageKey}`)}))}
  const blockOut:NonNullable<FieldsExportManifest['blockDocuments']>[number][]=[]
  for(const item of parsed.manifest.blockDocuments??[]){const mapped=await remappers.entity(item.sourceRef);if(!mapped){conflicts.push(Object.freeze({section:'blockDocuments',sourceKey:sourceKey(item.sourceRef),kind:'missingDependency'}));continue}if(!scope.entityTypes.includes(mapped.entityType))fail('access-denied','mapped block document falls outside import scope');await assertFieldsAuthorized(context,'write',mapped);blockOut.push(Object.freeze({...item,sourceRef:mapped,document:await remapBlockReusableIds(item.document,remappers,mappings,conflicts,sourceKey(item.sourceRef))}))}

  // Destination lookups happen only after scope + concrete branch authorization above.
  const codeGroupKeys=new Set(codeRegistry.groups.map((item)=>item.key)),codeOptionKeys=new Set(codeRegistry.optionsPages.map((item)=>item.key)),codeBlockKeys=new Set(codeRegistry.blocks.map((item)=>item.key))
  for(const key of codeGroupKeys)mappings[mappingKey('code-group',key)]='1'
  for(const key of codeOptionKeys)mappings[mappingKey('code-options',key)]='1'
  for(const key of codeBlockKeys)mappings[mappingKey('code-block',key)]='1'
  const groupRows=await execRows<Record<string,unknown>>(db,sql`SELECT key,origin,current_revision AS "currentRevision" FROM field_group_definitions`),destGroups=new Set(groupRows.map((row)=>String(row.key)))
  const optionRows=await execRows<Record<string,unknown>>(db,sql`SELECT key,origin,current_revision AS "currentRevision" FROM field_options_definitions`),destOptions=new Set(optionRows.map((row)=>String(row.key)))
  const blockRows=await execRows<Record<string,unknown>>(db,sql`SELECT key,origin,current_revision AS "currentRevision" FROM field_block_definitions`),destBlocks=new Set(blockRows.map((row)=>String(row.key)))
  const destinationGroupVersions=await execRows<Record<string,unknown>>(db,sql`SELECT group_key AS "groupKey",revision,canonical_hash AS "canonicalHash" FROM field_definition_versions ORDER BY group_key,revision`)
  for(const group of parsed.manifest.definitions.groups){
    const existing=groupRows.find((row)=>String(row.key)===group.key),sourceVersions=parsed.manifest.definitions.versions.filter((version)=>version.groupKey===group.key).sort((a,b)=>a.revision-b.revision),destVersions=destinationGroupVersions.filter((row)=>String(row.groupKey)===group.key),maxRevision=Math.max(0,...destVersions.map((row)=>Number(row.revision)))
    let allocated=0
    for(const sourceVersion of sourceVersions){
      const match=destVersions.find((row)=>String(row.canonicalHash)===sourceVersion.canonicalHash)
      const isCodeCollision=codeGroupKeys.has(group.key)
      if(existing&&policy==='preserveDestination'&&!isCodeCollision){if(match)mappings[mappingKey('grouprev',`${group.key}@${sourceVersion.revision}`)]=String(Number(match.revision));else conflicts.push(Object.freeze({section:'definitions',sourceKey:`${group.key}@${sourceVersion.revision}`,kind:'incompatibleVersion'}));continue}
      mappings[mappingKey('grouprev',`${group.key}@${sourceVersion.revision}`)]=String(maxRevision+(++allocated))
    }
  }
  const destinationBlockVersions=await execRows<Record<string,unknown>>(db,sql`SELECT block_key AS "blockKey",revision,canonical_hash AS "canonicalHash" FROM field_block_definition_versions ORDER BY block_key,revision`)
  for(const block of parsed.manifest.definitions.blocks){
    const existing=blockRows.find((row)=>String(row.key)===block.key),sourceVersions=parsed.manifest.definitions.blockVersions.filter((version)=>version.blockKey===block.key).sort((a,b)=>a.revision-b.revision),destVersions=destinationBlockVersions.filter((row)=>String(row.blockKey)===block.key),maxRevision=Math.max(0,...destVersions.map((row)=>Number(row.revision)))
    let allocated=0
    for(const sourceVersion of sourceVersions){const match=destVersions.find((row)=>String(row.canonicalHash)===sourceVersion.canonicalHash),isCodeCollision=codeBlockKeys.has(block.key);if(existing&&policy==='preserveDestination'&&!isCodeCollision){if(match)mappings[mappingKey('blockrev',`${block.key}@${sourceVersion.revision}`)]=String(Number(match.revision));else conflicts.push(Object.freeze({section:'definitions',sourceKey:`block:${block.key}@${sourceVersion.revision}`,kind:'incompatibleVersion'}));continue}mappings[mappingKey('blockrev',`${block.key}@${sourceVersion.revision}`)]=String(maxRevision+(++allocated))}
  }
  for(const page of parsed.manifest.definitions.optionsPages){const existing=optionRows.find((row)=>String(row.key)===page.key),isCodeCollision=codeOptionKeys.has(page.key);if(existing&&policy==='preserveDestination'&&!isCodeCollision)mappings[mappingKey('optionsrev',page.key)]=String(Number(existing.currentRevision));else mappings[mappingKey('optionsrev',page.key)]=String(Number(existing?.currentRevision??0)+1)}
  for(const group of parsed.manifest.definitions.groups)if(destGroups.has(group.key)||codeGroupKeys.has(group.key))conflicts.push(Object.freeze({section:'definitions',sourceKey:group.key,kind:'exists'}))
  for(const page of parsed.manifest.definitions.optionsPages)if(destOptions.has(page.key)||codeOptionKeys.has(page.key))conflicts.push(Object.freeze({section:'definitions',sourceKey:`options:${page.key}`,kind:'exists'}))
  for(const block of parsed.manifest.definitions.blocks)if(destBlocks.has(block.key)||codeBlockKeys.has(block.key))conflicts.push(Object.freeze({section:'definitions',sourceKey:`block:${block.key}`,kind:'exists'}))
  for(const item of entityOut){const found=await execRows<Record<string,unknown>>(db,sql`SELECT 1 AS present FROM field_value_nodes WHERE entity_type=${item.sourceRef.entityType} AND entity_id=${item.sourceRef.entityId} LIMIT 1`);if(found.length)conflicts.push(Object.freeze({section:'entities',sourceKey:sourceKey(item.sourceRef),kind:'exists'}))}
  for(const item of blockOut){const found=await execRows<Record<string,unknown>>(db,sql`SELECT 1 AS present FROM field_block_documents WHERE entity_type=${item.sourceRef.entityType} AND entity_id=${item.sourceRef.entityId} LIMIT 1`);if(found.length)conflicts.push(Object.freeze({section:'blockDocuments',sourceKey:sourceKey(item.sourceRef),kind:'exists'}))}
  if(policy==='reject'&&conflicts.length)fail('validation','import conflicts require an explicit non-reject policy')
  const transformed:FieldsExportManifest=Object.freeze({...parsed.manifest,...(entityOut.length?{entities:Object.freeze(entityOut)}:{entities:undefined}),...(revisionOut.length?{revisions:Object.freeze(revisionOut)}:{revisions:undefined}),...(optionOut.length?{options:Object.freeze(optionOut)}:{options:undefined}),...(blockOut.length?{blockDocuments:Object.freeze(blockOut)}:{blockDocuments:undefined})})
  const valueCorpus=await valueCorpusVersion(db,scope)
  for(const item of revisionOut)mappings[mappingKey('revision',item.sourceRevisionKey)]=await deterministicRevisionId(await fieldsRequestHash({manifestHash:parsed.manifestHash,valueCorpus,scope}),item.sourceRevisionKey)
  const requiredAuthority=Object.freeze(scope.entityTypes.map((entityType)=>Object.freeze({entityType,branches:Object.freeze(['manageDefinitions','migrateDefinitions','write'] as const)})))
  const counts=Object.freeze({definitions:parsed.manifest.definitions.groups.length+parsed.manifest.definitions.versions.length+parsed.manifest.definitions.optionsPages.length+parsed.manifest.definitions.blocks.length+parsed.manifest.definitions.blockVersions.length,entities:entityOut.length,revisions:revisionOut.length,options:optionOut.length,blockDocuments:blockOut.length})
  const planMaterial={manifestHash:parsed.manifestHash,registryVersion:registry.version,codeRegistryVersion:codeRegistry.version,codeRegistryHash:codeRegistry.canonicalHash,valueCorpusVersion:valueCorpus,authorizationPolicyVersion,scope,requiredAuthority,mappings:Object.freeze({...mappings}),counts,conflicts:Object.freeze(conflicts),omissions:parsed.manifest.omissions,policy,manifest:transformed}
  const planId=await fieldsRequestHash(planMaterial)
  return Object.freeze({planId,...planMaterial})
}

interface ImportJournalRow extends Record<string,unknown>{importId:string;hostSessionId:string;manifestHash:string;planHash:string;state:FieldsImportJournal['state'];version:number|string;nextSection:string|null;nextOffset:number|string|null;highWaterMark:string|null;payload:unknown}
interface PersistedImportPayload{readonly plan:FieldsImportPlan;readonly publishedValueCorpusVersion?:string}
interface StagedItem{readonly kind:string;readonly action:'apply'|'skip'|'shadow';readonly after:FieldStorageValue;readonly before:FieldStorageValue|null}
function sameScope(a:FieldsImportScope,b:FieldsImportScope):boolean{return a.tenantKey===b.tenantKey&&JSON.stringify([...a.entityTypes].sort())===JSON.stringify([...b.entityTypes].sort())}
function planMaterial(plan:FieldsImportPlan):Omit<FieldsImportPlan,'planId'>{const {planId:_planId,...rest}=plan;return rest}
async function assertPlanIdentity(plan:FieldsImportPlan):Promise<void>{if(await fieldsRequestHash(planMaterial(plan))!==plan.planId)fail('integrity','import plan identity is invalid');if(await fieldsRequestHash(plan.manifest)===plan.manifestHash){/* transformed manifests can equal source when no mappings */}}
function parsePersistedPayload(value:unknown):PersistedImportPayload{const raw=typeof value==='string'?(()=>{try{return JSON.parse(value) as unknown}catch{fail('integrity','import journal payload is invalid JSON')}})():value,row=plain(raw,'journal.payload');if(!row.plan||typeof row.plan!=='object')fail('integrity','import journal plan is missing');return Object.freeze({plan:row.plan as FieldsImportPlan,...(typeof row.publishedValueCorpusVersion==='string'?{publishedValueCorpusVersion:row.publishedValueCorpusVersion}:{})})}
function journalFromRow(row:ImportJournalRow,payload:PersistedImportPayload):FieldsImportJournal{return Object.freeze({importId:String(row.importId),version:String(row.version),planId:payload.plan.planId,manifestHash:String(row.manifestHash),codeRegistryVersion:payload.plan.codeRegistryVersion,codeRegistryHash:payload.plan.codeRegistryHash,staging:Object.freeze({hostSessionId:String(row.hostSessionId),visibility:'hiddenUntilPublish' as const}),scope:payload.plan.scope,authorizationPolicyVersion:payload.plan.authorizationPolicyVersion,state:row.state,nextSection:row.nextSection as FieldsImportSection|null,nextOffset:Number(row.nextOffset??0)})}
async function readJournal(db:FieldsReader,importId:string):Promise<{row:ImportJournalRow;payload:PersistedImportPayload;journal:FieldsImportJournal}|null>{const rows=await execRows<ImportJournalRow>(db,sql`SELECT import_id AS "importId",host_session_id AS "hostSessionId",manifest_hash AS "manifestHash",plan_hash AS "planHash",state,version,next_section AS "nextSection",next_offset AS "nextOffset",high_water_mark AS "highWaterMark",payload FROM fields_import_journal WHERE import_id=${importId} LIMIT 1`),row=rows[0];if(!row)return null;const payload=parsePersistedPayload(row.payload);return {row,payload,journal:journalFromRow(row,payload)}}
function commandVersion(value:string|null,field:string):number|null{if(value===null)return null;const n=Number(value);if(!Number.isSafeInteger(n)||n<0)fail('validation',`${field} must be a nonnegative integer string`);return n}
function validateCommand(command:FieldsImportCommand,start=false):FieldsImportScope{boundedString(command.operationId,'command.operationId',512);boundedString(command.expectedPlanId,'command.expectedPlanId',128);boundedString(command.expectedManifestHash,'command.expectedManifestHash',128);const scope=normalizeScope(command.scope);if(command.expectedSection!==null&&!SECTIONS.includes(command.expectedSection))fail('validation','command.expectedSection is invalid');if(command.expectedOffset!==null&&(!Number.isSafeInteger(command.expectedOffset)||command.expectedOffset<0))fail('validation','command.expectedOffset is invalid');if(start&&(command.expectedJournalVersion!==null||command.expectedSection!==null||command.expectedOffset!==null))fail('validation','start command journal coordinates must all be null');if(!start)commandVersion(command.expectedJournalVersion,'command.expectedJournalVersion');return scope}
async function authorizePlan(context:FieldsContext,plan:FieldsImportPlan):Promise<string>{const policy=await assertScopeAuthorized(context,plan.scope);for(const item of plan.manifest.entities??[])await assertFieldsAuthorized(context,'write',item.sourceRef);for(const item of plan.manifest.revisions??[])await assertFieldsAuthorized(context,'write',item.sourceRef);for(const item of plan.manifest.blockDocuments??[])await assertFieldsAuthorized(context,'write',item.sourceRef);for(const item of plan.manifest.options??[])await assertFieldsAuthorized(context,'write',{entityType:'options',entityId:item.pageKey});return policy}
async function assertRuntimeBindings(db:FieldsReader,plan:FieldsImportPlan,registry:FieldTypeRegistry,codeRegistry:CodeFieldsRegistrySnapshot,checkCorpus:boolean):Promise<void>{if(registry.version!==plan.registryVersion)fail('stale-plan','field type registry changed');await assertCodeRegistry(codeRegistry);if(codeRegistry.version!==plan.codeRegistryVersion||codeRegistry.canonicalHash!==plan.codeRegistryHash)fail('stale-plan','code fields registry changed');if(checkCorpus&&await valueCorpusVersion(db,plan.scope)!==plan.valueCorpusVersion)fail('stale-plan','destination value corpus changed since plan')}
function countsForSection(plan:FieldsImportPlan,section:FieldsImportSection):number{return sectionItems(plan,section).length}
function firstSection(plan:FieldsImportPlan,startIndex=0):FieldsImportSection|null{for(let i=startIndex;i<SECTIONS.length;i++)if(countsForSection(plan,SECTIONS[i]!)>0)return SECTIONS[i]!;return null}
function sectionItems(plan:FieldsImportPlan,section:FieldsImportSection):readonly {kind:string;data:unknown;key:string}[]{
  const m=plan.manifest
  if(section==='definitions')return Object.freeze([...m.definitions.versions.map((data)=>({kind:'groupVersion',data,key:`${data.groupKey}@${data.revision}`})),...m.definitions.groups.map((data)=>({kind:'groupCurrent',data,key:data.key}))])
  if(section==='optionsPages')return Object.freeze(m.definitions.optionsPages.map((data)=>({kind:'optionsPage',data,key:data.key})))
  if(section==='blocks')return Object.freeze([...m.definitions.blockVersions.map((data)=>({kind:'blockVersion',data,key:`${data.blockKey}@${data.revision}`})),...m.definitions.blocks.map((data)=>({kind:'blockCurrent',data,key:data.key}))])
  if(section==='entities')return Object.freeze((m.entities??[]).map((data)=>({kind:'entity',data,key:sourceKey(data.sourceRef)})))
  if(section==='revisions')return Object.freeze((m.revisions??[]).map((data)=>({kind:'revision',data,key:data.sourceRevisionKey})))
  if(section==='options')return Object.freeze((m.options??[]).map((data)=>({kind:'optionsValues',data,key:data.pageKey})))
  return Object.freeze((m.blockDocuments??[]).map((data)=>({kind:'blockDocument',data,key:sourceKey(data.sourceRef)})))
}
function conflictExists(plan:FieldsImportPlan,section:FieldsImportConflict['section'],key:string):boolean{return plan.conflicts.some((conflict)=>conflict.section===section&&conflict.sourceKey===key&&conflict.kind==='exists')}
function codeCollision(plan:FieldsImportPlan,kind:'group'|'options'|'block',key:string):boolean{return plan.mappings[mappingKey(kind==='group'?'code-group':kind==='options'?'code-options':'code-block',key)]==='1'}
async function emitImportEvent(tx:FieldsTransaction,effects:FieldsImportEffects|undefined,event:FieldsImportEvent):Promise<void>{if(!effects)return;await effects.audit(tx,event);await effects.enqueue(tx,event)}
function importEvent(kind:FieldsImportEvent['kind'],operationId:string,journal:FieldsImportJournal,plan:FieldsImportPlan,principalId:string):FieldsImportEvent{return Object.freeze({kind,operationId,importId:journal.importId,principalId,planId:plan.planId,manifestHash:plan.manifestHash,state:journal.state,version:journal.version,nextSection:journal.nextSection,nextOffset:journal.nextOffset,counts:plan.counts})}
function journalStorage(journal:FieldsImportJournal):FieldStorageValue{return jsonStorage(journal,'journal')}
function receiptJournal(receiptResult:FieldStorageValue):FieldsImportJournal{const row=plain(receiptResult,'receipt.result');return Object.freeze({importId:boundedString(row.importId,'receipt.importId',128),version:boundedString(row.version,'receipt.version',64),planId:boundedString(row.planId,'receipt.planId',128),manifestHash:boundedString(row.manifestHash,'receipt.manifestHash',128),codeRegistryVersion:boundedString(row.codeRegistryVersion,'receipt.codeRegistryVersion',256),codeRegistryHash:boundedString(row.codeRegistryHash,'receipt.codeRegistryHash',128),staging:Object.freeze({hostSessionId:boundedString(plain(row.staging,'receipt.staging').hostSessionId,'receipt.staging.hostSessionId',512),visibility:'hiddenUntilPublish'}),scope:normalizeScope(row.scope as FieldsImportScope),authorizationPolicyVersion:boundedString(row.authorizationPolicyVersion,'receipt.authorizationPolicyVersion',256),state:boundedString(row.state,'receipt.state',32) as FieldsImportJournal['state'],nextSection:row.nextSection===null?null:boundedString(row.nextSection,'receipt.nextSection',32) as FieldsImportSection,nextOffset:Number(row.nextOffset)})}

export async function startFieldsImport(tx:FieldsTransaction,plan:FieldsImportPlan,staging:FieldsImportStaging,command:FieldsImportCommand,registry:FieldTypeRegistry,codeRegistry:CodeFieldsRegistrySnapshot,context:FieldsContext,effects?:FieldsImportEffects):Promise<FieldsImportJournal>{
  assertActiveFieldsTransaction(tx);await assertPlanIdentity(plan);const commandScope=validateCommand(command,true);if(!sameScope(commandScope,plan.scope)||command.expectedPlanId!==plan.planId||command.expectedManifestHash!==plan.manifestHash)fail('operation-conflict','start command identity differs from plan');if(staging.visibility!=='hiddenUntilPublish')fail('validation','staging visibility must be hiddenUntilPublish');const hostSessionId=importSessionId(staging.hostSessionId)
  const authorized=requireAuthorizedFieldsContext(context),policy=await authorizePlan(context,plan);if(policy!==plan.authorizationPolicyVersion)fail('access-denied','import scope is unavailable');await assertRuntimeBindings(tx,plan,registry,codeRegistry,false)
  const requestHash=await fieldsRequestHash({kind:'start',planId:plan.planId,manifestHash:plan.manifestHash,scope:plan.scope,staging:{hostSessionId,visibility:'hiddenUntilPublish'},registryVersion:registry.version,codeRegistryVersion:codeRegistry.version,codeRegistryHash:codeRegistry.canonicalHash,command})
  const receipt=await readFieldsReceipt(tx,command.operationId);if(receipt){assertReceiptReplay(receipt,{kind:'fields:import-start',context:authorized,requestHash});return receiptJournal(receipt.result)}
  await assertRuntimeBindings(tx,plan,registry,codeRegistry,true)
  const existing=await execRows<Record<string,unknown>>(tx,sql`SELECT import_id FROM fields_import_journal WHERE host_session_id=${hostSessionId} LIMIT 1`);if(existing.length)fail('operation-conflict','staging session already exists')
  const importId=hostSessionId,nextSection=firstSection(plan),now=new Date().toISOString(),payload:PersistedImportPayload=Object.freeze({plan})
  try{await tx.execute(sql`INSERT INTO fields_import_journal (import_id,host_session_id,manifest_hash,plan_hash,state,version,next_section,next_offset,payload,created_at,updated_at) VALUES (${importId},${hostSessionId},${plan.manifestHash},${plan.planId},'planned',0,${nextSection},0,${JSON.stringify(payload)},${now},${now})`)}catch(error){if(isImportUniqueViolation(error))fail('operation-conflict','staging session already exists');throw error}
  const journal:FieldsImportJournal=Object.freeze({importId,version:'0',planId:plan.planId,manifestHash:plan.manifestHash,codeRegistryVersion:plan.codeRegistryVersion,codeRegistryHash:plan.codeRegistryHash,staging:Object.freeze({hostSessionId,visibility:'hiddenUntilPublish'}),scope:plan.scope,authorizationPolicyVersion:plan.authorizationPolicyVersion,state:nextSection?'planned':'staged',nextSection,nextOffset:0})
  if(!nextSection)await tx.execute(sql`UPDATE fields_import_journal SET state='staged' WHERE import_id=${importId}`)
  await emitImportEvent(tx,effects,importEvent('started',command.operationId,journal,plan,authorized.principal.id));await writeFieldsReceipt(tx,{operationId:command.operationId,kind:'fields:import-start',context:authorized,requestHash,result:journalStorage(journal)});return journal
}

function snapshotStorage(value:unknown,field='snapshot'):FieldStorageValue{
  let normalized:unknown
  try{normalized=JSON.parse(JSON.stringify(value)) as unknown}catch{fail('integrity',`${field} is not serializable`)}
  return jsonStorage(normalized,field)
}
function mappedRevision(plan:FieldsImportPlan,kind:'grouprev'|'blockrev',key:string,sourceRevision:number):number{
  const revision=Number(plan.mappings[mappingKey(kind,`${key}@${sourceRevision}`)])
  if(!Number.isSafeInteger(revision)||revision<1)fail('stale-plan',`missing ${kind} mapping for ${key}@${sourceRevision}`)
  return revision
}
function mappedOptionsRevision(plan:FieldsImportPlan,key:string):number{
  const revision=Number(plan.mappings[mappingKey('optionsrev',key)])
  if(!Number.isSafeInteger(revision)||revision<1)fail('stale-plan',`missing options revision mapping for ${key}`)
  return revision
}
function mappedRevisionId(plan:FieldsImportPlan,key:string):string{
  const id=plan.mappings[mappingKey('revision',key)]
  if(!id)fail('stale-plan',`missing revision id mapping for ${key}`)
  return id
}
async function currentGroupSourceRevision(plan:FieldsImportPlan,group:FieldGroup):Promise<number>{
  const hash=await canonicalFieldGroupHash(group),version=plan.manifest.definitions.versions.find((item)=>item.groupKey===group.key&&item.canonicalHash===hash)
  if(!version)fail('integrity',`group ${group.key} current version mapping is missing`)
  return version.revision
}
async function currentBlockSourceRevision(plan:FieldsImportPlan,block:FieldBlockDefinition):Promise<number>{
  const hash=await fieldsRequestHash(block),version=plan.manifest.definitions.blockVersions.find((item)=>item.blockKey===block.key&&item.canonicalHash===hash)
  if(!version)fail('integrity',`block ${block.key} current version mapping is missing`)
  return version.revision
}

function destinationDefinitionVersions(plan:FieldsImportPlan,versions:Readonly<Record<string,number>>):Readonly<Record<string,number>>{
  const out:Record<string,number>={}
  for(const [identity,sourceRevision] of Object.entries(versions)){
    if(identity.startsWith('block:'))out[identity]=mappedRevision(plan,'blockrev',identity.slice(6),sourceRevision)
    else{const key=identity.startsWith('group:')?identity.slice(6):identity;out[identity]=mappedRevision(plan,'grouprev',key,sourceRevision)}
  }
  return Object.freeze(out)
}
function remapBlockPins(plan:FieldsImportPlan,document:BlockDocument):BlockDocument{
  const walk=(nodes:BlockDocument['roots']):BlockDocument['roots']=>Object.freeze(nodes.map((node)=>{
    if(node.kind==='reusable')return node
    return Object.freeze({...node,blockDefinitionRevision:mappedRevision(plan,'blockrev',node.type,node.blockDefinitionRevision),fieldGroups:Object.freeze(node.fieldGroups.map((group)=>Object.freeze({...group,definitionRevision:mappedRevision(plan,'grouprev',group.groupKey,group.definitionRevision)}))),...(node.children?{children:walk(node.children)}:{})})
  }))
  return Object.freeze({version:1,roots:walk(document.roots)})
}
function remapBlockDefinition(plan:FieldsImportPlan,definition:FieldBlockDefinition):FieldBlockDefinition{
  return definition.template?Object.freeze({...definition,template:remapBlockPins(plan,{version:1,roots:definition.template}).roots}):definition
}
function stripSensitiveFields(fields:readonly AnyFieldDefinition[]):readonly AnyFieldDefinition[]{
  const output:AnyFieldDefinition[]=[]
  for(const field of fields){
    if(field.type==='password'||field.type==='clone')continue
    if(field.type==='group'||field.type==='repeater')output.push(Object.freeze({...field,settings:Object.freeze({...field.settings,fields:stripSensitiveFields((field.settings as {fields:readonly AnyFieldDefinition[]}).fields)})}) as AnyFieldDefinition)
    else if(field.type==='flexible')output.push(Object.freeze({...field,settings:Object.freeze({...field.settings,layouts:Object.freeze((field.settings as {layouts:readonly {key:string;name:string;label:string;fields:readonly AnyFieldDefinition[]}[]}).layouts.map((layout)=>Object.freeze({...layout,fields:stripSensitiveFields(layout.fields)})))})}) as AnyFieldDefinition)
    else output.push(field)
  }
  return Object.freeze(output)
}
function importWritableGroup(group:FieldGroup):FieldGroup{return Object.freeze({...group,fields:stripSensitiveFields(group.fields)})}
async function nodeBeforeImage(tx:FieldsTransaction,refValue:EntityRef,groups:readonly string[]):Promise<FieldStorageValue>{
  if(groups.length===0)return Object.freeze([])
  const rows=await execRows<Record<string,unknown>>(tx,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",legacy_source_ids AS "legacySourceIds",legacy_source_hash AS "legacySourceHash",host_session_id AS "hostSessionId",created_at AS "createdAt",updated_at AS "updatedAt" FROM field_value_nodes WHERE entity_type=${refValue.entityType} AND entity_id=${refValue.entityId} AND group_key IN (${sql.join(groups.map((group)=>sql`${group}`),sql`, `)}) ORDER BY group_key,path`)
  return snapshotStorage(rows,'before.nodes')
}

async function prepareDefinitionStagedItem(tx:FieldsTransaction,plan:FieldsImportPlan,item:{kind:string;data:unknown;key:string}):Promise<StagedItem|null>{
  if(item.kind==='groupVersion'){
    const data=item.data as FieldDefinitionVersionExport,destRevision=mappedRevision(plan,'grouprev',data.groupKey,data.revision)
    const beforeRows=await execRows<Record<string,unknown>>(tx,sql`SELECT group_key AS "groupKey",revision,canonical_hash AS "canonicalHash",definition,origin,host_session_id AS "hostSessionId",created_at AS "createdAt" FROM field_definition_versions WHERE group_key=${data.groupKey} AND revision=${destRevision} LIMIT 1`),before=beforeRows[0]??null
    if(before&&String(before.canonicalHash)!==data.canonicalHash)fail('stale-plan',`destination definition revision ${data.groupKey}@${destRevision} changed`)
    return Object.freeze({kind:item.kind,action:before?'skip':'apply',after:snapshotStorage({...data,revision:destRevision},'staged.groupVersion'),before:before?snapshotStorage(before):null})
  }
  if(item.kind==='groupCurrent'){
    const data=item.data as FieldGroup,sourceRevision=await currentGroupSourceRevision(plan,data),destRevision=mappedRevision(plan,'grouprev',data.key,sourceRevision)
    const beforeRows=await execRows<Record<string,unknown>>(tx,sql`SELECT key,origin,version,active,current_revision AS "currentRevision",canonical_hash AS "canonicalHash",definition,shadowed_db_version AS "shadowedDbVersion",host_session_id AS "hostSessionId",created_at AS "createdAt",updated_at AS "updatedAt" FROM field_group_definitions WHERE key=${data.key} LIMIT 1`),before=beforeRows[0]??null
    const action:StagedItem['action']=codeCollision(plan,'group',data.key)?'shadow':plan.policy==='preserveDestination'&&before?'skip':'apply'
    return Object.freeze({kind:item.kind,action,after:snapshotStorage({definition:data,currentRevision:destRevision},'staged.groupCurrent'),before:before?snapshotStorage(before):null})
  }
  if(item.kind==='optionsPage'){
    const data=item.data as OptionsPageDefinition,destRevision=mappedOptionsRevision(plan,data.key)
    const current=await execRows<Record<string,unknown>>(tx,sql`SELECT key,origin,version,active,current_revision AS "currentRevision",canonical_hash AS "canonicalHash",definition,shadowed_db_version AS "shadowedDbVersion",host_session_id AS "hostSessionId",created_at AS "createdAt",updated_at AS "updatedAt" FROM field_options_definitions WHERE key=${data.key} LIMIT 1`)
    const version=await execRows<Record<string,unknown>>(tx,sql`SELECT options_key AS "optionsKey",revision,canonical_hash AS "canonicalHash",definition,origin,host_session_id AS "hostSessionId",created_at AS "createdAt" FROM field_options_definition_versions WHERE options_key=${data.key} AND revision=${destRevision} LIMIT 1`)
    const action:StagedItem['action']=codeCollision(plan,'options',data.key)?'shadow':plan.policy==='preserveDestination'&&current.length?'skip':'apply'
    return Object.freeze({kind:item.kind,action,after:snapshotStorage({definition:data,currentRevision:destRevision},'staged.optionsPage'),before:snapshotStorage({current:current[0]??null,version:version[0]??null})})
  }
  if(item.kind==='blockVersion'){
    const data=item.data as FieldBlockDefinitionVersionExport,destRevision=mappedRevision(plan,'blockrev',data.blockKey,data.revision),definition=remapBlockDefinition(plan,data.definition),canonicalHash=await fieldsRequestHash(definition)
    const beforeRows=await execRows<Record<string,unknown>>(tx,sql`SELECT block_key AS "blockKey",revision,canonical_hash AS "canonicalHash",definition,origin,host_session_id AS "hostSessionId",created_at AS "createdAt" FROM field_block_definition_versions WHERE block_key=${data.blockKey} AND revision=${destRevision} LIMIT 1`),before=beforeRows[0]??null
    if(before&&String(before.canonicalHash)!==canonicalHash)fail('stale-plan',`destination block revision ${data.blockKey}@${destRevision} changed`)
    return Object.freeze({kind:item.kind,action:before?'skip':'apply',after:snapshotStorage({...data,revision:destRevision,canonicalHash,definition},'staged.blockVersion'),before:before?snapshotStorage(before):null})
  }
  if(item.kind==='blockCurrent'){
    const data=item.data as FieldBlockDefinition,sourceRevision=await currentBlockSourceRevision(plan,data),destRevision=mappedRevision(plan,'blockrev',data.key,sourceRevision),definition=remapBlockDefinition(plan,data)
    const beforeRows=await execRows<Record<string,unknown>>(tx,sql`SELECT key,origin,version,active,current_revision AS "currentRevision",canonical_hash AS "canonicalHash",definition,shadowed_db_version AS "shadowedDbVersion",host_session_id AS "hostSessionId",created_at AS "createdAt",updated_at AS "updatedAt" FROM field_block_definitions WHERE key=${data.key} LIMIT 1`),before=beforeRows[0]??null
    const action:StagedItem['action']=codeCollision(plan,'block',data.key)?'shadow':plan.policy==='preserveDestination'&&before?'skip':'apply'
    return Object.freeze({kind:item.kind,action,after:snapshotStorage({definition,currentRevision:destRevision},'staged.blockCurrent'),before:before?snapshotStorage(before):null})
  }
  return null
}

async function prepareDataStagedItem(tx:FieldsTransaction,plan:FieldsImportPlan,item:{kind:string;data:unknown;key:string}):Promise<StagedItem>{
  if(item.kind==='entity'){
    const data=item.data as NonNullable<FieldsExportManifest['entities']>[number],before=await nodeBeforeImage(tx,data.sourceRef,Object.keys(data.values))
    const action:StagedItem['action']=plan.policy==='preserveDestination'&&Array.isArray(before)&&before.length?'skip':'apply'
    return Object.freeze({kind:item.kind,action,after:snapshotStorage(data,'staged.entity'),before})
  }
  if(item.kind==='revision'){
    const data=item.data as FieldsRevisionExport,destId=mappedRevisionId(plan,data.sourceRevisionKey)
    const before=await execRows<Record<string,unknown>>(tx,sql`SELECT id,entity_type AS "entityType",entity_id AS "entityId",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",host_session_id AS "hostSessionId" FROM field_revisions WHERE id=${destId} LIMIT 1`)
    if(before.length&&plan.policy!=='preserveDestination')fail('stale-plan',`destination revision ${destId} already exists`)
    return Object.freeze({kind:item.kind,action:before.length?'skip':'apply',after:snapshotStorage({...data,destinationRevisionId:destId},'staged.revision'),before:before.length?snapshotStorage(before[0]):null})
  }
  if(item.kind==='optionsValues'){
    const data=item.data as NonNullable<FieldsExportManifest['options']>[number],before=await nodeBeforeImage(tx,{entityType:'options',entityId:data.pageKey},Object.keys(data.values))
    const action:StagedItem['action']=plan.policy==='preserveDestination'&&Array.isArray(before)&&before.length?'skip':'apply'
    return Object.freeze({kind:item.kind,action,after:snapshotStorage(data,'staged.optionsValues'),before})
  }
  const data=item.data as NonNullable<FieldsExportManifest['blockDocuments']>[number]
  const beforeRows=await execRows<Record<string,unknown>>(tx,sql`SELECT entity_type AS "entityType",entity_id AS "entityId",version,document,definition_versions AS "definitionVersions",parent_revision_id AS "parentRevisionId",host_session_id AS "hostSessionId",updated_by AS "updatedBy",updated_at AS "updatedAt" FROM field_block_documents WHERE entity_type=${data.sourceRef.entityType} AND entity_id=${data.sourceRef.entityId} LIMIT 1`)
  const action:StagedItem['action']=plan.policy==='preserveDestination'&&beforeRows.length?'skip':'apply'
  return Object.freeze({kind:'blockDocument',action,after:snapshotStorage(data,'staged.blockDocument'),before:beforeRows.length?snapshotStorage(beforeRows[0]):null})
}
async function prepareStagedItem(tx:FieldsTransaction,plan:FieldsImportPlan,section:FieldsImportSection,item:{kind:string;data:unknown;key:string}):Promise<StagedItem>{
  const definition=await prepareDefinitionStagedItem(tx,plan,item)
  if(definition)return definition
  if(!['entities','revisions','options','blockDocuments'].includes(section))fail('integrity',`unsupported staged section ${section}`)
  return prepareDataStagedItem(tx,plan,item)
}

async function casJournal(tx:FieldsTransaction,current:{row:ImportJournalRow;journal:FieldsImportJournal},command:FieldsImportCommand,next:{state:FieldsImportJournal['state'];section:FieldsImportSection|null;offset:number}):Promise<number>{
  const expectedVersion=commandVersion(command.expectedJournalVersion,'command.expectedJournalVersion')
  if(expectedVersion===null||command.expectedOffset===null)fail('validation','continuation requires complete journal coordinates')
  if(current.journal.version!==command.expectedJournalVersion||current.journal.nextSection!==command.expectedSection||current.journal.nextOffset!==command.expectedOffset)fail('stale-journal','import journal coordinate changed')
  const sectionPredicate=command.expectedSection===null?sql`next_section IS NULL`:sql`next_section=${command.expectedSection}`
  const rows=await execRows<Record<string,unknown>>(tx,sql`UPDATE fields_import_journal SET version=version+1,state=${next.state},next_section=${next.section},next_offset=${next.offset},updated_at=${new Date().toISOString()} WHERE import_id=${current.journal.importId} AND version=${expectedVersion} AND ${sectionPredicate} AND next_offset=${command.expectedOffset} RETURNING version`)
  if(rows.length!==1)fail('stale-journal','import journal coordinate changed')
  return Number(rows[0]!.version)
}

export async function resumeFieldsImport(
  tx:FieldsTransaction,
  importId:string,
  batchSize:number,
  command:FieldsImportCommand,
  registry:FieldTypeRegistry,
  codeRegistry:CodeFieldsRegistrySnapshot,
  context:FieldsContext,
  effects?:FieldsImportEffects,
):Promise<FieldsImportJournal>{
  assertActiveFieldsTransaction(tx)
  boundedString(importId,'importId',128)
  if(!Number.isSafeInteger(batchSize)||batchSize<1||batchSize>500)fail('validation','batchSize must be between 1 and 500')
  const scope=validateCommand(command,false),authorized=requireAuthorizedFieldsContext(context),scopePolicy=await assertScopeAuthorized(context,scope)
  const requestHash=await fieldsRequestHash({kind:'resume',importId,batchSize,command,registryVersion:registry.version,codeRegistryVersion:codeRegistry.version,codeRegistryHash:codeRegistry.canonicalHash})
  const receipt=await readFieldsReceipt(tx,command.operationId)
  if(receipt){assertReceiptReplay(receipt,{kind:'fields:import-resume',context:authorized,requestHash});return receiptJournal(receipt.result)}

  const current=await readJournal(tx,importId)
  if(!current)fail('access-denied','import session is unavailable')
  const plan=current.payload.plan
  await assertPlanIdentity(plan)
  if(!sameScope(scope,plan.scope)||command.expectedPlanId!==plan.planId||command.expectedManifestHash!==plan.manifestHash)fail('operation-conflict','resume command identity differs from import plan')
  if(scopePolicy!==plan.authorizationPolicyVersion)fail('access-denied','import session is unavailable')
  if(current.journal.state!=='planned'&&current.journal.state!=='applying')fail('stale-journal','import is not resumable')
  const policy=await authorizePlan(context,plan)
  if(policy!==plan.authorizationPolicyVersion)fail('access-denied','import session is unavailable')
  await assertRuntimeBindings(tx,plan,registry,codeRegistry,true)

  const section=current.journal.nextSection
  if(!section)fail('stale-journal','import has no remaining section')
  const items=sectionItems(plan,section),offset=current.journal.nextOffset
  if(offset<0||offset>=items.length)fail('integrity','journal offset is outside section')
  const end=Math.min(items.length,offset+batchSize),batch=items.slice(offset,end),sectionIndex=SECTIONS.indexOf(section)
  let nextSection:FieldsImportSection|null=section,nextOffset=end
  if(end>=items.length){nextSection=firstSection(plan,sectionIndex+1);nextOffset=0}
  const nextState:FieldsImportJournal['state']=nextSection?'applying':'staged'

  const nextVersion=await casJournal(tx,current,command,{state:nextState,section:nextSection,offset:nextOffset})
  for(let index=0;index<batch.length;index++){
    const ordinal=offset+index,prepared=await prepareStagedItem(tx,plan,section,batch[index]!),payload=snapshotStorage(prepared,'staged.item'),canonicalHash=await fieldsRequestHash(payload)
    await tx.execute(sql`INSERT INTO fields_import_staging (host_session_id,section,ordinal,canonical_hash,payload,created_at) VALUES (${current.journal.staging.hostSessionId},${section},${ordinal},${canonicalHash},${JSON.stringify(payload)},${new Date().toISOString()})`)
  }
  const journal:FieldsImportJournal=Object.freeze({...current.journal,version:String(nextVersion),state:nextState,nextSection,nextOffset})
  await emitImportEvent(tx,effects,importEvent('batchStaged',command.operationId,journal,plan,authorized.principal.id))
  await writeFieldsReceipt(tx,{operationId:command.operationId,kind:'fields:import-resume',context:authorized,requestHash,result:journalStorage(journal)})
  return journal
}

interface StagingRow extends Record<string,unknown>{section:string;ordinal:number|string;canonicalHash:string;payload:unknown}
function parseStagedItem(value:unknown):StagedItem{
  const raw=typeof value==='string'?(()=>{try{return JSON.parse(value) as unknown}catch{fail('integrity','staged payload is invalid JSON')}})():value,row=plain(raw,'staged.payload')
  if(typeof row.kind!=='string'||!['apply','skip','shadow'].includes(String(row.action)))fail('integrity','staged payload has invalid action')
  return Object.freeze({kind:row.kind,action:row.action as StagedItem['action'],after:jsonStorage(row.after,'staged.after'),before:row.before===null?null:jsonStorage(row.before,'staged.before')})
}
async function readVerifiedStaging(tx:FieldsTransaction,journal:FieldsImportJournal,plan:FieldsImportPlan):Promise<readonly {section:FieldsImportSection;ordinal:number;item:StagedItem}[]>{
  const rows=await execRows<StagingRow>(tx,sql`SELECT section,ordinal,canonical_hash AS "canonicalHash",payload FROM fields_import_staging WHERE host_session_id=${journal.staging.hostSessionId} ORDER BY section,ordinal`),output:{section:FieldsImportSection;ordinal:number;item:StagedItem}[]=[]
  const expected=new Map<FieldsImportSection,number>(SECTIONS.map((section)=>[section,sectionItems(plan,section).length]))
  const seen=new Map<FieldsImportSection,number>()
  for(const row of rows){if(!SECTIONS.includes(row.section as FieldsImportSection))fail('integrity','staging contains unknown section');const section=row.section as FieldsImportSection,ordinal=Number(row.ordinal);if(!Number.isSafeInteger(ordinal)||ordinal<0||ordinal>=expected.get(section)!)fail('integrity','staging ordinal is invalid');const item=parseStagedItem(row.payload),canonicalHash=await fieldsRequestHash(snapshotStorage(item,'staged.item'));if(canonicalHash!==row.canonicalHash)fail('integrity','staging payload hash mismatch');seen.set(section,(seen.get(section)??0)+1);output.push({section,ordinal,item})}
  for(const section of SECTIONS)if((seen.get(section)??0)!==expected.get(section))fail('integrity',`staging section ${section} is incomplete`)
  output.sort((left,right)=>SECTIONS.indexOf(left.section)-SECTIONS.indexOf(right.section)||left.ordinal-right.ordinal)
  return Object.freeze(output)
}
function sensitiveFieldKeys(fields:readonly AnyFieldDefinition[],out=new Set<string>()):Set<string>{for(const field of fields){if(field.type==='password'||field.type==='clone')out.add(field.key);if(field.type==='group'||field.type==='repeater')sensitiveFieldKeys((field.settings as {fields:readonly AnyFieldDefinition[]}).fields,out);if(field.type==='flexible')for(const layout of (field.settings as {layouts:readonly {fields:readonly AnyFieldDefinition[]}[]}).layouts)sensitiveFieldKeys(layout.fields,out)}return out}
function filterWritableValues(fields:readonly AnyFieldDefinition[],input:Readonly<Record<string,unknown>>):Readonly<Record<string,unknown>>{
  const out:Record<string,unknown>={}
  for(const field of fields){if(field.type==='password'||field.type==='clone'||!Object.prototype.hasOwnProperty.call(input,field.key))continue;const value=input[field.key]
    if(field.type==='group'&&value&&typeof value==='object'&&!Array.isArray(value))out[field.key]=filterWritableValues((field.settings as {fields:readonly AnyFieldDefinition[]}).fields,value as Record<string,unknown>)
    else if(field.type==='repeater'&&Array.isArray(value))out[field.key]=Object.freeze(value.map((raw)=>{const row=plain(raw,field.key),values=plain(row.values,`${field.key}.values`);return Object.freeze({...row,values:filterWritableValues((field.settings as {fields:readonly AnyFieldDefinition[]}).fields,values)})}))
    else if(field.type==='flexible'&&Array.isArray(value))out[field.key]=Object.freeze(value.map((raw)=>{const row=plain(raw,field.key),layout=(field.settings as {layouts:readonly {key:string;fields:readonly AnyFieldDefinition[]}[]}).layouts.find((candidate)=>candidate.key===row.layoutKey);if(!layout)return row;return Object.freeze({...row,values:filterWritableValues(layout.fields,plain(row.values,`${field.key}.values`))})}))
    else out[field.key]=value
  }
  return Object.freeze(out)
}
async function writeImportedGroupValues(tx:FieldsTransaction,plan:FieldsImportPlan,refValue:EntityRef,groupKey:string,sourceRevision:number,values:Readonly<Record<string,unknown>>,sessionId:string,registry:FieldTypeRegistry,context:FieldsContext):Promise<void>{
  const source=plan.manifest.definitions.versions.find((item)=>item.groupKey===groupKey&&item.revision===sourceRevision);if(!source)fail('integrity',`missing source group definition ${groupKey}@${sourceRevision}`)
  const destinationRevision=mappedRevision(plan,'grouprev',groupKey,sourceRevision),group=source.definition,writable=importWritableGroup(group),filtered=filterWritableValues(group.fields,values),runtimeContext=Object.freeze({...context,fieldTypeRegistry:registry})
  const validated=await __fieldBlockLifecycleInternals.validateBlockFieldMap(writable.fields,filtered,runtimeContext as FieldsContext,`import.${refValue.entityType}.${refValue.entityId}.${groupKey}`)
  const existing=await execRows<Record<string,unknown>>(tx,sql`SELECT node_id AS "nodeId",path,parent_node_id AS "parentNodeId",field_key AS "fieldKey" FROM field_value_nodes WHERE entity_type=${refValue.entityType} AND entity_id=${refValue.entityId} AND group_key=${groupKey} ORDER BY path`),byPath=new Map(existing.map((row)=>[String(row.path),String(row.nodeId)])),byId=new Map(existing.map((row)=>[String(row.nodeId),row])),protectedIds=new Set<string>(),sensitiveKeys=sensitiveFieldKeys(group.fields)
  for(const row of existing)if(sensitiveKeys.has(String(row.fieldKey))){let cursor:Record<string,unknown>|undefined=row;while(cursor){const id=String(cursor.nodeId);if(protectedIds.has(id))break;protectedIds.add(id);const parentId:unknown=cursor.parentNodeId;cursor=parentId===null||parentId===undefined?undefined:byId.get(String(parentId))}}
  if(protectedIds.size){const ids=[...protectedIds];await tx.execute(sql`UPDATE field_value_nodes SET definition_revision=${destinationRevision},host_session_id=${sessionId},updated_at=${new Date().toISOString()} WHERE node_id IN (${sql.join(ids.map((id)=>sql`${id}`),sql`, `)})`);await tx.execute(sql`DELETE FROM field_value_nodes WHERE entity_type=${refValue.entityType} AND entity_id=${refValue.entityId} AND group_key=${groupKey} AND node_id NOT IN (${sql.join(ids.map((id)=>sql`${id}`),sql`, `)})`)}else await tx.execute(sql`DELETE FROM field_value_nodes WHERE entity_type=${refValue.entityType} AND entity_id=${refValue.entityId} AND group_key=${groupKey}`)
  const generated=new Map<string,string>(),nodeIdForPath=(path:string)=>byPath.get(path)??generated.get(path)??(()=>{const id=crypto.randomUUID();generated.set(path,id);return id})(),rows=encodeFieldValueNodes(writable,validated,{entityType:refValue.entityType,entityId:refValue.entityId,definitionRevision:destinationRevision,nodeIdForPath,...(context.cloneResolver?{cloneResolver:{resolve:(definition)=>context.cloneResolver!.resolve(definition)}}:{})})
  for(const row of rows){if(protectedIds.has(row.nodeId))continue;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,host_session_id,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},${sessionId},${new Date().toISOString()},${new Date().toISOString()})`)}
}

async function applyDefinitionStagedItem(tx:FieldsTransaction,plan:FieldsImportPlan,sessionId:string,staged:StagedItem):Promise<boolean>{
  if(staged.action==='skip')return true
  const after=plain(staged.after,'staged.after')
  if(staged.kind==='groupVersion'){
    const groupKey=boundedString(after.groupKey,'staged.groupVersion.groupKey',128),revision=positive(after.revision,'staged.groupVersion.revision'),definition=after.definition as unknown as FieldGroup,canonicalHash=boundedString(after.canonicalHash,'staged.groupVersion.canonicalHash',128)
    if(await canonicalFieldGroupHash(definition)!==canonicalHash)fail('integrity','staged group definition hash mismatch')
    await tx.execute(sql`INSERT INTO field_definition_versions (group_key,revision,canonical_hash,definition,origin,host_session_id,created_at) VALUES (${groupKey},${revision},${canonicalHash},${JSON.stringify(definition)},'import',${sessionId},${dateString(after.createdAt,'staged.groupVersion.createdAt')})`)
    return true
  }
  if(staged.kind==='groupCurrent'){
    const definition=after.definition as unknown as FieldGroup,currentRevision=positive(after.currentRevision,'staged.groupCurrent.currentRevision'),canonicalHash=await canonicalFieldGroupHash(definition),before=staged.before===null?null:plain(staged.before,'staged.groupCurrent.before'),now=new Date().toISOString()
    if(staged.action==='shadow'){
      if(!before||before.origin!=='code')fail('stale-plan','group code collision no longer exists')
      await tx.execute(sql`UPDATE field_group_definitions SET version=version+1,shadowed_db_version=${currentRevision},updated_at=${now} WHERE key=${definition.key} AND origin='code'`)
      return true
    }
    if(before){const version=Number(before.version)+1;if(!Number.isSafeInteger(version)||version<2)fail('integrity','staged group before-image version is invalid');await tx.execute(sql`UPDATE field_group_definitions SET origin='import',version=${version},active=${definition.active},current_revision=${currentRevision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=NULL,host_session_id=${sessionId},updated_at=${now} WHERE key=${definition.key}`)}
    else await tx.execute(sql`INSERT INTO field_group_definitions (key,origin,version,active,current_revision,canonical_hash,definition,shadowed_db_version,host_session_id,created_at,updated_at) VALUES (${definition.key},'import',1,${definition.active},${currentRevision},${canonicalHash},${JSON.stringify(definition)},NULL,${sessionId},${now},${now})`)
    return true
  }
  if(staged.kind==='optionsPage'){
    const definition=__optionsLifecycleInternals.normalizeOptionsPage(after.definition as never),currentRevision=positive(after.currentRevision,'staged.optionsPage.currentRevision'),canonicalHash=await fieldsRequestHash(definition),beforeBundle=staged.before===null?null:plain(staged.before,'staged.optionsPage.before'),before=beforeBundle&&beforeBundle.current?plain(beforeBundle.current,'staged.optionsPage.before.current'):null,versionBefore=beforeBundle&&beforeBundle.version?plain(beforeBundle.version,'staged.optionsPage.before.version'):null,now=new Date().toISOString()
    if(!versionBefore)await tx.execute(sql`INSERT INTO field_options_definition_versions (options_key,revision,canonical_hash,definition,origin,host_session_id,created_at) VALUES (${definition.key},${currentRevision},${canonicalHash},${JSON.stringify(definition)},'import',${sessionId},${now})`)
    else if(String(versionBefore.canonicalHash)!==canonicalHash&&Number(versionBefore.revision)===currentRevision)fail('stale-plan','options immutable revision changed')
    if(staged.action==='shadow'){
      if(!before||before.origin!=='code')fail('stale-plan','options code collision no longer exists')
      await tx.execute(sql`UPDATE field_options_definitions SET version=version+1,shadowed_db_version=${currentRevision},updated_at=${now} WHERE key=${definition.key} AND origin='code'`)
      return true
    }
    if(before){const version=Number(before.version)+1;await tx.execute(sql`UPDATE field_options_definitions SET origin='import',version=${version},active=true,current_revision=${currentRevision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=NULL,host_session_id=${sessionId},updated_at=${now} WHERE key=${definition.key}`)}
    else await tx.execute(sql`INSERT INTO field_options_definitions (key,origin,version,active,current_revision,canonical_hash,definition,shadowed_db_version,host_session_id,created_at,updated_at) VALUES (${definition.key},'import',1,true,${currentRevision},${canonicalHash},${JSON.stringify(definition)},NULL,${sessionId},${now},${now})`)
    return true
  }
  if(staged.kind==='blockVersion'){
    const blockKey=boundedString(after.blockKey,'staged.blockVersion.blockKey',128),revision=positive(after.revision,'staged.blockVersion.revision'),definition=__fieldBlockLifecycleInternals.normalizeDefinition(after.definition as never),canonicalHash=boundedString(after.canonicalHash,'staged.blockVersion.canonicalHash',128)
    if(await fieldsRequestHash(definition)!==canonicalHash)fail('integrity','staged block definition hash mismatch')
    await tx.execute(sql`INSERT INTO field_block_definition_versions (block_key,revision,canonical_hash,definition,origin,host_session_id,created_at) VALUES (${blockKey},${revision},${canonicalHash},${JSON.stringify(definition)},'import',${sessionId},${dateString(after.createdAt,'staged.blockVersion.createdAt')})`)
    return true
  }
  if(staged.kind==='blockCurrent'){
    const definition=__fieldBlockLifecycleInternals.normalizeDefinition(after.definition as never),currentRevision=positive(after.currentRevision,'staged.blockCurrent.currentRevision'),canonicalHash=await fieldsRequestHash(definition),before=staged.before===null?null:plain(staged.before,'staged.blockCurrent.before'),now=new Date().toISOString()
    if(staged.action==='shadow'){
      if(!before||before.origin!=='code')fail('stale-plan','block code collision no longer exists')
      await tx.execute(sql`UPDATE field_block_definitions SET version=version+1,shadowed_db_version=${currentRevision},updated_at=${now} WHERE key=${definition.key} AND origin='code'`)
      return true
    }
    if(before){const version=Number(before.version)+1;await tx.execute(sql`UPDATE field_block_definitions SET origin='import',version=${version},active=true,current_revision=${currentRevision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=NULL,host_session_id=${sessionId},updated_at=${now} WHERE key=${definition.key}`)}
    else await tx.execute(sql`INSERT INTO field_block_definitions (key,origin,version,active,current_revision,canonical_hash,definition,shadowed_db_version,host_session_id,created_at,updated_at) VALUES (${definition.key},'import',1,true,${currentRevision},${canonicalHash},${JSON.stringify(definition)},NULL,${sessionId},${now},${now})`)
    return true
  }
  return false
}

async function validateImportedBlockDocument(tx:FieldsTransaction,document:BlockDocument,registry:FieldTypeRegistry,codeRegistry:CodeFieldsRegistrySnapshot,context:FieldsContext):Promise<string>{
  const runtimeContext=Object.freeze({...context,fieldTypeRegistry:registry}),blockRegistry=await resolveFieldBlocks(tx,{codeBlocks:codeRegistry.blocks}),validated=await validateBlockDocument(document,blockRegistry,createReusableBlockResolver(tx),runtimeContext,DEFAULT_BLOCK_POLICY)
  return serializeBlockDocument(validated)
}
async function applyValueStagedItem(tx:FieldsTransaction,plan:FieldsImportPlan,sessionId:string,staged:StagedItem,registry:FieldTypeRegistry,context:FieldsContext):Promise<boolean>{
  if(staged.action==='skip')return staged.kind==='entity'||staged.kind==='optionsValues'
  const after=plain(staged.after,'staged.after')
  if(staged.kind==='entity'){
    const refValue=ref(after.sourceRef,'staged.entity.sourceRef'),values=plain(after.values,'staged.entity.values'),versions=revisionsMap(after.definitionVersions,'staged.entity.definitionVersions')
    for(const [groupKey,value] of Object.entries(values)){const sourceRevision=versions[groupKey];if(!sourceRevision)fail('integrity',`entity value has no pinned revision for ${groupKey}`);await writeImportedGroupValues(tx,plan,refValue,groupKey,sourceRevision,plain(value,`staged.entity.values.${groupKey}`),sessionId,registry,context)}
    return true
  }
  if(staged.kind==='optionsValues'){
    const pageKey=boundedString(after.pageKey,'staged.options.pageKey',128),values=plain(after.values,'staged.options.values'),versions=revisionsMap(after.definitionVersions,'staged.options.definitionVersions')
    for(const [groupKey,value] of Object.entries(values)){const sourceRevision=versions[groupKey];if(!sourceRevision)fail('integrity',`options value has no pinned revision for ${groupKey}`);await writeImportedGroupValues(tx,plan,{entityType:'options',entityId:pageKey},groupKey,sourceRevision,plain(value,`staged.options.values.${groupKey}`),sessionId,registry,context)}
    return true
  }
  return false
}

async function applySnapshotStagedItem(tx:FieldsTransaction,plan:FieldsImportPlan,sessionId:string,staged:StagedItem,principalId:string,registry:FieldTypeRegistry,codeRegistry:CodeFieldsRegistrySnapshot,context:FieldsContext):Promise<boolean>{
  if(staged.action==='skip')return staged.kind==='revision'||staged.kind==='blockDocument'
  const after=plain(staged.after,'staged.after')
  if(staged.kind==='revision'){
    const destinationRevisionId=boundedString(after.destinationRevisionId,'staged.revision.destinationRevisionId',128),sourceRef=ref(after.sourceRef,'staged.revision.sourceRef'),sourceRevisionKey=boundedString(after.sourceRevisionKey,'staged.revision.sourceRevisionKey',512),definitionVersions=destinationDefinitionVersions(plan,revisionsMap(after.definitionVersions,'staged.revision.definitionVersions')),values=jsonStorage(after.values,'staged.revision.values')
    if(mappedRevisionId(plan,sourceRevisionKey)!==destinationRevisionId)fail('integrity','revision destination identity differs from plan')
    let serializedBlock:string|null=null
    if(after.blockDocument!==undefined){const document=remapBlockPins(plan,blockDocument(after.blockDocument,DEFAULT_IMPORT_LIMITS,'staged.revision.blockDocument'));serializedBlock=await validateImportedBlockDocument(tx,document,registry,codeRegistry,context)}
    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,host_session_id) VALUES (${destinationRevisionId},${sourceRef.entityType},${sourceRef.entityId},${boundedString(after.parentRevisionKey,'staged.revision.parentRevisionKey',512)},NULL,'revision',NULL,${JSON.stringify(definitionVersions)},${JSON.stringify(values)},${serializedBlock},${dateString(after.createdAt,'staged.revision.createdAt')},${boundedString(after.createdBySourceId,'staged.revision.createdBySourceId',512)},${String(positive(after.sourceVersion,'staged.revision.sourceVersion'))},${sessionId})`)
    return true
  }
  if(staged.kind==='blockDocument'){
    const sourceRef=ref(after.sourceRef,'staged.blockDocument.sourceRef'),document=remapBlockPins(plan,blockDocument(after.document,DEFAULT_IMPORT_LIMITS,'staged.blockDocument.document')),serialized=await validateImportedBlockDocument(tx,document,registry,codeRegistry,context),definitionVersions=destinationDefinitionVersions(plan,revisionsMap(after.definitionVersions,'staged.blockDocument.definitionVersions')),before=staged.before===null?null:plain(staged.before,'staged.blockDocument.before'),numericVersion=Number(before?.version??0)+1,parentRevisionId=`fields-import:${plan.manifestHash.slice(0,32)}:${(await fieldsRequestHash(sourceRef)).slice(0,24)}`,now=new Date().toISOString()
    if(!Number.isSafeInteger(numericVersion)||numericVersion<1)fail('integrity','block document before-image version is invalid')
    await tx.execute(sql`INSERT INTO field_block_documents (entity_type,entity_id,version,document,definition_versions,parent_revision_id,host_session_id,updated_by,updated_at) VALUES (${sourceRef.entityType},${sourceRef.entityId},${numericVersion},${serialized},${JSON.stringify(definitionVersions)},${parentRevisionId},${sessionId},${principalId},${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,host_session_id=EXCLUDED.host_session_id,updated_by=EXCLUDED.updated_by,updated_at=EXCLUDED.updated_at`)
    return true
  }
  return false
}
async function applyStagedItem(tx:FieldsTransaction,plan:FieldsImportPlan,sessionId:string,staged:StagedItem,principalId:string,registry:FieldTypeRegistry,codeRegistry:CodeFieldsRegistrySnapshot,context:FieldsContext):Promise<void>{
  if(await applyDefinitionStagedItem(tx,plan,sessionId,staged))return
  if(await applyValueStagedItem(tx,plan,sessionId,staged,registry,context))return
  if(await applySnapshotStagedItem(tx,plan,sessionId,staged,principalId,registry,codeRegistry,context))return
  fail('integrity',`unknown staged item kind ${staged.kind}`)
}

export async function publishFieldsImport(
  tx:FieldsTransaction,
  importId:string,
  command:FieldsImportCommand,
  registry:FieldTypeRegistry,
  codeRegistry:CodeFieldsRegistrySnapshot,
  context:FieldsContext,
  effects?:FieldsImportEffects,
):Promise<FieldsImportJournal>{
  assertActiveFieldsTransaction(tx)
  boundedString(importId,'importId',128)
  const scope=validateCommand(command,false),authorized=requireAuthorizedFieldsContext(context),scopePolicy=await assertScopeAuthorized(context,scope)
  const requestHash=await fieldsRequestHash({kind:'publish',importId,command,registryVersion:registry.version,codeRegistryVersion:codeRegistry.version,codeRegistryHash:codeRegistry.canonicalHash})
  const receipt=await readFieldsReceipt(tx,command.operationId)
  if(receipt){assertReceiptReplay(receipt,{kind:'fields:import-publish',context:authorized,requestHash});return receiptJournal(receipt.result)}

  const current=await readJournal(tx,importId)
  if(!current)fail('access-denied','import session is unavailable')
  const plan=current.payload.plan
  await assertPlanIdentity(plan)
  if(!sameScope(scope,plan.scope)||command.expectedPlanId!==plan.planId||command.expectedManifestHash!==plan.manifestHash)fail('operation-conflict','publish command identity differs from import plan')
  if(scopePolicy!==plan.authorizationPolicyVersion)fail('access-denied','import session is unavailable')
  if(current.journal.state!=='staged'||current.journal.nextSection!==null||current.journal.nextOffset!==0)fail('stale-journal','import is not staged for publication')
  const policy=await authorizePlan(context,plan)
  if(policy!==plan.authorizationPolicyVersion)fail('access-denied','import session is unavailable')
  await assertRuntimeBindings(tx,plan,registry,codeRegistry,true)

  const nextVersion=await casJournal(tx,current,command,{state:'published',section:null,offset:0})
  const staged=await readVerifiedStaging(tx,current.journal,plan)
  for(const entry of staged)await applyStagedItem(tx,plan,current.journal.staging.hostSessionId,entry.item,authorized.principal.id,registry,codeRegistry,context)
  const publishedValueCorpusVersion=await valueCorpusVersion(tx,plan.scope),payload:PersistedImportPayload=Object.freeze({plan,publishedValueCorpusVersion})
  await tx.execute(sql`UPDATE fields_import_journal SET payload=${JSON.stringify(payload)},high_water_mark=${publishedValueCorpusVersion},updated_at=${new Date().toISOString()} WHERE import_id=${importId} AND version=${nextVersion}`)
  const journal:FieldsImportJournal=Object.freeze({...current.journal,version:String(nextVersion),state:'published',nextSection:null,nextOffset:0})
  await emitImportEvent(tx,effects,importEvent('published',command.operationId,journal,plan,authorized.principal.id))
  await writeFieldsReceipt(tx,{operationId:command.operationId,kind:'fields:import-publish',context:authorized,requestHash,result:journalStorage(journal)})
  return journal
}

export async function rollbackFieldsImport(
  tx:FieldsTransaction,
  importId:string,
  command:FieldsImportCommand,
  registry:FieldTypeRegistry,
  codeRegistry:CodeFieldsRegistrySnapshot,
  context:FieldsContext,
  effects?:FieldsImportEffects,
):Promise<FieldsImportJournal>{
  assertActiveFieldsTransaction(tx)
  boundedString(importId,'importId',128)
  const scope=validateCommand(command,false),authorized=requireAuthorizedFieldsContext(context),scopePolicy=await assertScopeAuthorized(context,scope)
  const requestHash=await fieldsRequestHash({kind:'rollback',importId,command,registryVersion:registry.version,codeRegistryVersion:codeRegistry.version,codeRegistryHash:codeRegistry.canonicalHash})
  const receipt=await readFieldsReceipt(tx,command.operationId)
  if(receipt){assertReceiptReplay(receipt,{kind:'fields:import-rollback',context:authorized,requestHash});return receiptJournal(receipt.result)}
  const current=await readJournal(tx,importId)
  if(!current)fail('access-denied','import session is unavailable')
  const plan=current.payload.plan
  await assertPlanIdentity(plan)
  if(!sameScope(scope,plan.scope)||command.expectedPlanId!==plan.planId||command.expectedManifestHash!==plan.manifestHash)fail('operation-conflict','rollback command identity differs from import plan')
  if(scopePolicy!==plan.authorizationPolicyVersion)fail('access-denied','import session is unavailable')
  if(!['planned','applying','staged'].includes(current.journal.state))fail('stale-journal','published or terminal imports cannot use staged rollback')
  const policy=await authorizePlan(context,plan)
  if(policy!==plan.authorizationPolicyVersion)fail('access-denied','import session is unavailable')
  await assertRuntimeBindings(tx,plan,registry,codeRegistry,true)
  const nextVersion=await casJournal(tx,current,command,{state:'rolledBack',section:null,offset:0})
  await tx.execute(sql`DELETE FROM fields_import_staging WHERE host_session_id=${current.journal.staging.hostSessionId}`)
  const journal:FieldsImportJournal=Object.freeze({...current.journal,version:String(nextVersion),state:'rolledBack',nextSection:null,nextOffset:0})
  await emitImportEvent(tx,effects,importEvent('rolledBack',command.operationId,journal,plan,authorized.principal.id))
  await writeFieldsReceipt(tx,{operationId:command.operationId,kind:'fields:import-rollback',context:authorized,requestHash,result:journalStorage(journal)})
  return journal
}

async function stagingBeforeImageHash(entries:readonly {section:FieldsImportSection;ordinal:number;item:StagedItem}[]):Promise<string>{
  return fieldsRequestHash(entries.map((entry)=>({section:entry.section,ordinal:entry.ordinal,kind:entry.item.kind,action:entry.item.action,before:entry.item.before})))
}
export async function planPublishedFieldsImportReverse(
  tx:FieldsTransaction,
  importId:string,
  scopeInput:FieldsImportScope,
  registry:FieldTypeRegistry,
  codeRegistry:CodeFieldsRegistrySnapshot,
  context:FieldsContext,
):Promise<FieldsPublishedReversePlan>{
  assertActiveFieldsTransaction(tx)
  boundedString(importId,'importId',128)
  const scope=normalizeScope(scopeInput),authorizationPolicyVersion=await assertScopeAuthorized(context,scope)
  const current=await readJournal(tx,importId)
  if(!current)fail('access-denied','import session is unavailable')
  const plan=current.payload.plan
  await assertPlanIdentity(plan)
  if(!sameScope(scope,plan.scope))fail('access-denied','import session is unavailable')
  if(current.journal.state!=='published')fail('stale-journal','import is not published')
  await authorizePlan(context,plan)
  await assertRuntimeBindings(tx,plan,registry,codeRegistry,false)
  const publishedValueCorpusVersion=current.payload.publishedValueCorpusVersion
  if(!publishedValueCorpusVersion)fail('integrity','published import has no corpus version')
  if(await valueCorpusVersion(tx,scope)!==publishedValueCorpusVersion)fail('stale-plan','published corpus changed after import')
  const staged=await readVerifiedStaging(tx,current.journal,plan),beforeImageHash=await stagingBeforeImageHash(staged)
  const material={importId,originalPlanId:plan.planId,manifestHash:plan.manifestHash,publishedValueCorpusVersion,beforeImageHash,counts:plan.counts,scope,requiredAuthority:plan.requiredAuthority,authorizationPolicyVersion,registryVersion:registry.version,codeRegistryVersion:codeRegistry.version,codeRegistryHash:codeRegistry.canonicalHash}
  return Object.freeze({reversePlanId:await fieldsRequestHash(material),importId,originalPlanId:plan.planId,manifestHash:plan.manifestHash,publishedValueCorpusVersion,beforeImageHash,counts:plan.counts,scope,requiredAuthority:plan.requiredAuthority,authorizationPolicyVersion})
}

async function restoreCurrentRow(tx:FieldsTransaction,table:'group'|'options'|'block',before:Record<string,unknown>|null,key:string,sessionId:string):Promise<void>{
  if(!before){
    if(table==='group')await tx.execute(sql`DELETE FROM field_group_definitions WHERE key=${key} AND host_session_id=${sessionId}`)
    else if(table==='options')await tx.execute(sql`DELETE FROM field_options_definitions WHERE key=${key} AND host_session_id=${sessionId}`)
    else await tx.execute(sql`DELETE FROM field_block_definitions WHERE key=${key} AND host_session_id=${sessionId}`)
    return
  }
  const version=Number(before.version),active=bool(before.active,'before.active'),currentRevision=positive(before.currentRevision,'before.currentRevision'),canonicalHash=boundedString(before.canonicalHash,'before.canonicalHash',128),origin=boundedString(before.origin,'before.origin',16),definition=before.definition,shadowed=before.shadowedDbVersion===null||before.shadowedDbVersion===undefined?null:Number(before.shadowedDbVersion),hostSession=before.hostSessionId===null||before.hostSessionId===undefined?null:String(before.hostSessionId),updatedAt=dateString(before.updatedAt,'before.updatedAt')
  if(!['code','db','import'].includes(origin)||!Number.isSafeInteger(version)||version<1||(shadowed!==null&&(!Number.isSafeInteger(shadowed)||shadowed<1)))fail('integrity','definition before-image is invalid')
  if(table==='group')await tx.execute(sql`UPDATE field_group_definitions SET origin=${origin},version=${version},active=${active},current_revision=${currentRevision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=${shadowed},host_session_id=${hostSession},updated_at=${updatedAt} WHERE key=${key}`)
  else if(table==='options')await tx.execute(sql`UPDATE field_options_definitions SET origin=${origin},version=${version},active=${active},current_revision=${currentRevision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=${shadowed},host_session_id=${hostSession},updated_at=${updatedAt} WHERE key=${key}`)
  else await tx.execute(sql`UPDATE field_block_definitions SET origin=${origin},version=${version},active=${active},current_revision=${currentRevision},canonical_hash=${canonicalHash},definition=${JSON.stringify(definition)},shadowed_db_version=${shadowed},host_session_id=${hostSession},updated_at=${updatedAt} WHERE key=${key}`)
}
async function restoreNodeRows(tx:FieldsTransaction,refValue:EntityRef,groups:readonly string[],before:FieldStorageValue|null):Promise<void>{
  if(groups.length)await tx.execute(sql`DELETE FROM field_value_nodes WHERE entity_type=${refValue.entityType} AND entity_id=${refValue.entityId} AND group_key IN (${sql.join(groups.map((group)=>sql`${group}`),sql`, `)})`)
  if(!Array.isArray(before))return
  for(const raw of before){const row=plain(raw,'before.node'),legacy=row.legacySourceIds===null||row.legacySourceIds===undefined?null:JSON.stringify(row.legacySourceIds),createdAt=dateString(row.createdAt,'before.node.createdAt'),updatedAt=dateString(row.updatedAt,'before.node.updatedAt');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,legacy_source_ids,legacy_source_hash,host_session_id,created_at,updated_at) VALUES (${boundedString(row.nodeId,'before.node.nodeId',128)},${boundedString(row.entityType,'before.node.entityType',256)},${boundedString(row.entityId,'before.node.entityId',512)},${boundedString(row.groupKey,'before.node.groupKey',128)},${positive(row.definitionRevision,'before.node.definitionRevision')},${boundedString(row.fieldKey,'before.node.fieldKey',256)},${boundedString(row.path,'before.node.path',8192)},${row.parentNodeId===null?null:String(row.parentNodeId)},${row.rowId===null?null:String(row.rowId)},${row.layoutKey===null?null:String(row.layoutKey)},${boundedString(row.nodeKind,'before.node.nodeKind',32)},${Number(row.ordinal)},${Boolean(row.isNull)},${row.valueText===null?null:String(row.valueText)},${row.valueNumber===null?null:String(row.valueNumber)},${row.valueBoolean===null?null:Boolean(row.valueBoolean)},${row.valueDateTime===null?null:String(row.valueDateTime)},${row.valueRef===null?null:String(row.valueRef)},${row.valueJson===null?null:String(row.valueJson)},${legacy},${row.legacySourceHash===null?null:String(row.legacySourceHash)},${row.hostSessionId===null||row.hostSessionId===undefined?null:String(row.hostSessionId)},${createdAt},${updatedAt})`)}
}

async function reverseStagedItem(tx:FieldsTransaction,sessionId:string,staged:StagedItem):Promise<void>{
  if(staged.action==='skip')return
  const after=plain(staged.after,'staged.after')
  if(staged.kind==='groupVersion'){
    await tx.execute(sql`DELETE FROM field_definition_versions WHERE group_key=${boundedString(after.groupKey,'staged.groupVersion.groupKey',128)} AND revision=${positive(after.revision,'staged.groupVersion.revision')} AND origin='import' AND host_session_id=${sessionId}`);return
  }
  if(staged.kind==='groupCurrent'){
    const definition=plain(after.definition,'staged.groupCurrent.definition');await restoreCurrentRow(tx,'group',staged.before===null?null:plain(staged.before,'staged.groupCurrent.before'),boundedString(definition.key,'staged.groupCurrent.key',128),sessionId);return
  }
  if(staged.kind==='optionsPage'){
    const definition=plain(after.definition,'staged.optionsPage.definition'),key=boundedString(definition.key,'staged.optionsPage.key',128),revision=positive(after.currentRevision,'staged.optionsPage.currentRevision'),bundle=staged.before===null?null:plain(staged.before,'staged.optionsPage.before'),versionBefore=bundle&&bundle.version?plain(bundle.version,'staged.optionsPage.before.version'):null,currentBefore=bundle&&bundle.current?plain(bundle.current,'staged.optionsPage.before.current'):null
    if(!versionBefore)await tx.execute(sql`DELETE FROM field_options_definition_versions WHERE options_key=${key} AND revision=${revision} AND origin='import' AND host_session_id=${sessionId}`)
    await restoreCurrentRow(tx,'options',currentBefore,key,sessionId);return
  }
  if(staged.kind==='blockVersion'){
    await tx.execute(sql`DELETE FROM field_block_definition_versions WHERE block_key=${boundedString(after.blockKey,'staged.blockVersion.blockKey',128)} AND revision=${positive(after.revision,'staged.blockVersion.revision')} AND origin='import' AND host_session_id=${sessionId}`);return
  }
  if(staged.kind==='blockCurrent'){
    const definition=plain(after.definition,'staged.blockCurrent.definition');await restoreCurrentRow(tx,'block',staged.before===null?null:plain(staged.before,'staged.blockCurrent.before'),boundedString(definition.key,'staged.blockCurrent.key',128),sessionId);return
  }
  if(staged.kind==='entity'){
    const refValue=ref(after.sourceRef,'staged.entity.sourceRef'),values=plain(after.values,'staged.entity.values');await restoreNodeRows(tx,refValue,Object.keys(values),staged.before);return
  }
  if(staged.kind==='optionsValues'){
    const pageKey=boundedString(after.pageKey,'staged.options.pageKey',128),values=plain(after.values,'staged.options.values');await restoreNodeRows(tx,{entityType:'options',entityId:pageKey},Object.keys(values),staged.before);return
  }
  if(staged.kind==='revision'){
    const id=boundedString(after.destinationRevisionId,'staged.revision.destinationRevisionId',128);if(staged.before===null)await tx.execute(sql`DELETE FROM field_revisions WHERE id=${id} AND host_session_id=${sessionId}`);return
  }
  if(staged.kind==='blockDocument'){
    const refValue=ref(after.sourceRef,'staged.blockDocument.sourceRef')
    if(staged.before===null){await tx.execute(sql`DELETE FROM field_block_documents WHERE entity_type=${refValue.entityType} AND entity_id=${refValue.entityId} AND host_session_id=${sessionId}`);return}
    const before=plain(staged.before,'staged.blockDocument.before'),document=typeof before.document==='string'?before.document:JSON.stringify(before.document),definitionVersions=JSON.stringify(before.definitionVersions),hostSession=before.hostSessionId===null||before.hostSessionId===undefined?null:String(before.hostSessionId)
    await tx.execute(sql`INSERT INTO field_block_documents (entity_type,entity_id,version,document,definition_versions,parent_revision_id,host_session_id,updated_by,updated_at) VALUES (${boundedString(before.entityType,'before.block.entityType',256)},${boundedString(before.entityId,'before.block.entityId',512)},${positive(before.version,'before.block.version')},${document},${definitionVersions},${boundedString(before.parentRevisionId,'before.block.parentRevisionId',512)},${hostSession},${boundedString(before.updatedBy,'before.block.updatedBy',512)},${dateString(before.updatedAt,'before.block.updatedAt')}) 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,host_session_id=EXCLUDED.host_session_id,updated_by=EXCLUDED.updated_by,updated_at=EXCLUDED.updated_at`);return
  }
  fail('integrity',`unknown staged item kind ${staged.kind}`)
}

export async function reversePublishedFieldsImport(
  tx:FieldsTransaction,
  reversePlan:FieldsPublishedReversePlan,
  command:FieldsPublishedReverseCommand,
  registry:FieldTypeRegistry,
  codeRegistry:CodeFieldsRegistrySnapshot,
  context:FieldsContext,
  effects?:FieldsImportEffects,
):Promise<FieldsImportJournal>{
  assertActiveFieldsTransaction(tx)
  boundedString(reversePlan.importId,'reversePlan.importId',128)
  const scope=validateCommand(command,false),authorized=requireAuthorizedFieldsContext(context),scopePolicy=await assertScopeAuthorized(context,scope)
  if(command.expectedReversePlanId!==reversePlan.reversePlanId||command.expectedPublishedValueCorpusVersion!==reversePlan.publishedValueCorpusVersion)fail('operation-conflict','reverse command identity differs from reverse plan')
  const reverseIdentity={importId:reversePlan.importId,originalPlanId:reversePlan.originalPlanId,manifestHash:reversePlan.manifestHash,publishedValueCorpusVersion:reversePlan.publishedValueCorpusVersion,beforeImageHash:reversePlan.beforeImageHash,counts:reversePlan.counts,scope:reversePlan.scope,requiredAuthority:reversePlan.requiredAuthority,authorizationPolicyVersion:reversePlan.authorizationPolicyVersion,registryVersion:registry.version,codeRegistryVersion:codeRegistry.version,codeRegistryHash:codeRegistry.canonicalHash}
  if(await fieldsRequestHash(reverseIdentity)!==reversePlan.reversePlanId)fail('integrity','reverse plan identity is invalid')
  const requestHash=await fieldsRequestHash({kind:'reverse-published',reversePlanId:reversePlan.reversePlanId,command,registryVersion:registry.version,codeRegistryVersion:codeRegistry.version,codeRegistryHash:codeRegistry.canonicalHash})
  const receipt=await readFieldsReceipt(tx,command.operationId)
  if(receipt){assertReceiptReplay(receipt,{kind:'fields:import-reverse',context:authorized,requestHash});return receiptJournal(receipt.result)}

  const current=await readJournal(tx,reversePlan.importId)
  if(!current)fail('access-denied','import session is unavailable')
  const plan=current.payload.plan
  await assertPlanIdentity(plan)
  if(!sameScope(scope,plan.scope)||!sameScope(scope,reversePlan.scope)||command.expectedPlanId!==plan.planId||command.expectedManifestHash!==plan.manifestHash||reversePlan.originalPlanId!==plan.planId||reversePlan.manifestHash!==plan.manifestHash)fail('operation-conflict','reverse command identity differs from import plan')
  if(scopePolicy!==reversePlan.authorizationPolicyVersion)fail('access-denied','import session is unavailable')
  if(current.journal.state!=='published'||current.journal.nextSection!==null||current.journal.nextOffset!==0)fail('stale-journal','import is not published')
  const policy=await authorizePlan(context,plan)
  if(policy!==reversePlan.authorizationPolicyVersion)fail('access-denied','import session is unavailable')
  await assertRuntimeBindings(tx,plan,registry,codeRegistry,false)
  if(current.payload.publishedValueCorpusVersion!==reversePlan.publishedValueCorpusVersion||await valueCorpusVersion(tx,scope)!==reversePlan.publishedValueCorpusVersion)fail('stale-plan','published corpus changed after reverse planning')
  const staged=await readVerifiedStaging(tx,current.journal,plan)
  if(await stagingBeforeImageHash(staged)!==reversePlan.beforeImageHash)fail('stale-plan','retained before-image ledger changed')

  const nextVersion=await casJournal(tx,current,command,{state:'reversed',section:null,offset:0})
  for(const entry of [...staged].reverse())await reverseStagedItem(tx,current.journal.staging.hostSessionId,entry.item)
  const restoredCorpus=await valueCorpusVersion(tx,scope)
  if(restoredCorpus!==plan.valueCorpusVersion)fail('integrity','published reverse did not restore the original corpus')
  const journal:FieldsImportJournal=Object.freeze({...current.journal,version:String(nextVersion),state:'reversed',nextSection:null,nextOffset:0})
  await emitImportEvent(tx,effects,importEvent('reversed',command.operationId,journal,plan,authorized.principal.id))
  await writeFieldsReceipt(tx,{operationId:command.operationId,kind:'fields:import-reverse',context:authorized,requestHash,result:journalStorage(journal)})
  return journal
}
