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

type PlotReport = {
  schemaVersion: 1
  status: 'pass' | 'failed'
  browserId: 'chrome'
  crossOriginIsolated: boolean
  series?: { count: number; boundSource: string; points: number; updatedValue: number; legend: boolean }
  axes?: { x: string; y: string; xRange: number[]; yRange: number[] }
  logAxis?: { xScale: string; yScale: string; svgHasLogMapping: boolean; rejectedNonPositive: boolean }
  svg?: { bytes: number; sha256: string; deterministic: boolean; paths: number; accessible: boolean }
  png?: { bytes: number; sha256: string; deterministic: boolean; width: number; height: number; validSignature: boolean }
  csv?: { bytes: number; rows: number; sha256: string }
  resources?: Array<{ mediaType: string; hash: string; byteLength: number; roundTrip: boolean; released: boolean }>
  opfs?: { markerSuite: string; markerSeries: number; markerRemoved: boolean }
  afterRelease?: { shapeCount: number; kernelReferenceCount: number }
  error?: string
}

const sha256 = async (bytes: Uint8Array) => [...new Uint8Array(await crypto.subtle.digest('SHA-256', Uint8Array.from(bytes).buffer))].map((value) => value.toString(16).padStart(2, '0')).join('')

const rasterizeSvg = async (svg: string, width: number, height: number) => {
  const url = URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }))
  try {
    const source = new Image()
    await new Promise<void>((resolve, reject) => {
      source.addEventListener('load', () => resolve(), { once: true })
      source.addEventListener('error', () => reject(new Error('SVG image decoding failed.')), { once: true })
      source.src = url
    })
    const canvas = document.createElement('canvas')
    canvas.width = width
    canvas.height = height
    const context = canvas.getContext('2d')
    if (!context) throw new Error('Canvas 2D context is unavailable.')
    context.fillStyle = '#ffffff'
    context.fillRect(0, 0, width, height)
    context.drawImage(source, 0, 0, width, height)
    const blob = await new Promise<Blob>((resolve, reject) => canvas.toBlob((result) => result ? resolve(result) : reject(new Error('PNG encoding failed.')), 'image/png'))
    return new Uint8Array(await blob.arrayBuffer())
  } finally {
    URL.revokeObjectURL(url)
  }
}

