/**
 * Détection et correction de contraste dans les iframes d’aperçu mail.
 */

const REPAIR_LIGHT_TEXT = "#202124"
const REPAIR_LIGHT_LINK = "#1a73e8"
const REPAIR_DARK_TEXT = "#e8eaed"
const REPAIR_DARK_LINK = "#8ab4f8"

const CONTRAST_REPAIR_TEXT_TAGS =
  "div,p,span,td,th,li,h1,h2,h3,h4,h5,h6,font,label,strong,b,em,i,u,center,table"

function emailPreviewContrastRepairCss(): string {
  const lightChildren = CONTRAST_REPAIR_TEXT_TAGS.split(",")
    .map((t) => `[data-ultimail-light-surface] ${t}`)
    .join(", ")
  const darkChildren = CONTRAST_REPAIR_TEXT_TAGS.split(",")
    .map((t) => `[data-ultimail-dark-surface] ${t}`)
    .join(", ")

  return `
    ${lightChildren},
    [data-ultimail-light-surface] {
      color: ${REPAIR_LIGHT_TEXT} !important;
    }
    [data-ultimail-light-surface] a,
    [data-ultimail-light-surface] a * {
      color: ${REPAIR_LIGHT_LINK} !important;
    }

    ${darkChildren},
    [data-ultimail-dark-surface] {
      color: ${REPAIR_DARK_TEXT} !important;
    }
    [data-ultimail-dark-surface] a,
    [data-ultimail-dark-surface] a * {
      color: ${REPAIR_DARK_LINK} !important;
    }

    [data-ultimail-light-surface] blockquote {
      color: #5f6368 !important;
      border-left-color: #dadce0 !important;
    }
    [data-ultimail-dark-surface] blockquote {
      color: #9aa0a6 !important;
      border-left-color: #5f6368 !important;
    }
  `
}

export const EMAIL_PREVIEW_MIN_CONTRAST_RATIO = 3

export type Rgb = readonly [number, number, number]

export type EmailPreviewContrastIssue = {
  tag: string
  contrastRatio: number
  color: string
  backgroundColor: string
  effectiveBackground: string
  sample: string
  selectorHint: string
}

export type EmailPreviewContrastReport = {
  issueCount: number
  issues: EmailPreviewContrastIssue[]
  hasExternalStyles: boolean
  scannedElements: number
}

export type DetectEmailPreviewContrastOptions = {
  minRatio?: number
  maxIssues?: number
  maxScan?: number
  /** Fallback when la chaîne de fonds est transparente (aperçu dark : blanc newsletter). */
  assumedCanvasRgb?: Rgb
  /** En dark mode, défaut fond clair pour les zones transparentes. */
  newsletterLightCanvas?: boolean
  textSelectors?: string
}

type BackgroundSample = {
  rgb: Rgb
  css: string
  source: Element
  /** Fond déduit (transparent) — ne pas en déduire light-surface. */
  isAssumedCanvas?: boolean
}

const DEFAULT_TEXT_SELECTORS =
  "p,span,td,th,div,li,a,h1,h2,h3,h4,h5,h6,font,label,strong,b,em"

const NAMED_RGB: Record<string, Rgb> = {
  white: [255, 255, 255],
  black: [0, 0, 0],
  transparent: [0, 0, 0],
}

export function parseCssColorToRgb(color: string): Rgb | null {
  const trimmed = color.trim().toLowerCase()
  if (!trimmed || trimmed === "transparent") return null

  const named = NAMED_RGB[trimmed]
  if (named) return named

  const hex = trimmed.match(/^#([0-9a-f]{3,8})$/)
  if (hex) {
    let h = hex[1]
    if (h.length === 3) h = h.split("").map((c) => c + c).join("")
    if (h.length === 6) {
      return [
        parseInt(h.slice(0, 2), 16),
        parseInt(h.slice(2, 4), 16),
        parseInt(h.slice(4, 6), 16),
      ]
    }
  }

  const rgb = trimmed.match(
    /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/
  )
  if (rgb) {
    const alpha = rgb[4] !== undefined ? Number(rgb[4]) : 1
    if (alpha < 0.08) return null
    return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])]
  }

  return null
}

export function relativeLuminance([r, g, b]: Rgb): number {
  const f = (x: number) => {
    const s = x / 255
    return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4
  }
  return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b)
}

