import type { DocPageLayout } from "@/lib/drive/doc-page-setup"
import { DOCS_PAGE_GAP_PX } from "@/lib/drive/docs-page-layout-constants"
import type { DocsExportSnapshot } from "@/lib/drive/docs-export-snapshot"

export type DocsPageCaptureTarget = {
  stack: HTMLElement
  pageWidth: number
  pageHeight: number
  pageCount: number
}

export function resolveDocsPageCaptureTarget(
  snapshot: DocsExportSnapshot
): DocsPageCaptureTarget | null {
  const stack = snapshot.getPageStackElement()
  if (!stack) return null

  return {
    stack,
    pageWidth: Number(stack.dataset.docsPageWidth ?? snapshot.pageLayout.widthPx),
    pageHeight: Number(stack.dataset.docsPageHeight ?? snapshot.pageLayout.heightPx),
    pageCount: snapshot.pageCount,
  }
}

function normalizeStackCloneForCapture(
  stack: HTMLElement,
  pageLayout: DocPageLayout,
  pageCount: number
): void {
  const pageWidth = pageLayout.widthPx
  const pageHeight = pageLayout.heightPx
  const pageStep = pageHeight + DOCS_PAGE_GAP_PX
  const gapTotal = Math.max(0, pageCount - 1) * DOCS_PAGE_GAP_PX

  stack.style.transform = "none"
  stack.style.transformOrigin = "top left"
  stack.style.left = "0"
  stack.style.marginLeft = "0"
  stack.style.position = "relative"
  stack.style.width = `${pageWidth}px`
  stack.classList.remove("left-1/2", "-translate-x-1/2", "top-0")

  stack.querySelectorAll<HTMLElement>(".docs-page-gap-band").forEach((el) => {
    el.style.display = "none"
  })
  stack.querySelectorAll<HTMLElement>(".docs-page-inter-margin-gutter").forEach((el) => {
    el.style.display = "none"
  })

  const stackHeight = parseFloat(stack.style.height)
  if (Number.isFinite(stackHeight) && gapTotal > 0) {
    stack.style.height = `${stackHeight - gapTotal}px`
  }

  stack.querySelectorAll<HTMLElement>("[style]").forEach((el) => {
    const top = parseFloat(el.style.top)
    if (!Number.isFinite(top) || top <= 0) return
    const pageIndex = Math.floor(top / pageStep)
    el.style.top = `${top - pageIndex * DOCS_PAGE_GAP_PX}px`
  })

  stack.querySelectorAll<HTMLElement>(".docs-page-flow-spacer").forEach((spacer) => {
    const height = parseFloat(spacer.style.height)
    if (Number.isFinite(height) && height >= DOCS_PAGE_GAP_PX) {
      spacer.style.height = `${height - DOCS_PAGE_GAP_PX}px`
    }
  })

  const surface = stack.querySelector<HTMLElement>(".ultidrive-docs-editor-surface")
  if (surface) {
    const surfaceHeight = parseFloat(surface.style.height)
    if (Number.isFinite(surfaceHeight) && gapTotal > 0) {
      surface.style.height = `${surfaceHeight - gapTotal}px`
    }
    surface.removeAttribute("contenteditable")
  }

  stack.querySelectorAll<HTMLElement>(".ProseMirror").forEach((el) => {
    el.removeAttribute("contenteditable")
  })
}

async function cloneStylesIntoDocument(targetDoc: Document): Promise<void> {
  const head = targetDoc.head
  const loads: Promise<void>[] = []

  const styleNodes = document.querySelectorAll('link[rel="stylesheet"], style')
  for (const node of styleNodes) {
    if (node instanceof HTMLLinkElement) {
      const href = node.href
      if (!href) continue
      const link = targetDoc.createElement("link")
      link.rel = "stylesheet"
      link.href = href
      if (node.media) link.media = node.media
      loads.push(
        new Promise<void>((resolve) => {
          link.onload = () => resolve()
          link.onerror = () => resolve()
        })
      )
      head.appendChild(link)
      continue
    }
    head.appendChild(node.cloneNode(true))
  }

  targetDoc.documentElement.lang = document.documentElement.lang
  targetDoc.documentElement.className = document.documentElement.className
  targetDoc.documentElement.classList.remove("dark")
  targetDoc.documentElement.style.background = "#ffffff"

  await Promise.all(loads)
  if (targetDoc.fonts?.ready) {
    await targetDoc.fonts.ready
  }
}

