import sqlite3InitModule, { type Database, type Sqlite3Static } from '@sqlite.org/sqlite-wasm'
import { PROJECT_SCHEMA_MIGRATIONS, PROJECT_SCHEMA_VERSION, runProjectSchemaMigrations } from './projectSchema'
import { assessResourceQuota, planResourceSweep, type ResourceSweepRecord } from './resourcePolicy'
import type { DocumentObjectSnapshot, DocumentSnapshot, ModelTreeItem, ObjectPropertySnapshot, PersistenceCapabilities, ProjectRecoveryReport, ProjectResource, ProjectResourceSweepReport, ProjectSummary } from './types'

type PersistenceRequest =
  | { id: number; type: 'initialize'; databasePath?: string; migrationFailureVersion?: number; migrationInterruptAfterVersion?: number }
  | { id: number; type: 'list-projects' }
  | { id: number; type: 'save-document'; document: DocumentSnapshot }
  | { id: number; type: 'load-document'; documentId: string }
  | { id: number; type: 'load-checkpoint'; documentId: string; version?: number }
  | { id: number; type: 'recovery-report'; documentId: string }
  | { id: number; type: 'put-resource'; bytes: ArrayBuffer; mediaType: string }
  | { id: number; type: 'get-resource'; hash: string }
  | { id: number; type: 'release-resource'; hash: string }
  | { id: number; type: 'sweep-resources' }
  | { id: number; type: 'dispose' }

type PersistenceResponse =
  | { id: number; ok: true; type: 'initialized'; capabilities: PersistenceCapabilities }
  | { id: number; ok: true; type: 'projects-listed'; projects: ProjectSummary[] }
  | { id: number; ok: true; type: 'saved'; documentId: string; documentVersion: number; persistedAt: number; mode: PersistenceCapabilities['mode'] }
  | { id: number; ok: true; type: 'loaded' | 'checkpoint-loaded'; document: DocumentSnapshot | null }
  | { id: number; ok: true; type: 'recovery-report'; report: ProjectRecoveryReport }
  | { id: number; ok: true; type: 'resource-put'; resource: ProjectResource }
  | { id: number; ok: true; type: 'resource-get'; bytes: ArrayBuffer | null }
  | { id: number; ok: true; type: 'resource-released' }
  | { id: number; ok: true; type: 'resources-swept'; report: ProjectResourceSweepReport }
  | { id: number; ok: true; type: 'disposed' }
  | { id: number; ok: false; error: string }

type WorkerScope = {
  postMessage(message: PersistenceResponse): void
  onmessage: ((event: MessageEvent<PersistenceRequest>) => void) | null
}

const workerScope = globalThis as unknown as WorkerScope
let sqlite3: Sqlite3Static | undefined
let database: Database | undefined
let databasePath = '/bitbybit-project.sqlite3'
let assetsDirectory: FileSystemDirectoryHandle | undefined
const transientResources = new Map<string, { resource: ProjectResource; bytes: Uint8Array }>()
let capabilities: PersistenceCapabilities = { mode: 'unavailable', sqliteWasm: false, opfs: false, schemaVersion: PROJECT_SCHEMA_VERSION, reason: 'SQLite WASM has not been initialized.' }

const cloneTree = (tree: ModelTreeItem[]) => tree.map((item) => ({ ...item, children: item.children ? [...item.children] : undefined }))

const getAssetsDirectory = async () => {
  if (assetsDirectory) return assetsDirectory
  if (!('storage' in navigator) || !navigator.storage.getDirectory) throw new Error('OPFS is unavailable for resource storage.')
  const root = await navigator.storage.getDirectory()
  assetsDirectory = await root.getDirectoryHandle('bitbybit-assets', { create: true })
  return assetsDirectory
}

const hashBytes = async (bytes: ArrayBuffer) => {
  const digest = await crypto.subtle.digest('SHA-256', bytes)
  return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, '0')).join('')
}

