import type { DocPageBackground, DocPageBackgroundLayers } from "./doc-page-background.ts"
import { extractDocxPageBackground, resolvePageBackgroundLayers } from "./doc-page-background.ts"
import {
  DEFAULT_PAGE_FORMAT_ID,
  getPageFormat,
  MM_TO_PX,
  PAGE_FORMATS,
  type PageFormat,
  type PageFormatId,
} from "./page-formats.ts"

export type { DocPageBackground, DocPageBackgroundLayers, DocPageFillImage, DocPageWatermark } from "./doc-page-background.ts"
export { extractDocxPageBackground, resolvePageBackgroundLayers } from "./doc-page-background.ts"

const TWIPS_PER_MM = 1440 / 25.4
const FORMAT_MATCH_TOLERANCE_MM = 2

export type DocPageMarginsMm = {
  top: number
  right: number
  bottom: number
  left: number
}

export type DocPageBorderStyle = "solid" | "dashed" | "dotted" | "double" | "none"

export type DocPageBorderSide = {
  style: DocPageBorderStyle
  widthPx: number
  color: string
}

export type DocPageBorders = {
  top: DocPageBorderSide
  right: DocPageBorderSide
  bottom: DocPageBorderSide
  left: DocPageBorderSide
  offsetFrom?: "page" | "text"
}

export type DocPageHeaderFooter = {
  content: Record<string, unknown>
  heightMm?: number
}

export type DocPageNumberSettings = {
  enabled: boolean
  placement: "header" | "footer"
  align: "left" | "center" | "right"
  startAt: number
  showOnFirstPage: boolean
}

export type DocPageSetup = {
  widthMm: number
  heightMm: number
  marginsMm: DocPageMarginsMm
  /** Distance from page top to header content (cm/mm from top). */
  headerMarginMm?: number
  /** Distance from page bottom to footer content. */
  footerMarginMm?: number
  formatId?: PageFormatId | null
  orientation?: "portrait" | "landscape"
  pageColor?: string | null
  pageBackground?: DocPageBackground | null
  borders?: DocPageBorders | null
  header?: DocPageHeaderFooter | null
  footer?: DocPageHeaderFooter | null
  headerFirstPage?: DocPageHeaderFooter | null
  footerFirstPage?: DocPageHeaderFooter | null
  headerFooterDifferentFirstPage?: boolean
  headerFooterDifferentOddEven?: boolean
  pageNumbers?: DocPageNumberSettings | null
}

export type DocPageBorderCss = {
  top?: string
  right?: string
  bottom?: string
  left?: string
}

export type DocPageLayout = {
  widthPx: number
  heightPx: number
  marginsPx: DocPageMarginsMm
  format: PageFormat
  pageColor: string
  pageBackgroundLayers?: DocPageBackgroundLayers
  sheetBorderCss?: DocPageBorderCss
  textAreaBorderCss?: DocPageBorderCss
  header?: DocPageHeaderFooter | null
  footer?: DocPageHeaderFooter | null
  headerFirstPage?: DocPageHeaderFooter | null
  footerFirstPage?: DocPageHeaderFooter | null
  headerMarginMm?: number
  footerMarginMm?: number
  headerFooterDifferentFirstPage?: boolean
  headerFooterDifferentOddEven?: boolean
  pageNumbers?: DocPageNumberSettings | null
}

export function twipsToMm(twips: number): number {
  return Math.round((twips / TWIPS_PER_MM) * 100) / 100
}

export function mmToPx(mm: number): number {
  return Math.round(mm * MM_TO_PX)
}

export function pxToMm(px: number): number {
  return Math.round((px / MM_TO_PX) * 100) / 100
}

export function defaultPageMarginsMm(): DocPageMarginsMm {
  const cm = 2
  return { top: cm * 10, right: cm * 10, bottom: cm * 10, left: cm * 10 }
}

export function mmToCm(mm: number): number {
  return Math.round((mm / 10) * 10) / 10
}

export function cmToMm(cm: number): number {
  return Math.round(cm * 10 * 100) / 100
}

export function formatPaperSizeLabel(format: PageFormat): string {
  const w = format.widthMm / 10
  const h = format.heightMm / 10
  const fmt = (n: number) => n.toLocaleString("fr-FR", { maximumFractionDigits: 1 })
  return `${format.label} (${fmt(w)} x ${fmt(h)} cm)`
}