async function createCaptureFrame(
  stack: HTMLElement,
  pageLayout: DocPageLayout,
  pageCount: number
): Promise<{ root: HTMLElement; dispose: () => void }> {
  const pageWidth = pageLayout.widthPx
  const pageHeight = pageLayout.heightPx
  const stageHeight = pageCount * pageHeight

  const iframe = document.createElement("iframe")
  iframe.setAttribute("aria-hidden", "true")
  iframe.style.cssText =
    "position:fixed;top:0;left:0;width:1px;height:1px;border:0;opacity:0;pointer-events:none;overflow:hidden"

  const docReady = new Promise<Document>((resolve, reject) => {
    iframe.onload = () => {
      const doc = iframe.contentDocument
      if (doc) resolve(doc)
      else reject(new Error("Impossible de préparer la capture"))
    }
    iframe.onerror = () => reject(new Error("Impossible de préparer la capture"))
  })

  iframe.src = "about:blank"
  document.body.appendChild(iframe)

  const doc = await docReady
  await cloneStylesIntoDocument(doc)

  const clone = stack.cloneNode(true) as HTMLElement
  normalizeStackCloneForCapture(clone, pageLayout, pageCount)

  doc.body.replaceChildren()
  doc.body.style.cssText = `margin:0;padding:0;background:#ffffff;width:${pageWidth}px;height:${stageHeight}px;overflow:hidden`

  const mount = doc.createElement("div")
  mount.style.cssText = `position:relative;width:${pageWidth}px;height:${stageHeight}px;overflow:hidden`
  mount.appendChild(clone)
  doc.body.appendChild(mount)

  await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
  await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))

  return {
    root: clone,
    dispose: () => {
      iframe.remove()
    },
  }
}

async function rasterizePageSlice(
  element: HTMLElement,
  pageWidth: number,
  pageHeight: number,
  yOffset: number,
  scale: number
): Promise<HTMLCanvasElement> {
  const { default: html2canvas } = await import("html2canvas-pro")
  const canvas = await html2canvas(element, {
    backgroundColor: "#ffffff",
    scale,
    useCORS: true,
    logging: false,
    width: pageWidth,
    height: pageHeight,
    x: 0,
    y: yOffset,
    scrollX: 0,
    scrollY: 0,
    windowWidth: pageWidth,
    windowHeight: element.scrollHeight,
  })

  if (canvas.width === 0 || canvas.height === 0) {
    throw new Error("Capture page vide")
  }

  return canvas
}

async function withFrozenViewport<T>(fn: () => Promise<T>): Promise<T> {
  const scrollX = window.scrollX
  const scrollY = window.scrollY
  const html = document.documentElement
  const body = document.body
  const prevHtmlOverflow = html.style.overflow
  const prevBodyOverflow = body.style.overflow

  html.style.overflow = "hidden"
  body.style.overflow = "hidden"

  try {
    return await fn()
  } finally {
    html.style.overflow = prevHtmlOverflow
    body.style.overflow = prevBodyOverflow
    window.scrollTo(scrollX, scrollY)
  }
}

/** Rasterize each page from an isolated iframe clone — no live UI mutation. */
export async function captureDocsPagesFromCanvas(
  snapshot: DocsExportSnapshot,
  options?: { scale?: number }
): Promise<HTMLCanvasElement[]> {
  const target = resolveDocsPageCaptureTarget(snapshot)
  if (!target) {
    throw new Error("Page stack introuvable")
  }

  const { stack, pageWidth, pageHeight, pageCount } = target
  const preferredScale = options?.scale ?? 2

  return withFrozenViewport(async () => {
    const { root, dispose } = await createCaptureFrame(stack, snapshot.pageLayout, pageCount)

    try {
      const canvases: HTMLCanvasElement[] = []
      for (let pageIndex = 0; pageIndex < pageCount; pageIndex += 1) {
        const yOffset = pageIndex * pageHeight
        let canvas: HTMLCanvasElement
        try {
          canvas = await rasterizePageSlice(root, pageWidth, pageHeight, yOffset, preferredScale)
        } catch (firstError) {
          if (preferredScale === 1) throw firstError
          canvas = await rasterizePageSlice(root, pageWidth, pageHeight, yOffset, 1)
        }
        canvases.push(canvas)
      }
      return canvases
    } finally {
      dispose()
    }
  })
}