const initialize = async (requestedPath?: string, migrationFailureVersion?: number, migrationInterruptAfterVersion?: number): Promise<PersistenceCapabilities> => {
  if (database) return capabilities
  if (requestedPath !== undefined) {
    if (!/^\/[A-Za-z0-9._-]+\.sqlite3$/.test(requestedPath)) throw new Error('Persistence databasePath must be an absolute OPFS SQLite filename.')
    databasePath = requestedPath
  }
  sqlite3 = await sqlite3InitModule()
  const opfsAvailable = Boolean(sqlite3.oo1.OpfsDb)
  try {
    database = opfsAvailable ? new sqlite3.oo1.OpfsDb(databasePath) : new sqlite3.oo1.DB(':memory:', 'c')
    capabilities = { mode: opfsAvailable ? 'sqlite-opfs' : 'sqlite-memory', sqliteWasm: true, opfs: opfsAvailable, schemaVersion: PROJECT_SCHEMA_VERSION, reason: opfsAvailable ? undefined : 'OPFS VFS is unavailable; persistence is transient until export.' }
  } catch (error) {
    database = new sqlite3.oo1.DB(':memory:', 'c')
    capabilities = { mode: 'sqlite-memory', sqliteWasm: true, opfs: false, schemaVersion: PROJECT_SCHEMA_VERSION, reason: error instanceof Error ? `OPFS initialization failed: ${error.message}` : 'OPFS initialization failed.' }
  }
  database.exec('PRAGMA foreign_keys = ON; CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL);')
  let currentMigrationVersion: number | undefined
  runProjectSchemaMigrations({
    begin: () => { database?.exec('BEGIN;') },
    isApplied: (version) => { currentMigrationVersion = version; return (database?.exec({ sql: 'SELECT version FROM schema_migrations WHERE version = ?', bind: [version], returnValue: 'resultRows' }) as unknown[]).length > 0 },
    execute: (sql) => { if (migrationFailureVersion !== undefined && currentMigrationVersion === migrationFailureVersion) throw new Error(`Injected migration failure at schema version ${migrationFailureVersion}.`); database?.exec(sql) },
    markApplied: (version, appliedAt) => {
      database?.exec({ sql: 'INSERT INTO schema_migrations(version, applied_at) VALUES(?, ?)', bind: [version, appliedAt] })
      if (migrationInterruptAfterVersion === version) throw new Error(`Injected migration interruption after schema version ${version}.`)
    },
    commit: () => { database?.exec('COMMIT;') },
    rollback: () => { database?.exec('ROLLBACK;') },
  }, PROJECT_SCHEMA_MIGRATIONS)
  return capabilities
}

const saveDocument = (document: DocumentSnapshot) => {
  if (!database) throw new Error('Persistence database is not initialized.')
  const nativeObjectTags = new Set<number>()
  for (const object of document.objects) {
    if (object.nativeObjectTag === undefined) continue
    if (!Number.isSafeInteger(object.nativeObjectTag) || object.nativeObjectTag <= 0) throw new RangeError(`Object ${object.id} nativeObjectTag must be a positive safe integer.`)
    if (nativeObjectTags.has(object.nativeObjectTag)) throw new RangeError(`nativeObjectTag ${object.nativeObjectTag} is assigned to more than one object.`)
    nativeObjectTags.add(object.nativeObjectTag)
  }
  const now = Date.now()
  database.exec('BEGIN;')
  try {
    database.exec({ sql: 'INSERT INTO projects(id, name, schema_version, created_at, updated_at) VALUES(?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name=excluded.name, schema_version=excluded.schema_version, updated_at=excluded.updated_at', bind: [document.id, document.label, PROJECT_SCHEMA_VERSION, now, now] })
    database.exec({ sql: 'INSERT INTO documents(id, project_id, label, version, dirty, read_only, units, recompute_json) VALUES(?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET label=excluded.label, version=excluded.version, dirty=excluded.dirty, read_only=excluded.read_only, units=excluded.units, recompute_json=excluded.recompute_json', bind: [document.id, document.id, document.label, document.version, document.dirty ? 1 : 0, document.readOnly ? 1 : 0, document.units, JSON.stringify(document.recompute ?? null)] })
    database.exec({ sql: 'DELETE FROM objects WHERE document_id = ?', bind: [document.id] })
    const parentByChild = new Map<string, string>()
    for (const item of document.tree) for (const childId of item.children || []) parentByChild.set(childId, item.id)
    document.tree.forEach((item, ordinal) => { const object = document.objects.find((candidate) => candidate.id === item.id); database?.exec({ sql: 'INSERT INTO objects(id, document_id, parent_id, label, object_type, state, detail, children_json, ordinal, sketch_json, topology_json, native_object_tag) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', bind: [item.id, document.id, parentByChild.get(item.id) || null, item.label, item.type, item.state || null, item.detail || null, JSON.stringify(item.children || []), ordinal, object?.sketch ? JSON.stringify(object.sketch) : null, object?.topology ? JSON.stringify(object.topology) : null, object?.nativeObjectTag ?? null] }) })
    for (const object of document.objects) for (const property of object.properties) database.exec({ sql: 'INSERT INTO object_properties(document_id, object_id, name, value_json, property_type, updated_at) VALUES(?, ?, ?, ?, ?, ?)', bind: [document.id, object.id, property.name, JSON.stringify(property), property.type, now] })
    database.exec({ sql: 'DELETE FROM dependencies WHERE document_id = ?', bind: [document.id] })
    for (const edge of document.dependencies ?? []) database.exec({ sql: 'INSERT INTO dependencies(document_id, source_id, target_id, relation, property_name, reference) VALUES(?, ?, ?, ?, ?, ?)', bind: [document.id, edge.sourceId, edge.targetId, edge.relation, edge.propertyName ?? null, edge.reference ?? null] })
    database.exec({ sql: 'INSERT INTO document_checkpoints(document_id, version, snapshot_json, created_at) VALUES(?, ?, ?, ?) ON CONFLICT(document_id, version) DO UPDATE SET snapshot_json=excluded.snapshot_json, created_at=excluded.created_at', bind: [document.id, document.version, JSON.stringify(document), now] })
    database.exec({ sql: 'DELETE FROM document_checkpoints WHERE document_id = ? AND version NOT IN (SELECT version FROM document_checkpoints WHERE document_id = ? ORDER BY created_at DESC, version DESC LIMIT 5)', bind: [document.id, document.id] })
    database.exec('COMMIT;')
  } catch (error) {
    database.exec('ROLLBACK;')
    throw error
  }
  return { documentId: document.id, documentVersion: document.version, persistedAt: now, mode: capabilities.mode }
}