export function contrastRatio(fg: Rgb, bg: Rgb): number {
  const L1 = relativeLuminance(fg)
  const L2 = relativeLuminance(bg)
  const lighter = Math.max(L1, L2)
  const darker = Math.min(L1, L2)
  return (lighter + 0.05) / (darker + 0.05)
}

function selectorHint(el: Element): string {
  const tag = el.tagName.toLowerCase()
  const id = el.id ? `#${el.id}` : ""
  const cls =
    typeof el.className === "string" && el.className.trim()
      ? `.${el.className.trim().split(/\s+/).slice(0, 2).join(".")}`
      : ""
  return `${tag}${id}${cls}`
}

function parseHtmlBgcolorAttribute(el: Element): Rgb | null {
  const raw = el.getAttribute("bgcolor")?.trim()
  if (!raw) return null
  const normalized = raw.startsWith("#") ? raw : `#${raw}`
  return parseCssColorToRgb(normalized) ?? parseCssColorToRgb(raw)
}

/** Gradients / fonds peints sans background-color opaque (newsletters type bunny.net). */
export function isLikelyLightPaintedBackground(backgroundImage: string): boolean {
  if (!backgroundImage || backgroundImage === "none") return false
  const s = backgroundImage.toLowerCase()
  if (s.includes("url(")) return false
  if (!s.includes("gradient")) return false
  if (
    s.includes("#000") ||
    s.includes("rgb(0,") ||
    s.includes("rgb(0 ") ||
    s.includes(", 0,") ||
    s.includes("black")
  ) {
    return false
  }
  return (
    s.includes("#fff") ||
    s.includes("white") ||
    s.includes("rgb(255") ||
    /#[ef][0-9a-f]{5}/i.test(s) ||
    s.includes("#f") ||
    s.includes("rgb(24") ||
    s.includes("rgb(23") ||
    s.includes("rgb(22") ||
    s.includes("rgb(236") ||
    s.includes("rgb(239") ||
    s.includes("rgb(245")
  )
}

function backgroundRgbFromElement(
  el: Element,
  view: Window
): { rgb: Rgb; css: string } | null {
  const cs = view.getComputedStyle(el)
  const solid = parseCssColorToRgb(cs.backgroundColor)
  if (solid) return { rgb: solid, css: cs.backgroundColor }

  const bgcolor = parseHtmlBgcolorAttribute(el)
  if (bgcolor) {
    return { rgb: bgcolor, css: el.getAttribute("bgcolor") ?? "" }
  }

  if (isLikelyLightPaintedBackground(cs.backgroundImage)) {
    return { rgb: [245, 247, 250], css: cs.backgroundImage }
  }

  return null
}

function clamp(n: number, min: number, max: number): number {
  return Math.min(Math.max(n, min), max)
}

/** Fond réellement visible sous le texte (évite de confondre avec un ancêtre bleu foncé). */
function visualBackgroundAtTextElement(
  el: Element,
  doc: Document,
  view: Window
): BackgroundSample | null {
  const rect = el.getBoundingClientRect()
  if (rect.width < 1 || rect.height < 1) return null

  const x = clamp(
    rect.left + rect.width / 2,
    0,
    Math.max(0, view.innerWidth - 1)
  )
  const y = clamp(
    rect.top + Math.min(14, rect.height * 0.2),
    0,
    Math.max(0, view.innerHeight - 1)
  )

  let node = doc.elementFromPoint(x, y)
  if (!node || node === doc.documentElement) return null

  while (node && node !== doc.body) {
    const bg = backgroundRgbFromElement(node, view)
    if (bg) return { ...bg, source: node }
    node = node.parentElement
  }

  return null
}

function ancestryBackgroundAtElement(
  el: Element,
  doc: Document,
  view: Window
): BackgroundSample | null {
  let node: Element | null = el
  while (node && node !== doc.documentElement) {
    const bg = backgroundRgbFromElement(node, view)
    if (bg) return { ...bg, source: node }
    node = node.parentElement
  }

  const bodyBg = backgroundRgbFromElement(doc.body, view)
  if (bodyBg) return { ...bodyBg, source: doc.body }

  return null
}

function defaultAssumedCanvasRgb(options: DetectEmailPreviewContrastOptions): Rgb {
  if (options.newsletterLightCanvas === true) {
    return [255, 255, 255]
  }
  return options.assumedCanvasRgb ?? [32, 33, 36]
}

