import { createMockFacade } from './facade/mockFacade'
import { createSpreadsheet } from './facade/spreadsheet'

type SpreadsheetReport = {
  schemaVersion: 1
  status: 'pass' | 'failed'
  browserId: 'chrome'
  crossOriginIsolated: boolean
  cells?: { width: number; length: number; updatedLength: number; dimension: string }
  aliases?: string[]
  dependencies?: string[]
  cycle?: { detected: boolean; cells: string[] }
  binding?: { alias: string; objectId: string; propertyName: string; appliedValue: number; undoValue: number; redoValue: number }
  expression?: { value: number; dimension: string; references: string[] }
  layout?: { styledCells: number; namedRange: string; mergedRange: string; shiftedFormulaAddress: string }
  csv?: { rows: number; columns: number; bytes: number }
  largeSheet?: { cells: number; rows: number; columns: number; evaluationErrors: number; csvBytes: number; durationMs: number; budgetMs: number }
  persistence?: { mode: string; savedVersion: number; reopenedVersion: number; reopenedValue: number }
  resource?: { hash: string; byteLength: number; roundTrip: boolean; released: boolean }
  opfs?: { markerSuite: string; markerCells: number; markerRemoved: boolean }
  afterRelease?: { shapeCount: number; kernelReferenceCount: number }
  error?: string
}

const propertyValue = (facade: ReturnType<typeof createMockFacade>, objectId: string, name: string) => facade.app.document.getObject(objectId)?.properties.find((property) => property.name === name)?.value