const listProjects = (): ProjectSummary[] => {
  if (!database) throw new Error('Persistence database is not initialized.')
  const rows = database.exec({
    sql: `SELECT documents.id AS document_id, documents.label, documents.version, documents.dirty, documents.read_only, projects.updated_at, COUNT(objects.id) AS object_count
      FROM documents
      JOIN projects ON projects.id = documents.project_id
      LEFT JOIN objects ON objects.document_id = documents.id
      GROUP BY documents.id, documents.label, documents.version, documents.dirty, documents.read_only, projects.updated_at
      ORDER BY projects.updated_at DESC`,
    rowMode: 'object',
    returnValue: 'resultRows',
  }) as Array<Record<string, string | number>>
  return rows.map((row) => ({ documentId: String(row.document_id), label: String(row.label), documentVersion: Number(row.version), objectCount: Number(row.object_count), dirty: Boolean(row.dirty), readOnly: Boolean(row.read_only), updatedAt: Number(row.updated_at) }))
}

const loadDocument = (documentId: string): DocumentSnapshot | null => {
  if (!database) throw new Error('Persistence database is not initialized.')
  const documents = database.exec({ sql: 'SELECT id, label, version, dirty, read_only, units, recompute_json FROM documents WHERE id = ?', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number | null>>
  const row = documents[0]
  if (!row) return null
  const objects = database.exec({ sql: 'SELECT id, label, object_type, state, detail, children_json, sketch_json, topology_json, native_object_tag FROM objects WHERE document_id = ? ORDER BY ordinal', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number | null>>
  const propertyRows = database.exec({ sql: 'SELECT object_id, value_json FROM object_properties WHERE document_id = ? ORDER BY object_id, name', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string>>
  const dependencyRows = database.exec({ sql: 'SELECT source_id, target_id, relation, property_name, reference FROM dependencies WHERE document_id = ?', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | null>>
  const propertiesByObject = new Map<string, ObjectPropertySnapshot[]>()
  for (const propertyRow of propertyRows) {
    const properties = propertiesByObject.get(String(propertyRow.object_id)) ?? []
    properties.push(JSON.parse(String(propertyRow.value_json)) as ObjectPropertySnapshot)
    propertiesByObject.set(String(propertyRow.object_id), properties)
  }
  const tree: ModelTreeItem[] = objects.map((object) => ({ id: String(object.id), label: String(object.label), type: String(object.object_type) as ModelTreeItem['type'], state: object.state ? String(object.state) as ModelTreeItem['state'] : undefined, detail: object.detail ? String(object.detail) : undefined, children: object.children_json ? JSON.parse(String(object.children_json)) as string[] : undefined }))
  const usedNativeObjectTags = new Set<number>()
  const persistedNativeObjectTags = new Map<string, number>()
  for (const object of objects) {
    if (object.native_object_tag === null || object.native_object_tag === undefined) continue
    const nativeObjectTag = Number(object.native_object_tag)
    if (!Number.isSafeInteger(nativeObjectTag) || nativeObjectTag <= 0) throw new Error(`Persisted object ${String(object.id)} has an invalid native object tag.`)
    if (usedNativeObjectTags.has(nativeObjectTag)) throw new Error(`Persisted native object tag ${nativeObjectTag} is duplicated.`)
    usedNativeObjectTags.add(nativeObjectTag)
    persistedNativeObjectTags.set(String(object.id), nativeObjectTag)
  }
  let nextNativeObjectTag = 1
  const objectSnapshots: DocumentObjectSnapshot[] = tree.map((item) => {
    const properties = propertiesByObject.get(item.id) ?? []
    const typeId = properties.find((property) => property.name === 'TypeId')?.value
    const row = objects.find((candidate) => String(candidate.id) === item.id)
    let nativeObjectTag = persistedNativeObjectTags.get(item.id)
    if (nativeObjectTag === undefined) {
      while (usedNativeObjectTags.has(nextNativeObjectTag)) nextNativeObjectTag += 1
      nativeObjectTag = nextNativeObjectTag
      usedNativeObjectTags.add(nativeObjectTag)
      nextNativeObjectTag += 1
    }
    return { id: item.id, typeId: typeof typeId === 'string' ? typeId : item.type, nativeObjectTag, properties, sketch: row?.sketch_json ? JSON.parse(String(row.sketch_json)) : undefined, topology: row?.topology_json ? JSON.parse(String(row.topology_json)) : undefined }
  })
  return {
    id: String(row.id),
    label: String(row.label),
    version: Number(row.version),
    dirty: Boolean(row.dirty),
    readOnly: Boolean(row.read_only),
    units: String(row.units),
    tree,
    objects: objectSnapshots,
    dependencies: dependencyRows.map((dependency) => ({ sourceId: String(dependency.source_id), targetId: String(dependency.target_id), relation: String(dependency.relation) as 'link' | 'expression' | 'topo-ref' | 'container' | 'view', propertyName: dependency.property_name ? String(dependency.property_name) : undefined, reference: dependency.reference ? String(dependency.reference) : undefined })),
    recompute: row.recompute_json ? JSON.parse(String(row.recompute_json)) : undefined,
  }
}

const recoveryReport = (documentId: string): ProjectRecoveryReport => {
  if (!database) throw new Error('Persistence database is not initialized.')
  const integrityRows = database.exec({ sql: 'PRAGMA integrity_check', rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string>>
  const integrityValue = String(integrityRows[0]?.integrity_check ?? '')
  const documents = database.exec({ sql: 'SELECT version, dirty FROM documents WHERE id = ?', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number>>
  const checkpoints = database.exec({ sql: 'SELECT version, created_at FROM document_checkpoints WHERE document_id = ? ORDER BY created_at DESC, version DESC', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number>>
  const warnings: string[] = []
  if (!documents[0]) warnings.push('No saved document exists for this ID.')
  if (integrityValue !== 'ok') warnings.push(`SQLite integrity check returned: ${integrityValue || 'no result'}.`)
  return { documentId, mode: capabilities.mode, schemaVersion: capabilities.schemaVersion, integrity: !documents[0] ? 'unavailable' : integrityValue === 'ok' ? 'ok' : 'failed', lastSavedVersion: documents[0] ? Number(documents[0].version) : null, dirtyAtLastSave: documents[0] ? Boolean(documents[0].dirty) : null, checkpoints: checkpoints.map((checkpoint) => ({ version: Number(checkpoint.version), createdAt: Number(checkpoint.created_at) })), warnings }
}

const loadCheckpoint = (documentId: string, version?: number): DocumentSnapshot | null => {
  if (!database) throw new Error('Persistence database is not initialized.')
  const rows = version === undefined
    ? database.exec({ sql: 'SELECT snapshot_json FROM document_checkpoints WHERE document_id = ? ORDER BY created_at DESC, version DESC LIMIT 1', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string>>
    : database.exec({ sql: 'SELECT snapshot_json FROM document_checkpoints WHERE document_id = ? AND version = ?', bind: [documentId, version], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string>>
  return rows[0]?.snapshot_json ? JSON.parse(String(rows[0].snapshot_json)) as DocumentSnapshot : null
}

const putResource = async (bytes: ArrayBuffer, mediaType: string): Promise<ProjectResource> => {
  if (!database) throw new Error('Persistence database is not initialized.')
  const hash = await hashBytes(bytes)
  if (!capabilities.opfs) {
    const existing = transientResources.get(hash)
    const resource = { hash, byteLength: bytes.byteLength, mediaType, refCount: (existing?.resource.refCount || 0) + 1 }
    transientResources.set(hash, { resource, bytes: new Uint8Array(bytes.slice(0)) })
    return { ...resource }
  }
  const existingRows = database.exec({ sql: 'SELECT hash, byte_length, media_type, ref_count FROM resources WHERE hash = ?', bind: [hash], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number>>
  const directory = await getAssetsDirectory()
  if (existingRows[0]) {
    try {
      await directory.getFileHandle(hash)
      database.exec({ sql: 'UPDATE resources SET ref_count = ref_count + 1, updated_at = ? WHERE hash = ?', bind: [Date.now(), hash] })
      const existing = existingRows[0]
      return { hash, byteLength: Number(existing.byte_length), mediaType: String(existing.media_type), refCount: Number(existing.ref_count) + 1 }
    } catch (error) {
      if (!(error instanceof DOMException && error.name === 'NotFoundError')) throw error
    }
  }
  const quota = assessResourceQuota(navigator.storage.estimate ? await navigator.storage.estimate() : {}, bytes.byteLength)
  if (!quota.allowed) throw new DOMException(quota.reason, 'QuotaExceededError')
  const handle = await directory.getFileHandle(hash, { create: true })
  const writable = await handle.createWritable()
  await writable.write(bytes)
  await writable.close()
  const now = Date.now()
  database.exec({ sql: 'INSERT INTO resources(hash, path, byte_length, media_type, ref_count, created_at, updated_at) VALUES(?, ?, ?, ?, 1, ?, ?) ON CONFLICT(hash) DO UPDATE SET ref_count=resources.ref_count + 1, updated_at=excluded.updated_at', bind: [hash, `bitbybit-assets/${hash}`, bytes.byteLength, mediaType, now, now] })
  const resources = database.exec({ sql: 'SELECT hash, byte_length, media_type, ref_count FROM resources WHERE hash = ?', bind: [hash], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number>>
  const row = resources[0]
  return { hash, byteLength: Number(row.byte_length), mediaType: String(row.media_type), refCount: Number(row.ref_count) }
}

type IterableDirectoryHandle = FileSystemDirectoryHandle & { entries(): AsyncIterableIterator<[string, FileSystemHandle]> }

const sweepResources = async (): Promise<ProjectResourceSweepReport> => {
  if (!database) throw new Error('Persistence database is not initialized.')
  if (!capabilities.opfs) return { inspectedRecords: transientResources.size, inspectedFiles: transientResources.size, deletedRecords: 0, deletedFiles: 0, reclaimedBytes: 0, missingFiles: [], warnings: ['SQLite memory mode has no separately enumerable orphan files.'] }
  const rows = database.exec({ sql: 'SELECT hash, byte_length, ref_count FROM resources', rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number>>
  const records: ResourceSweepRecord[] = rows.map((row) => ({ hash: String(row.hash), byteLength: Number(row.byte_length), refCount: Number(row.ref_count) }))
  const directory = await getAssetsDirectory() as IterableDirectoryHandle
  const fileSizes = new Map<string, number>()
  for await (const [name, handle] of directory.entries()) {
    if (handle.kind !== 'file') continue
    try { fileSizes.set(name, (await (handle as FileSystemFileHandle).getFile()).size) } catch { fileSizes.set(name, 0) }
  }
  const plan = planResourceSweep(records, fileSizes.keys())
  let deletedFiles = 0
  let reclaimedBytes = 0
  const warnings: string[] = []
  for (const hash of plan.deleteFileHashes) {
    try {
      await directory.removeEntry(hash)
      deletedFiles += 1
      reclaimedBytes += fileSizes.get(hash) ?? 0
    } catch (error) {
      warnings.push(`Unable to delete orphan resource ${hash}: ${error instanceof Error ? error.message : String(error)}`)
    }
  }
  database.exec('BEGIN;')
  try {
    for (const hash of plan.deleteRecordHashes) database.exec({ sql: 'DELETE FROM resources WHERE hash = ?', bind: [hash] })
    database.exec('COMMIT;')
  } catch (error) {
    database.exec('ROLLBACK;')
    throw error
  }
  if (plan.missingFileHashes.length > 0) warnings.push(`${plan.missingFileHashes.length} referenced resource file(s) are missing from OPFS.`)
  return { inspectedRecords: records.length, inspectedFiles: fileSizes.size, deletedRecords: plan.deleteRecordHashes.length, deletedFiles, reclaimedBytes, missingFiles: plan.missingFileHashes, warnings }
}

const getResource = async (hash: string): Promise<ArrayBuffer | null> => {
  if (!capabilities.opfs) {
    const resource = transientResources.get(hash)
    return resource ? resource.bytes.slice().buffer : null
  }
  const directory = await getAssetsDirectory()
  try {
    const handle = await directory.getFileHandle(hash)
    return (await handle.getFile()).arrayBuffer()
  } catch (error) {
    if (error instanceof DOMException && error.name === 'NotFoundError') return null
    throw error
  }
}

const releaseResource = async (hash: string) => {
  if (!database) throw new Error('Persistence database is not initialized.')
  if (!capabilities.opfs) {
    const existing = transientResources.get(hash)
    if (!existing) return
    if (existing.resource.refCount > 1) existing.resource.refCount -= 1
    else transientResources.delete(hash)
    return
  }
  const resources = database.exec({ sql: 'SELECT ref_count FROM resources WHERE hash = ?', bind: [hash], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number>>
  const row = resources[0]
  if (!row) return
  if (Number(row.ref_count) > 1) {
    database.exec({ sql: 'UPDATE resources SET ref_count = ref_count - 1, updated_at = ? WHERE hash = ?', bind: [Date.now(), hash] })
    return
  }
  database.exec({ sql: 'DELETE FROM resources WHERE hash = ?', bind: [hash] })
  try { await (await getAssetsDirectory()).removeEntry(hash) } catch (error) { if (!(error instanceof DOMException && error.name === 'NotFoundError')) throw error }
}

const handle = async (request: PersistenceRequest): Promise<PersistenceResponse> => {
  try {
    if (request.type === 'initialize') return { id: request.id, ok: true, type: 'initialized', capabilities: await initialize(request.databasePath, request.migrationFailureVersion, request.migrationInterruptAfterVersion) }
    if (request.type === 'dispose') { database?.close(); database = undefined; transientResources.clear(); return { id: request.id, ok: true, type: 'disposed' } }
    await initialize()
    if (request.type === 'list-projects') return { id: request.id, ok: true, type: 'projects-listed', projects: listProjects() }
    if (request.type === 'save-document') { const result = saveDocument(request.document); return { id: request.id, ok: true, type: 'saved', ...result } }
    if (request.type === 'load-document') return { id: request.id, ok: true, type: 'loaded', document: loadDocument(request.documentId) }
    if (request.type === 'load-checkpoint') return { id: request.id, ok: true, type: 'checkpoint-loaded', document: loadCheckpoint(request.documentId, request.version) }
    if (request.type === 'recovery-report') return { id: request.id, ok: true, type: 'recovery-report', report: recoveryReport(request.documentId) }
    if (request.type === 'put-resource') return { id: request.id, ok: true, type: 'resource-put', resource: await putResource(request.bytes, request.mediaType) }
    if (request.type === 'get-resource') return { id: request.id, ok: true, type: 'resource-get', bytes: await getResource(request.hash) }
    if (request.type === 'release-resource') { await releaseResource(request.hash); return { id: request.id, ok: true, type: 'resource-released' } }
    if (request.type === 'sweep-resources') return { id: request.id, ok: true, type: 'resources-swept', report: await sweepResources() }
    throw new Error('Unknown persistence request.')
  } catch (error) {
    return { id: request.id, ok: false, error: error instanceof Error ? error.message : String(error) }
  }
}

workerScope.onmessage = (event) => { void handle(event.data).then((response) => workerScope.postMessage(response)) }
