import type { TipTapJSON } from "@/lib/drive/richtext-import"
import {
  buildGradientCss,
  DOCS_GRAPHIC_DEFAULTS,
  type DocsShapeType,
} from "./docs-graphic-types.ts"
import {
  parseVmlColorForDrawing,
  parseRotationDegFromStyle,
} from "./docx-drawing-vml.ts"
import { resolveDocxMediaDataUrl } from "./doc-page-background.ts"

type DocxArchive = Record<string, Uint8Array>

export type DocxBodyDrawing = {
  graphicType: "image" | "shape" | "gradient"
  src?: string
  shapeType?: DocsShapeType
  fill?: string
  stroke?: string
  strokeWidth?: number
  gradientCss?: string
  gradientAngle?: number
  gradientColor1?: string
  gradientColor2?: string
  width: number
  height: number
  x: number
  y: number
  rotationDeg: number
  wrap: "square" | "behind" | "in-front" | "inline"
  placement: "block" | "absolute" | "inline"
}

const EMU_PER_PX = 914400 / 96

function decodeXml(bytes: Uint8Array | undefined): string {
  if (!bytes) return ""
  return new TextDecoder().decode(bytes)
}

function emuToPx(emu: number): number {
  return Math.round(emu / EMU_PER_PX)
}

function readIntAttr(fragment: string, name: string): number | null {
  const match = fragment.match(new RegExp(`\\b${name}="(-?\\d+)"`, "i"))
  if (!match) return null
  const value = Number.parseInt(match[1] ?? "", 10)
  return Number.isFinite(value) ? value : null
}

function parsePresetShape(prst: string | undefined): DocsShapeType {
  switch (prst) {
    case "ellipse":
    case "oval":
      return "ellipse"
    case "line":
    case "straightConnector1":
      return "line"
    case "rightArrow":
    case "leftArrow":
    case "bentArrow":
      return "arrow"
    default:
      return "rect"
  }
}