function effectiveBackgroundForText(
  el: Element,
  doc: Document,
  view: Window,
  options: DetectEmailPreviewContrastOptions
): BackgroundSample {
  const visual = visualBackgroundAtTextElement(el, doc, view)
  if (visual) return visual

  const ancestry = ancestryBackgroundAtElement(el, doc, view)
  if (ancestry) return ancestry

  const rgb = defaultAssumedCanvasRgb(options)
  return {
    rgb,
    css: "(assumed-canvas)",
    source: doc.body,
    isAssumedCanvas: true,
  }
}

export type ContrastRepairKind = "light-surface" | "dark-surface"

export function classifyContrastRepairKind(
  fg: Rgb,
  bg: Rgb,
  minRatio = EMAIL_PREVIEW_MIN_CONTRAST_RATIO
): ContrastRepairKind | null {
  const fgL = relativeLuminance(fg)
  const bgL = relativeLuminance(bg)
  const ratio = contrastRatio(fg, bg)

  if (bgL > 0.8 && fgL > 0.55) return "light-surface"
  if (bgL < 0.22 && fgL < 0.48) return "dark-surface"

  if (ratio < minRatio) {
    if (bgL > 0.72 && fgL > 0.5) return "light-surface"
    if (bgL < 0.28 && fgL < 0.52) return "dark-surface"
  }

  return null
}

const CONTRAST_REPAIR_STYLE_ID = "ultimail-contrast-repair"
const MAX_MARKED_SURFACES = 48

function ensureContrastRepairStylesheet(doc: Document): void {
  if (doc.getElementById(CONTRAST_REPAIR_STYLE_ID)) return
  const style = doc.createElement("style")
  style.id = CONTRAST_REPAIR_STYLE_ID
  style.setAttribute("data-ultimail-contrast-repair", "true")
  style.textContent = emailPreviewContrastRepairCss()
  doc.head.appendChild(style)
}

export type RepairEmailPreviewContrastResult = {
  lightSurfaces: number
  darkSurfaces: number
}

export type EmailPreviewContrastRepairMode = "all" | "dark-only" | "light-only"

export type RepairEmailPreviewContrastOptions = DetectEmailPreviewContrastOptions & {
  isDark?: boolean
  repairMode?: EmailPreviewContrastRepairMode
}

export function repairEmailPreviewContrast(
  doc: Document,
  options: RepairEmailPreviewContrastOptions = {}
): RepairEmailPreviewContrastResult | null {
  if (options.isDark === false) return null

  const view = doc.defaultView
  const body = doc.body
  if (!view || !body) return null

  const minRatio = options.minRatio ?? EMAIL_PREVIEW_MIN_CONTRAST_RATIO
  const maxScan = options.maxScan ?? 800
  const repairMode = options.repairMode ?? "all"
  const detectOptions: DetectEmailPreviewContrastOptions = {
    ...options,
    newsletterLightCanvas: options.newsletterLightCanvas ?? false,
    assumedCanvasRgb: options.assumedCanvasRgb ?? [32, 33, 36],
  }
  const selectors = options.textSelectors ?? DEFAULT_TEXT_SELECTORS

  const lightSurfaces = new Set<Element>()
  const darkSurfaces = new Set<Element>()
  let scannedElements = 0

  for (const el of body.querySelectorAll(selectors)) {
    if (scannedElements >= maxScan) break
    scannedElements += 1

    const text = (el.textContent ?? "").replace(/\s+/g, " ").trim()
    if (text.length < 2) continue

    const cs = view.getComputedStyle(el)
    const fg = parseCssColorToRgb(cs.color)
    if (!fg) continue

    const bg = effectiveBackgroundForText(el, doc, view, detectOptions)

    const kind = classifyContrastRepairKind(fg, bg.rgb, minRatio)
    if (!kind) continue
    if (bg.isAssumedCanvas && kind === "light-surface") continue
    if (repairMode === "dark-only" && kind === "light-surface") continue
    if (repairMode === "light-only" && kind === "dark-surface") continue

    const bucket = kind === "light-surface" ? lightSurfaces : darkSurfaces
    if (bucket.size >= MAX_MARKED_SURFACES) continue
    bucket.add(el)
  }

  if (lightSurfaces.size === 0 && darkSurfaces.size === 0) {
    return { lightSurfaces: 0, darkSurfaces: 0 }
  }

  for (const el of lightSurfaces) {
    el.setAttribute("data-ultimail-light-surface", "true")
    el.removeAttribute("data-ultimail-dark-surface")
  }
  for (const el of darkSurfaces) {
    el.setAttribute("data-ultimail-dark-surface", "true")
    el.removeAttribute("data-ultimail-light-surface")
  }

  ensureContrastRepairStylesheet(doc)

  return {
    lightSurfaces: lightSurfaces.size,
    darkSurfaces: darkSurfaces.size,
  }
}