export function normalizePageColor(color: string | null | undefined): string {
  if (!color || color.toLowerCase() === "#ffffff" || color.toLowerCase() === "white") {
    return "#ffffff"
  }
  return color
}

export function matchPageFormatId(widthMm: number, heightMm: number): PageFormatId | null {
  for (const format of PAGE_FORMATS) {
    const direct =
      Math.abs(format.widthMm - widthMm) <= FORMAT_MATCH_TOLERANCE_MM &&
      Math.abs(format.heightMm - heightMm) <= FORMAT_MATCH_TOLERANCE_MM
    const rotated =
      Math.abs(format.widthMm - heightMm) <= FORMAT_MATCH_TOLERANCE_MM &&
      Math.abs(format.heightMm - widthMm) <= FORMAT_MATCH_TOLERANCE_MM
    if (direct || rotated) return format.id
  }
  return null
}

export function resolveDocumentPageLayout(
  pageSetup: DocPageSetup | null | undefined,
  fallbackFormatId: PageFormatId = DEFAULT_PAGE_FORMAT_ID
): DocPageLayout {
  if (!pageSetup) {
    const format = getPageFormat(fallbackFormatId)
    const marginPx = mmToPx(defaultPageMarginsMm().top)
    return {
      widthPx: mmToPx(format.widthMm),
      heightPx: mmToPx(format.heightMm),
      marginsPx: { top: marginPx, right: marginPx, bottom: marginPx, left: marginPx },
      format,
      pageColor: "#ffffff",
      pageBackgroundLayers: {},
    }
  }

  const formatId = pageSetup.formatId ?? matchPageFormatId(pageSetup.widthMm, pageSetup.heightMm)
  const format = formatId ? getPageFormat(formatId) : null
  const borderLayers = pageSetup.borders ? bordersToLayoutCss(pageSetup.borders) : {}
  return {
    widthPx: mmToPx(pageSetup.widthMm),
    heightPx: mmToPx(pageSetup.heightMm),
    marginsPx: {
      top: mmToPx(pageSetup.marginsMm.top),
      right: mmToPx(pageSetup.marginsMm.right),
      bottom: mmToPx(pageSetup.marginsMm.bottom),
      left: mmToPx(pageSetup.marginsMm.left),
    },
    format: format ?? {
      id: fallbackFormatId,
      label: `${pageSetup.widthMm} × ${pageSetup.heightMm} mm`,
      widthMm: pageSetup.widthMm,
      heightMm: pageSetup.heightMm,
    },
    pageColor: normalizePageColor(pageSetup.pageColor),
    pageBackgroundLayers: resolvePageBackgroundLayers(pageSetup.pageBackground),
    header: pageSetup.header ?? null,
    footer: pageSetup.footer ?? null,
    headerFirstPage: pageSetup.headerFirstPage ?? null,
    footerFirstPage: pageSetup.footerFirstPage ?? null,
    headerMarginMm: pageSetup.headerMarginMm,
    footerMarginMm: pageSetup.footerMarginMm,
    headerFooterDifferentFirstPage: pageSetup.headerFooterDifferentFirstPage ?? false,
    headerFooterDifferentOddEven: pageSetup.headerFooterDifferentOddEven ?? false,
    pageNumbers: pageSetup.pageNumbers ?? null,
    ...borderLayers,
  }
}

export function mapWordBorderStyle(val: string): DocPageBorderStyle {
  switch (val.toLowerCase()) {
    case "nil":
    case "none":
      return "none"
    case "dotted":
      return "dotted"
    case "dashed":
    case "dotdash":
    case "dotdotdash":
      return "dashed"
    case "double":
    case "triple":
      return "double"
    default:
      return "solid"
  }
}

export function borderSzToPx(sz: number): number {
  if (sz <= 0) return 0
  return Math.max(1, Math.round((sz / 8) * (96 / 72)))
}

export function parseWordBorderColor(raw: string | undefined): string {
  if (!raw || raw.toLowerCase() === "auto") return "#000000"
  if (raw.startsWith("#")) return raw
  if (/^[0-9a-f]{6}$/i.test(raw)) return `#${raw}`
  return "#000000"
}