const run = async (): Promise<PlotReport> => {
  const facade = createMockFacade()
  const report: PlotReport = { schemaVersion: 1, status: 'failed', browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true }
  try {
    if (!report.crossOriginIsolated || !navigator.storage?.getDirectory) throw new Error('Chrome Plot evidence requires isolated OPFS.')
    await facade.project.list()
    const sheet = createSpreadsheet('Spreadsheet', 'Plot data')
    sheet.setCell('A1', 0); sheet.setCell('A2', 1); sheet.setCell('A3', 2)
    sheet.setCell('B1', '6 mm'); sheet.setCell('B2', '12 mm'); sheet.setCell('B3', '16 mm')
    const cells = sheet.evaluate().cells
    const boundPoints = ['1', '2', '3'].map((row) => ({ x: cells[`A${row}`].value!.value, y: cells[`B${row}`].value!.value }))

    const plot = createPlot('plot', 'Length history')
    plot.setAxes({ x: { label: 'Revision', scale: 'linear', minimum: 0, maximum: 2 }, y: { label: 'Length (mm)', scale: 'linear', minimum: 0, maximum: 20 } })
    plot.setSeries({ id: 'length', label: 'Measured length', points: boundPoints.slice(0, 2), style: { color: '#007f86', lineWidth: 2 } })
    plot.bindSeries('length', { sourceId: 'Spreadsheet', xRange: 'A1:A3', yRange: 'B1:B3' })
    plot.setSeries({ id: 'target', label: 'Target', points: [{ x: 0, y: 10 }, { x: 1, y: 10 }, { x: 2, y: 10 }], style: { color: '#c24135', lineWidth: 1.5, dash: [6, 3] } })
    plot.updateBoundSeries('length', boundPoints)
    const snapshot = plot.snapshot()
    const svg = plot.exportSvg(640, 360)
    const secondSvg = plot.exportSvg(640, 360)
    const csv = plot.exportCsv()
    const svgBytes = new TextEncoder().encode(svg)
    const csvBytes = new TextEncoder().encode(csv)
    const pngBytes = await rasterizeSvg(svg, 640, 360)
    const secondPngBytes = await rasterizeSvg(svg, 640, 360)
    const svgHash = await sha256(svgBytes)
    const csvHash = await sha256(csvBytes)
    const pngHash = await sha256(pngBytes)
    const logPlot = createPlot('log-plot', 'Log history')
    logPlot.setAxes({ x: { label: 'Log X', scale: 'log', minimum: 1, maximum: 100 }, y: { label: 'Log Y', scale: 'log', minimum: 1, maximum: 1000 } })
    logPlot.setSeries({ id: 'log', label: 'Log series', points: [{ x: 1, y: 1 }, { x: 10, y: 10 }, { x: 100, y: 1000 }], style: { color: '#007f86', lineWidth: 1 } })
    const logSnapshot = logPlot.snapshot()
    const logSvg = logPlot.exportSvg()
    let rejectedNonPositive = false
    try { logPlot.setSeries({ id: 'invalid-log', label: 'Invalid', points: [{ x: 0, y: 1 }, { x: 1, y: 2 }], style: { color: '#c24135', lineWidth: 1 } }) } catch { rejectedNonPositive = true }
    const resources: PlotReport['resources'] = []
    for (const [bytes, mediaType] of [[svgBytes, 'image/svg+xml'], [csvBytes, 'text/csv'], [pngBytes, 'image/png']] as const) {
      const stored = await facade.project.resource.put(bytes, mediaType)
      const loaded = await facade.project.resource.get(stored.hash)
      const roundTrip = loaded !== null && await sha256(loaded) === await sha256(bytes)
      await facade.project.resource.release(stored.hash)
      const released = await facade.project.resource.get(stored.hash) === null
      resources.push({ mediaType, hash: stored.hash, byteLength: stored.byteLength, roundTrip, released })
    }
    const directory = await navigator.storage.getDirectory()
    const markerName = 'plot-chrome-marker.json'
    const marker = await directory.getFileHandle(markerName, { create: true })
    const writable = await marker.createWritable()
    await writable.write(JSON.stringify({ suite: 'PLOT-ALL', series: snapshot.series.length }))
    await writable.close()
    const markerPayload = JSON.parse(await (await marker.getFile()).text()) as { suite: string; series: number }
    await directory.removeEntry(markerName)
    let markerRemoved = false
    try { await directory.getFileHandle(markerName) } catch (error) { markerRemoved = error instanceof DOMException && error.name === 'NotFoundError' }

    report.series = { count: snapshot.series.length, boundSource: snapshot.series.find((series) => series.id === 'length')?.binding?.sourceId ?? '', points: snapshot.series.find((series) => series.id === 'length')?.points.length ?? 0, updatedValue: snapshot.series.find((series) => series.id === 'length')?.points.at(-1)?.y ?? Number.NaN, legend: snapshot.legend }
    report.axes = { x: snapshot.xAxis.label, y: snapshot.yAxis.label, xRange: [snapshot.xAxis.minimum!, snapshot.xAxis.maximum!], yRange: [snapshot.yAxis.minimum!, snapshot.yAxis.maximum!] }
    report.logAxis = { xScale: logSnapshot.xAxis.scale, yScale: logSnapshot.yAxis.scale, svgHasLogMapping: logSvg.includes('data-x-scale="log" data-y-scale="log"') && /M56\.000 312\.000/.test(logSvg), rejectedNonPositive }
    report.svg = { bytes: svgBytes.byteLength, sha256: svgHash, deterministic: svg === secondSvg, paths: (svg.match(/<path /g) ?? []).length, accessible: svg.includes('role="img"') && svg.includes('aria-label="Length history"') }
    report.png = { bytes: pngBytes.byteLength, sha256: pngHash, deterministic: pngHash === await sha256(secondPngBytes), width: 640, height: 360, validSignature: [137, 80, 78, 71, 13, 10, 26, 10].every((value, index) => pngBytes[index] === value) }
    report.csv = { bytes: csvBytes.byteLength, rows: csv.split('\n').length, sha256: csvHash }
    report.resources = resources
    report.opfs = { markerSuite: markerPayload.suite, markerSeries: markerPayload.series, markerRemoved }
    report.status = report.series.count === 2 && report.series.boundSource === 'Spreadsheet' && report.series.points === 3 && report.series.updatedValue === 16 && report.series.legend
      && JSON.stringify(report.axes.xRange) === JSON.stringify([0, 2]) && JSON.stringify(report.axes.yRange) === JSON.stringify([0, 20])
      && report.logAxis.xScale === 'log' && report.logAxis.yScale === 'log' && report.logAxis.svgHasLogMapping && report.logAxis.rejectedNonPositive
      && report.svg.bytes > 0 && report.svg.deterministic && report.svg.paths === 2 && report.svg.accessible && report.png.bytes > 0 && report.png.deterministic && report.png.validSignature && report.png.width === 640 && report.png.height === 360 && report.csv.rows === 7
      && resources.every((resource) => resource.roundTrip && resource.released) && report.opfs.markerSuite === 'PLOT-ALL' && report.opfs.markerSeries === 2 && 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 & { __bitbybitPlotReport?: PlotReport }).__bitbybitPlotReport = 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 {}