function parseDrawingXml(
  block: string,
  archive: DocxArchive,
  relsPath: string
): DocxBodyDrawing | null {
  const extent = block.match(/<wp:extent\b[^>]*\/?>/i)?.[0]
  const cx = extent ? readIntAttr(extent, "cx") : null
  const cy = extent ? readIntAttr(extent, "cy") : null
  const width = cx != null ? Math.max(24, emuToPx(cx)) : 240
  const height = cy != null ? Math.max(24, emuToPx(cy)) : 160

  let x = 0
  let y = 0
  const posH = block.match(/<wp:positionH\b[^>]*>[\s\S]*?<\/wp:positionH>/i)?.[0]
  const posV = block.match(/<wp:positionV\b[^>]*>[\s\S]*?<\/wp:positionV>/i)?.[0]
  const posOffsetH = posH?.match(/<wp:posOffset>(-?\d+)<\/wp:posOffset>/i)?.[1]
  const posOffsetV = posV?.match(/<wp:posOffset>(-?\d+)<\/wp:posOffset>/i)?.[1]
  if (posOffsetH) x = emuToPx(Number(posOffsetH))
  if (posOffsetV) y = emuToPx(Number(posOffsetV))

  const rot = block.match(/\brot="(-?\d+)"/i)?.[1]
  const rotationDeg = rot ? Math.round(Number(rot) / 60000) : 0

  const behindDoc = /<wp:anchor\b[^>]*\bbehindDoc="1"/i.test(block)
  const inline = /<wp:inline\b/i.test(block)
  const wrap = inline ? "inline" : behindDoc ? "behind" : "square"
  const placement = inline ? "inline" : "absolute"

  const blipEmbed = block.match(/<a:blip\b[^>]*\bembed="([^"]+)"/i)?.[1]
  if (blipEmbed) {
    const src = resolveDocxMediaDataUrl(archive, relsPath, blipEmbed)
    if (src) {
      return {
        graphicType: "image",
        src,
        width,
        height,
        x,
        y,
        rotationDeg,
        wrap,
        placement,
      }
    }
  }

  const prstGeom = block.match(/<a:prstGeom\b[^>]*\bprst="([^"]+)"/i)?.[1]
  const solidFill = block.match(/<a:solidFill\b[^>]*>[\s\S]*?<\/a:solidFill>/i)?.[0]
  const gradFill = block.match(/<a:gradFill\b[^>]*>[\s\S]*?<\/a:gradFill>/i)?.[0]
  const ln = block.match(/<a:ln\b[^>]*>[\s\S]*?<\/a:ln>/i)?.[0] ?? block.match(/<a:ln\b[^>]*\/?>/i)?.[0]

  if (gradFill) {
    const stop1 = gradFill.match(/<a:srgbClr\b[^>]*\bval="([^"]+)"/i)?.[1]
    const stop2 = [...gradFill.matchAll(/<a:srgbClr\b[^>]*\bval="([^"]+)"/gi)][1]?.[1]
    const color1 = stop1 ? `#${stop1}` : "#4285f4"
    const color2 = stop2 ? `#${stop2}` : "#34a853"
    const angle = readIntAttr(gradFill, "ang") ?? 1800000
    const gradientAngle = Math.round(angle / 60000)
    return {
      graphicType: "gradient",
      gradientCss: buildGradientCss(gradientAngle, color1, color2),
      gradientAngle,
      gradientColor1: color1,
      gradientColor2: color2,
      width,
      height,
      x,
      y,
      rotationDeg,
      wrap,
      placement,
    }
  }

  if (prstGeom || solidFill) {
    const srgb = solidFill?.match(/<a:srgbClr\b[^>]*\bval="([^"]+)"/i)?.[1]
    const fill = srgb ? `#${srgb}` : "#4285f4"
    const strokeClr = ln?.match(/<a:srgbClr\b[^>]*\bval="([^"]+)"/i)?.[1]
    const stroke = strokeClr ? `#${strokeClr}` : "#1a73e8"
    const strokeWidth = ln ? Math.max(1, emuToPx(readIntAttr(ln, "w") ?? 12700)) : 2
    return {
      graphicType: "shape",
      shapeType: parsePresetShape(prstGeom),
      fill,
      stroke,
      strokeWidth,
      width,
      height,
      x,
      y,
      rotationDeg,
      wrap,
      placement,
    }
  }

  return null
}

function parseVmlShape(
  shapeXml: string,
  archive: DocxArchive,
  relsPath: string
): DocxBodyDrawing | null {
  const style = shapeXml.match(/\bstyle="([^"]+)"/i)?.[1] ?? ""
  const widthMatch = style.match(/\bwidth:\s*([\d.]+)pt/i)
  const heightMatch = style.match(/\bheight:\s*([\d.]+)pt/i)
  const width = widthMatch ? Math.round(Number(widthMatch[1]) * 96 / 72) : 240
  const height = heightMatch ? Math.round(Number(heightMatch[1]) * 96 / 72) : 160

  const leftMatch = style.match(/\bleft:\s*([\d.]+)pt/i)
  const topMatch = style.match(/\btop:\s*([\d.]+)pt/i)
  const x = leftMatch ? Math.round(Number(leftMatch[1]) * 96 / 72) : 0
  const y = topMatch ? Math.round(Number(topMatch[1]) * 96 / 72) : 0

  const fill = parseVmlColorForDrawing(shapeXml.match(/\bfillcolor="([^"]+)"/i)?.[1])
  const stroke = parseVmlColorForDrawing(shapeXml.match(/\bstrokecolor="([^"]+)"/i)?.[1])
  const rotationDeg = parseRotationDegFromStyle(style)

  const imagedata = shapeXml.match(/<v:imagedata\b[^>]*\br:id="([^"]+)"/i)?.[1]
  if (imagedata) {
    const src = resolveDocxMediaDataUrl(archive, relsPath, imagedata)
    if (src) {
      return {
        graphicType: "image",
        src,
        width,
        height,
        x,
        y,
        rotationDeg,
        wrap: "square",
        placement: "absolute",
      }
    }
  }

  const type = shapeXml.match(/\btype="[^"]*#([^"]+)"/i)?.[1]?.toLowerCase()
  let shapeType: DocsShapeType = "rect"
  if (type?.includes("oval") || type?.includes("ellipse")) shapeType = "ellipse"
  else if (type?.includes("line")) shapeType = "line"

  if (fill || stroke) {
    return {
      graphicType: "shape",
      shapeType,
      fill: fill ?? "#4285f4",
      stroke: stroke ?? "#1a73e8",
      strokeWidth: 2,
      width,
      height,
      x,
      y,
      rotationDeg,
      wrap: "square",
      placement: "absolute",
    }
  }

  return null
}