export function parseWordBorderSide(tagXml: string): DocPageBorderSide {
  const val = tagXml.match(/\bw:val="([^"]+)"/i)?.[1] ?? "none"
  const sz = Number.parseInt(tagXml.match(/\bw:sz="(\d+)"/i)?.[1] ?? "0", 10)
  const color = parseWordBorderColor(tagXml.match(/\bw:color="([^"]+)"/i)?.[1])
  return {
    style: mapWordBorderStyle(val),
    widthPx: borderSzToPx(Number.isFinite(sz) ? sz : 0),
    color,
  }
}

function borderSideToCss(side: DocPageBorderSide): string | undefined {
  if (side.style === "none" || side.widthPx <= 0) return undefined
  return `${side.widthPx}px ${side.style} ${side.color}`
}

export function bordersToLayoutCss(borders: DocPageBorders): {
  sheetBorderCss?: DocPageBorderCss
  textAreaBorderCss?: DocPageBorderCss
} {
  const css: DocPageBorderCss = {
    top: borderSideToCss(borders.top),
    right: borderSideToCss(borders.right),
    bottom: borderSideToCss(borders.bottom),
    left: borderSideToCss(borders.left),
  }
  const hasAny = Boolean(css.top || css.right || css.bottom || css.left)
  if (!hasAny) return {}
  if (borders.offsetFrom === "page") {
    return { sheetBorderCss: css }
  }
  return { textAreaBorderCss: css }
}

function parsePgBorders(sectionXml: string): DocPageBorders | null {
  const block = sectionXml.match(/<w:pgBorders\b[^>]*>[\s\S]*?<\/w:pgBorders>/i)?.[0]
  if (!block) return null

  const offsetFrom =
    block.match(/\bw:offsetFrom="page"/i) != null ? ("page" as const) : ("text" as const)

  const readSide = (name: "top" | "right" | "bottom" | "left"): DocPageBorderSide => {
    const match = block.match(new RegExp(`<w:${name}\\b[^>]*/?>`, "i"))
    return match ? parseWordBorderSide(match[0]) : { style: "none", widthPx: 0, color: "#000000" }
  }

  const borders: DocPageBorders = {
    top: readSide("top"),
    right: readSide("right"),
    bottom: readSide("bottom"),
    left: readSide("left"),
    offsetFrom,
  }

  const hasAny = [borders.top, borders.right, borders.bottom, borders.left].some(
    (side) => side.style !== "none" && side.widthPx > 0
  )
  return hasAny ? borders : null
}

export type PageSetupDraft = {
  formatId: PageFormatId
  orientation: "portrait" | "landscape"
  marginsCm: DocPageMarginsMm
  pageColor: string
}

export function draftFromPageSetup(
  setup: DocPageSetup | null | undefined,
  fallbackFormatId: PageFormatId
): PageSetupDraft {
  const formatId =
    setup?.formatId ??
    (setup ? matchPageFormatId(setup.widthMm, setup.heightMm) : null) ??
    fallbackFormatId
  const margins = setup?.marginsMm ?? defaultPageMarginsMm()
  return {
    formatId,
    orientation: setup?.orientation ?? "portrait",
    marginsCm: {
      top: mmToCm(margins.top),
      right: mmToCm(margins.right),
      bottom: mmToCm(margins.bottom),
      left: mmToCm(margins.left),
    },
    pageColor: normalizePageColor(setup?.pageColor),
  }
}

function clampMarginCm(value: number): number {
  if (!Number.isFinite(value)) return 2
  return Math.min(25, Math.max(0, Math.round(value * 10) / 10))
}

export function buildPageSetupFromDraft(
  draft: PageSetupDraft,
  previous: DocPageSetup | null | undefined
): DocPageSetup {
  const format = getPageFormat(draft.formatId)
  let widthMm = format.widthMm
  let heightMm = format.heightMm
  if (draft.orientation === "landscape") {
    ;[widthMm, heightMm] = [heightMm, widthMm]
  }

  const pageColor = normalizePageColor(draft.pageColor)

  return {
    widthMm,
    heightMm,
    marginsMm: {
      top: cmToMm(clampMarginCm(draft.marginsCm.top)),
      right: cmToMm(clampMarginCm(draft.marginsCm.right)),
      bottom: cmToMm(clampMarginCm(draft.marginsCm.bottom)),
      left: cmToMm(clampMarginCm(draft.marginsCm.left)),
    },
    formatId: draft.formatId,
    orientation: draft.orientation,
    pageColor: pageColor === "#ffffff" ? null : pageColor,
    pageBackground: previous?.pageBackground ?? null,
    borders: previous?.borders ?? null,
    header: previous?.header ?? null,
    footer: previous?.footer ?? null,
    headerMarginMm: previous?.headerMarginMm,
    footerMarginMm: previous?.footerMarginMm,
    headerFooterDifferentFirstPage: previous?.headerFooterDifferentFirstPage ?? false,
    headerFooterDifferentOddEven: previous?.headerFooterDifferentOddEven ?? false,
    pageNumbers: previous?.pageNumbers ?? null,
  }
}

