import type { CSSProperties } from "react"
import { mmToPx } from "./doc-page-setup.ts"
import { pageTopPx, usesPageLayer } from "./docs-graphic-position.ts"
import {
  type DocsGraphicAttrs,
  parseGraphicAttrs,
} from "./docs-graphic-types.ts"

export type DocsGraphicLayoutStyle = {
  wrapper: CSSProperties
  inner: CSSProperties
  content: CSSProperties
  behindText: boolean
  inFrontText: boolean
  usePageLayer: boolean
}

export type DocsGraphicLayoutOptions = {
  pageHeight?: number
}

export function computeGraphicLayoutStyle(
  raw: Record<string, unknown> | DocsGraphicAttrs,
  options: DocsGraphicLayoutOptions = {}
): DocsGraphicLayoutStyle {
  const attrs = "graphicType" in raw && typeof raw.graphicType === "string"
    ? (raw as DocsGraphicAttrs)
    : parseGraphicAttrs(raw)

  const width = attrs.width
  const height = attrs.height
  const rotation = attrs.rotationDeg

  const behindText = attrs.wrap === "behind"
  const inFrontText = attrs.wrap === "in-front"
  const onPageLayer = usesPageLayer(attrs)
  const isAbsolute = onPageLayer || attrs.placement === "absolute"

  const wrapMarginPx = mmToPx(attrs.wrapMarginMm)
  const blockMarginPx = Math.max(4, Math.round(wrapMarginPx / 2))

  const wrapper: CSSProperties = {}
  const inner: CSSProperties = {
    width,
    height,
    transform: rotation ? `rotate(${rotation}deg)` : undefined,
    transformOrigin: "center center",
    position: isAbsolute ? "absolute" : "relative",
    zIndex: behindText ? 1 : inFrontText ? 2 : attrs.zIndex > 0 ? attrs.zIndex : undefined,
    pointerEvents: "auto",
    opacity: attrs.opacity < 1 ? attrs.opacity : undefined,
    boxShadow: attrs.shadow || undefined,
  }

  if (onPageLayer) {
    if (options.pageHeight != null && options.pageHeight > 0) {
      inner.left = attrs.pageX
      inner.top = pageTopPx(attrs.pageIndex, options.pageHeight) + attrs.pageY
    } else {
      inner.left = attrs.pageX
      inner.top = attrs.pageY
    }
  } else if (isAbsolute) {
    // Legacy anchored-absolute coords (kept for raw/unmigrated attrs).
    inner.left = attrs.x
    inner.top = attrs.y
  } else {
    // In-flow: layout (float, margins, display) must live on the wrapper so
    // surrounding text actually wraps around the node.
    wrapper.width = width
    wrapper.height = height
    wrapper.maxWidth = "100%"

    if (attrs.placement === "inline" || attrs.wrap === "inline") {
      wrapper.display = "inline-block"
      wrapper.verticalAlign = "baseline"
    } else if (attrs.wrap === "top-bottom") {
      wrapper.display = "block"
      wrapper.clear = "both"
      wrapper.marginBlock = `${wrapMarginPx}px`
      if (attrs.floatSide === "center") {
        wrapper.marginInline = "auto"
      }
    } else if (attrs.wrap === "square" || attrs.wrap === "tight" || attrs.wrap === "through") {
      wrapper.display = "block"
      wrapper.marginBlock = `${blockMarginPx}px`
      if (attrs.floatSide === "center") {
        wrapper.float = "none"
        wrapper.marginInline = "auto"
      } else {
        const sideMargin = attrs.wrap === "tight" ? Math.max(2, blockMarginPx / 2) : wrapMarginPx
        wrapper.float = attrs.floatSide === "right" ? "right" : "left"
        // Gap goes on the text side of the float.
        wrapper.marginInlineStart = attrs.floatSide === "right" ? `${sideMargin}px` : undefined
        wrapper.marginInlineEnd = attrs.floatSide === "right" ? undefined : `${sideMargin}px`
        if (attrs.wrap === "tight") {
          wrapper.shapeOutside = "margin-box"
        }
      }
      if (attrs.wrap === "through") {
        inner.opacity = 0.85
        inner.mixBlendMode = "multiply"
      }
    } else {
      wrapper.display = "block"
      wrapper.marginBlock = `${wrapMarginPx}px`
      if (attrs.floatSide === "center") {
        wrapper.marginInline = "auto"
      }
    }

    // Resize-from-left/top keeps the opposite edge fixed via x/y offset.
    if (attrs.x || attrs.y) {
      inner.left = attrs.x
      inner.top = attrs.y
    }
  }

  const content: CSSProperties = {
    width: "100%",
    height: "100%",
    overflow: "hidden",
    position: "relative",
  }

  return { wrapper, inner, content, behindText, inFrontText, usePageLayer: onPageLayer }
}

export const RESIZE_HANDLES = [
  "nw",
  "n",
  "ne",
  "e",
  "se",
  "s",
  "sw",
  "w",
] as const

export type ResizeHandle = (typeof RESIZE_HANDLES)[number]

export function resizeWithHandle(
  handle: ResizeHandle,
  startWidth: number,
  startHeight: number,
  dx: number,
  dy: number,
  minSize = 24,
  lockAspect = false
): { width: number; height: number; xOffset: number; yOffset: number } {
  let width = startWidth
  let height = startHeight
  let xOffset = 0
  let yOffset = 0

  if (lockAspect) {
    const aspect = startWidth / Math.max(startHeight, 1)
    if (Math.abs(dx) >= Math.abs(dy)) {
      width = startWidth + (handle.includes("w") ? -dx : dx)
      height = width / aspect
    } else {
      height = startHeight + (handle.includes("n") ? -dy : dy)
      width = height * aspect
    }
    if (handle.includes("w")) xOffset = startWidth - width
    if (handle.includes("n")) yOffset = startHeight - height
  } else {
    if (handle.includes("e")) width = startWidth + dx
    if (handle.includes("w")) {
      width = startWidth - dx
      xOffset = dx
    }
    if (handle.includes("s")) height = startHeight + dy
    if (handle.includes("n")) {
      height = startHeight - dy
      yOffset = dy
    }
  }

  width = Math.max(minSize, width)
  height = Math.max(minSize, height)

  if (handle.includes("w") && width === minSize) xOffset = startWidth - minSize
  if (handle.includes("n") && height === minSize) yOffset = startHeight - minSize

  return { width: Math.round(width), height: Math.round(height), xOffset, yOffset }
}

export const DOCS_GRAPHIC_WRAP_MARGIN_PRESETS_MM = [0, 3, 6, 12] as const