export function extractBodyDrawingsFromDocx(archive: DocxArchive): DocxBodyDrawing[] {
  const documentXml = decodeXml(archive["word/document.xml"])
  if (!documentXml) return []

  const relsPath = "word/_rels/document.xml.rels"
  const drawings: DocxBodyDrawing[] = []

  for (const match of documentXml.matchAll(/<w:drawing\b[^>]*>[\s\S]*?<\/w:drawing>/gi)) {
    const parsed = parseDrawingXml(match[0], archive, relsPath)
    if (parsed?.graphicType !== "image") {
      if (parsed) drawings.push(parsed)
    }
  }

  for (const match of documentXml.matchAll(/<v:shape\b[^>]*>[\s\S]*?<\/v:shape>/gi)) {
    const parsed = parseVmlShape(match[0], archive, relsPath)
    if (parsed) drawings.push(parsed)
  }

  return drawings
}

function drawingToGraphicNode(drawing: DocxBodyDrawing): TipTapJSON {
  return {
    type: "docsGraphic",
    attrs: {
      ...DOCS_GRAPHIC_DEFAULTS,
      graphicType: drawing.graphicType,
      src: drawing.src ?? null,
      shapeType: drawing.shapeType ?? "rect",
      fill: drawing.fill ?? DOCS_GRAPHIC_DEFAULTS.fill,
      stroke: drawing.stroke ?? DOCS_GRAPHIC_DEFAULTS.stroke,
      strokeWidth: drawing.strokeWidth ?? 2,
      gradientCss: drawing.gradientCss ?? "",
      gradientAngle: drawing.gradientAngle ?? 180,
      gradientColor1: drawing.gradientColor1 ?? DOCS_GRAPHIC_DEFAULTS.gradientColor1,
      gradientColor2: drawing.gradientColor2 ?? DOCS_GRAPHIC_DEFAULTS.gradientColor2,
      width: drawing.width,
      height: drawing.height,
      x: drawing.x,
      y: drawing.y,
      rotationDeg: drawing.rotationDeg,
      wrap: drawing.wrap,
      placement: drawing.placement,
      floatSide: "left",
    },
  }
}

function countGraphicImages(content: TipTapJSON): number {
  let count = 0
  const walk = (node: TipTapJSON) => {
    if (
      node.type === "image" ||
      (node.type === "docsGraphic" &&
        (node.attrs as { graphicType?: string })?.graphicType === "image")
    ) {
      count++
    }
    if (Array.isArray(node.content)) {
      node.content.forEach((child) => {
        if (child && typeof child === "object") walk(child as TipTapJSON)
      })
    }
  }
  walk(content)
  return count
}

/** Insert shape/gradient drawings not already represented as image nodes. */
export async function enrichContentFromDocxDrawings(
  buffer: ArrayBuffer,
  content: TipTapJSON
): Promise<TipTapJSON> {
  try {
    const { unzipSync } = await import("fflate")
    const archive = unzipSync(new Uint8Array(buffer)) as DocxArchive
    const drawings = extractBodyDrawingsFromDocx(archive)
    const nonImageDrawings = drawings.filter((d) => d.graphicType !== "image")
    if (nonImageDrawings.length === 0) return content

    const existingImages = countGraphicImages(content)
    const toInsert = nonImageDrawings.slice(Math.max(0, existingImages))

    if (toInsert.length === 0) return content

    const contentArray = Array.isArray(content.content) ? [...content.content] : []
    for (const drawing of toInsert) {
      contentArray.push(drawingToGraphicNode(drawing))
    }
    return { ...content, content: contentArray }
  } catch {
    return content
  }
}