const run = async (): Promise<SpreadsheetReport> => {
  const facade = createMockFacade()
  const report: SpreadsheetReport = { schemaVersion: 1, status: 'failed', browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true }
  try {
    if (!report.crossOriginIsolated || !navigator.storage?.getDirectory) throw new Error('Chrome Spreadsheet evidence requires isolated OPFS.')
    await facade.project.list()
    const sheet = createSpreadsheet('spreadsheet', 'Parameters')
    sheet.setCell('A1', '6 mm', { alias: 'Width' })
    sheet.setCell('A2', '=Width * 2', { alias: 'Length' })
    sheet.setCell('B1', '=max(A1, 4 mm)')
    const binding = sheet.bindProperty('Length', 'pad', 'Length')
    const first = sheet.evaluate()
    const width = first.cells.A1.value
    const length = first.cells.A2.value
    if (!width || !length || first.errors.length > 0) throw new Error(`Spreadsheet formula evaluation failed: ${JSON.stringify(first.errors)}`)

    const expression = facade.app.expression.evaluate('Spreadsheet.Length + 1 mm', { 'Spreadsheet.Length': length })
    facade.app.document.setProperty({ objectId: binding.objectId, propertyName: binding.propertyName, value: length.value })
    sheet.setCell('A1', '8 mm', { alias: 'Width' })
    const updated = sheet.evaluate()
    const updatedLength = updated.cells.A2.value
    if (!updatedLength) throw new Error('Spreadsheet alias update did not recompute Length.')
    facade.app.document.setProperty({ objectId: binding.objectId, propertyName: binding.propertyName, value: updatedLength.value })
    const appliedValue = Number(propertyValue(facade, 'pad', 'Length'))
    facade.history.undo()
    const undoValue = Number(propertyValue(facade, 'pad', 'Length'))
    facade.history.redo()
    const redoValue = Number(propertyValue(facade, 'pad', 'Length'))

    sheet.setCell('C1', '=D1 + 1')
    sheet.setCell('D1', '=C1 + 1')
    const diagnosed = sheet.evaluate()
    const cycleCells = [...new Set(diagnosed.cycles.flat())].sort()
    const csv = sheet.exportCsv()
    const csvRows = csv.split('\n')
    const layout = createSpreadsheet('layout', 'Layout')
    layout.setCell('A1', 1)
    layout.setCell('A2', '=A1 + 1')
    const styledCells = layout.setStyle('A1:B2', { background: '#e6f4f1', bold: true, numberFormat: '0.00' })
    layout.mergeCells('B1:A2')
    layout.setNamedRange('Inputs', 'A1:A2')
    layout.insertRows(2)
    layout.insertColumns(2)
    const layoutSnapshot = layout.snapshot()

    const largeStarted = performance.now()
    const large = createSpreadsheet('large', 'Large sheet')
    const columnName = (column: number) => { let value = ''; for (let current = column; current > 0; current = Math.floor((current - 1) / 26)) value = String.fromCharCode(65 + ((current - 1) % 26)) + value; return value }
    for (let row = 1; row <= 100; row += 1) for (let column = 1; column <= 100; column += 1) large.setCell(`${columnName(column)}${row}`, row + column)
    const largeEvaluation = large.evaluate()
    const largeCsv = large.exportCsv()
    const largeDurationMs = performance.now() - largeStarted

    const document = facade.app.document.getActive()
    const saved = await facade.project.save(document)
    const reopened = await facade.project.load(document.id)
    const reopenedValue = Number(reopened?.objects.find((object) => object.id === 'pad')?.properties.find((property) => property.name === 'Length')?.value)
    if (!reopened || reopenedValue !== updatedLength.value) throw new Error('Spreadsheet-bound property did not survive the OPFS project round-trip.')

    const payload = new TextEncoder().encode(JSON.stringify({ sheet: sheet.snapshot(), layout: layoutSnapshot }))
    const stored = await facade.project.resource.put(payload, 'application/vnd.bitbybit.spreadsheet+json')
    const resourceRoundTrip = await facade.project.resource.get(stored.hash)
    const roundTrip = resourceRoundTrip !== null && new TextDecoder().decode(resourceRoundTrip) === new TextDecoder().decode(payload)
    await facade.project.resource.release(stored.hash)
    const released = await facade.project.resource.get(stored.hash) === null

    const directory = await navigator.storage.getDirectory()
    const markerName = 'spreadsheet-chrome-marker.json'
    const marker = await directory.getFileHandle(markerName, { create: true })
    const writable = await marker.createWritable()
    await writable.write(JSON.stringify({ suite: 'SS-ALL', cells: sheet.snapshot().cells.length + layoutSnapshot.cells.length }))
    await writable.close()
    const markerPayload = JSON.parse(await (await marker.getFile()).text()) as { suite: string; cells: number }
    await directory.removeEntry(markerName)
    let markerRemoved = false
    try { await directory.getFileHandle(markerName) } catch (error) { markerRemoved = error instanceof DOMException && error.name === 'NotFoundError' }

    report.cells = { width: width.value, length: length.value, updatedLength: updatedLength.value, dimension: updatedLength.dimension }
    report.aliases = sheet.snapshot().cells.flatMap((cell) => cell.alias ? [cell.alias] : []).sort()
    report.dependencies = first.dependencies.map((dependency) => `${dependency.source}->${dependency.target}:${dependency.reference}`).sort()
    report.cycle = { detected: diagnosed.cycles.length > 0, cells: cycleCells }
    report.binding = { ...binding, appliedValue, undoValue, redoValue }
    report.expression = { value: expression.value.value, dimension: expression.value.dimension, references: expression.references }
    report.layout = { styledCells: styledCells.length, namedRange: layoutSnapshot.namedRanges[0]?.range ?? '', mergedRange: layoutSnapshot.mergedRanges[0] ?? '', shiftedFormulaAddress: layoutSnapshot.cells.find((cell) => cell.input === '=A1 + 1')?.address ?? '' }
    report.csv = { rows: csvRows.length, columns: Math.max(...csvRows.map((row) => row.split(',').length)), bytes: new TextEncoder().encode(csv).byteLength }
    report.largeSheet = { cells: large.snapshot().cells.length, rows: 100, columns: 100, evaluationErrors: largeEvaluation.errors.length, csvBytes: new TextEncoder().encode(largeCsv).byteLength, durationMs: largeDurationMs, budgetMs: 5000 }
    report.persistence = { mode: facade.project.capabilities().mode, savedVersion: saved.documentVersion, reopenedVersion: reopened.version, reopenedValue }
    report.resource = { hash: stored.hash, byteLength: stored.byteLength, roundTrip, released }
    report.opfs = { markerSuite: markerPayload.suite, markerCells: markerPayload.cells, markerRemoved }
    report.status = report.cells.width === 6 && report.cells.length === 12 && report.cells.updatedLength === 16 && report.cells.dimension === 'length'
      && JSON.stringify(report.aliases) === JSON.stringify(['Length', 'Width'])
      && report.dependencies.length === 2 && report.cycle.detected && JSON.stringify(report.cycle.cells) === JSON.stringify(['C1', 'D1'])
      && report.binding.appliedValue === 16 && report.binding.undoValue === 12 && report.binding.redoValue === 16
      && report.expression.value === 13 && report.expression.dimension === 'length' && report.expression.references.includes('Spreadsheet.Length')
      && report.layout.styledCells === 4 && report.layout.namedRange === 'A1:A3' && report.layout.mergedRange === 'A1:C3' && report.layout.shiftedFormulaAddress === 'A3'
      && report.largeSheet.cells === 10000 && report.largeSheet.evaluationErrors === 0 && report.largeSheet.csvBytes > 0 && report.largeSheet.durationMs <= report.largeSheet.budgetMs
      && report.persistence.mode === 'sqlite-opfs' && report.resource.roundTrip && report.resource.released
      && report.opfs.markerSuite === 'SS-ALL' && report.opfs.markerCells === 9 && report.opfs.markerRemoved ? 'pass' : 'failed'
  } catch (error) {
    report.error = error instanceof Error ? error.stack || error.message : String(error)
  } finally {
    facade.geometry.dispose()
    await new Promise((resolve) => setTimeout(resolve, 0))
    report.afterRelease = { shapeCount: facade.geometry.capabilities().shapeCount, kernelReferenceCount: facade.geometry.capabilities().kernelReferenceCount }
    if (report.afterRelease.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) { report.status = 'failed'; report.error = `${report.error ? `${report.error} ` : ''}Shape ownership gate failed.` }
  }
  return report
}

run().then((report) => {
  ;(window as Window & { __bitbybitSpreadsheetReport?: SpreadsheetReport }).__bitbybitSpreadsheetReport = report
  document.documentElement.dataset.status = report.status
  document.querySelector('#result')!.textContent = JSON.stringify(report, null, 2)
}).catch((error) => {
  document.documentElement.dataset.status = 'failed'
  document.querySelector('#result')!.textContent = error instanceof Error ? error.stack || error.message : String(error)
})

export {}