export function emailPreviewHasExternalStyles(doc: Document): boolean {
  return Boolean(
    doc.querySelector('link[rel~="stylesheet" i]') ||
      doc.querySelector("style:not([data-ultimail-wrapper])")
  )
}

export function detectEmailPreviewContrastIssues(
  doc: Document,
  options: DetectEmailPreviewContrastOptions = {}
): EmailPreviewContrastReport {
  const view = doc.defaultView
  const body = doc.body
  if (!view || !body) {
    return {
      issueCount: 0,
      issues: [],
      hasExternalStyles: false,
      scannedElements: 0,
    }
  }

  const minRatio = options.minRatio ?? EMAIL_PREVIEW_MIN_CONTRAST_RATIO
  const maxIssues = options.maxIssues ?? 20
  const maxScan = options.maxScan ?? 800
  const detectOptions: DetectEmailPreviewContrastOptions = {
    ...options,
    newsletterLightCanvas: options.newsletterLightCanvas ?? false,
    assumedCanvasRgb: options.assumedCanvasRgb ?? [32, 33, 36],
  }
  const selectors = options.textSelectors ?? DEFAULT_TEXT_SELECTORS

  const issues: EmailPreviewContrastIssue[] = []
  let scannedElements = 0

  for (const el of body.querySelectorAll(selectors)) {
    if (scannedElements >= maxScan || issues.length >= maxIssues) break
    scannedElements += 1

    const text = (el.textContent ?? "").replace(/\s+/g, " ").trim()
    if (text.length < 2) continue

    const cs = view.getComputedStyle(el)
    const fg = parseCssColorToRgb(cs.color)
    if (!fg) continue

    const bg = effectiveBackgroundForText(el, doc, view, detectOptions)
    const ratio = contrastRatio(fg, bg.rgb)
    const kind = classifyContrastRepairKind(fg, bg.rgb, minRatio)
    if (bg.isAssumedCanvas && kind === "light-surface") continue
    if (ratio >= minRatio && kind !== "light-surface") continue

    issues.push({
      tag: el.tagName,
      contrastRatio: Math.round(ratio * 100) / 100,
      color: cs.color,
      backgroundColor: cs.backgroundColor,
      effectiveBackground: bg.css,
      sample: text.slice(0, 80),
      selectorHint: selectorHint(el),
    })
  }

  return {
    issueCount: issues.length,
    hasExternalStyles: emailPreviewHasExternalStyles(doc),
    issues,
    scannedElements,
  }
}

export type EmailPreviewContrastLogContext = {
  messageId?: string
  part?: "body" | "quoted"
  blockRemoteContent: boolean
  isDark: boolean
  senderEmail?: string
}

const DEV_CONTRAST_LOG_PREFIX = "[email-preview:low-contrast]"

export function isEmailPreviewContrastDebugEnabled(): boolean {
  if (process.env.NODE_ENV === "development") return true
  return process.env.NEXT_PUBLIC_EMAIL_PREVIEW_CONTRAST_DEBUG === "1"
}

export function logEmailPreviewContrastIssues(
  context: EmailPreviewContrastLogContext,
  doc: Document,
  options?: DetectEmailPreviewContrastOptions
): EmailPreviewContrastReport | null {
  if (!isEmailPreviewContrastDebugEnabled()) return null

  const report = detectEmailPreviewContrastIssues(doc, {
    ...options,
    newsletterLightCanvas: context.isDark,
    assumedCanvasRgb:
      options?.assumedCanvasRgb ?? (context.isDark ? [255, 255, 255] : [255, 255, 255]),
  })

  if (report.issueCount === 0) return report

  console.warn(DEV_CONTRAST_LOG_PREFIX, {
    messageId: context.messageId,
    part: context.part ?? "body",
    blockRemoteContent: context.blockRemoteContent,
    isDark: context.isDark,
    senderEmail: context.senderEmail,
    hasExternalStyles: report.hasExternalStyles,
    scannedElements: report.scannedElements,
    issueCount: report.issueCount,
    issues: report.issues,
    hint:
      report.hasExternalStyles && !context.blockRemoteContent
        ? "Styles expéditeur après data-ultimail-wrapper — voir fixtures/email-preview/"
        : "Texte clair Ultimail sur fond clair expéditeur — repair runtime data-ultimail-light-surface",
  })

  return report
}