export function buildPageSetupForFormat(
  formatId: PageFormatId,
  previous: DocPageSetup | null | undefined
): DocPageSetup {
  return buildPageSetupFromDraft({ ...draftFromPageSetup(previous, formatId), formatId }, previous)
}

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

function parseSectPr(sectionXml: string): Omit<DocPageSetup, "pageColor" | "pageBackground"> | null {
  const pgSzMatch = sectionXml.match(/<w:pgSz\b[^>]*\/?>/i)
  const pgMarMatch = sectionXml.match(/<w:pgMar\b[^>]*\/?>/i)
  if (!pgSzMatch || !pgMarMatch) return null

  const widthTwips = readTwipsAttr(pgSzMatch[0], "w")
  const heightTwips = readTwipsAttr(pgSzMatch[0], "h")
  if (widthTwips == null || heightTwips == null) return null

  let widthMm = twipsToMm(widthTwips)
  let heightMm = twipsToMm(heightTwips)
  const orient = pgSzMatch[0].match(/\bw:orient="(landscape|portrait)"/i)?.[1]?.toLowerCase()
  if (orient === "landscape" && widthMm < heightMm) {
    ;[widthMm, heightMm] = [heightMm, widthMm]
  }

  const top = readTwipsAttr(pgMarMatch[0], "top")
  const right = readTwipsAttr(pgMarMatch[0], "right")
  const bottom = readTwipsAttr(pgMarMatch[0], "bottom")
  const left = readTwipsAttr(pgMarMatch[0], "left")
  const header = readTwipsAttr(pgMarMatch[0], "header")
  const footer = readTwipsAttr(pgMarMatch[0], "footer")
  if (top == null || right == null || bottom == null || left == null) return null

  const marginsMm = {
    top: twipsToMm(top),
    right: twipsToMm(right),
    bottom: twipsToMm(bottom),
    left: twipsToMm(left),
  }

  return {
    widthMm,
    heightMm,
    marginsMm,
    headerMarginMm: header != null ? twipsToMm(header) : marginsMm.top,
    footerMarginMm: footer != null ? twipsToMm(footer) : marginsMm.bottom,
    formatId: matchPageFormatId(widthMm, heightMm),
    orientation: orient === "landscape" ? "landscape" : "portrait",
    borders: parsePgBorders(sectionXml),
  }
}

/** Read Word section properties and page background from a DOCX buffer. */
export async function extractDocxPageSetup(buffer: ArrayBuffer): Promise<DocPageSetup | null> {
  try {
    const { unzipSync } = await import("fflate")
    const archive = unzipSync(new Uint8Array(buffer)) as Record<string, Uint8Array>
    const documentXml = archive["word/document.xml"]
    if (!documentXml) return null

    const xml = new TextDecoder().decode(documentXml)
    const sections = [...xml.matchAll(/<w:sectPr\b[^>]*>[\s\S]*?<\/w:sectPr>/gi)]
    const sectionXml = sections.length > 0 ? sections[sections.length - 1]![0] : null

    let layout: Omit<DocPageSetup, "pageColor" | "pageBackground"> | null = null
    if (!sectionXml) {
      const inline = xml.match(/<w:sectPr\b[^>]*\/>/i)
      if (!inline) return null
      layout = parseSectPr(inline[0])
    } else {
      layout = parseSectPr(sectionXml)
    }
    if (!layout) return null

    const backgroundInfo = extractDocxPageBackground(archive, xml)
    return {
      ...layout,
      pageColor: backgroundInfo.pageColor ?? null,
      pageBackground: backgroundInfo.background ?? null,
    }
  } catch {
    return null
  }
}
