import type { Editor } from "@tiptap/react"
import { getDocsBlockTargets } from "@/lib/drive/docs-block-targets"
import { getActiveParagraphStyleCatalog } from "@/lib/drive/docs-paragraph-style-bridge"
import {
  resolveParagraphStyleDefinition,
} from "@/lib/drive/docs-paragraph-styles"
import { readActiveParagraphStyleId } from "@/lib/drive/docs-paragraph-style-apply"
import {
  DOCS_DEFAULT_LINE_HEIGHT,
  DOCS_DEFAULT_PARAGRAPH_SPACING,
  DOCS_QUICK_PARAGRAPH_SPACE_PT,
  type DocsParagraphSpacingAttrs,
  lineHeightPresetId,
  readParagraphSpacingAttrs,
} from "@/lib/drive/docs-line-spacing"

export type DocsParagraphSpacingState = {
  lineHeight: number | null
  lineHeightPresetId: ReturnType<typeof lineHeightPresetId> | "mixed"
  spaceBeforePt: number | "mixed"
  spaceAfterPt: number | "mixed"
  hasSpaceBefore: boolean | "mixed"
  hasSpaceAfter: boolean | "mixed"
  keepWithNext: boolean | "mixed"
  keepLinesTogether: boolean | "mixed"
  preventWidowOrphan: boolean | "mixed"
  pageBreakBefore: boolean | "mixed"
}

function mergeMixed<T>(values: T[]): T | "mixed" {
  if (values.length === 0) return values[0] as T
  const first = values[0]
  return values.every((value) => value === first) ? first : "mixed"
}

function effectiveLineHeight(editor: Editor, attrs: DocsParagraphSpacingAttrs, styleId: string | null): number {
  if (attrs.lineHeight != null) return attrs.lineHeight
  if (styleId) {
    const catalog = getActiveParagraphStyleCatalog()
    const def = resolveParagraphStyleDefinition(catalog, styleId)
    if (def?.lineHeight) return def.lineHeight
  }
  return DOCS_DEFAULT_LINE_HEIGHT
}

function readBlockSpacingStates(editor: Editor): Array<{
  attrs: DocsParagraphSpacingAttrs
  effectiveLineHeight: number
}> {
  const { state } = editor
  const targets = getDocsBlockTargets(state.doc, state.selection.from, state.selection.to)
  return targets.map(({ node }) => {
    const attrs = readParagraphSpacingAttrs(node)
    const styleId = (node.attrs.styleId as string | null | undefined) ?? null
    return {
      attrs,
      effectiveLineHeight: effectiveLineHeight(editor, attrs, styleId),
    }
  })
}

export function readDocsParagraphSpacingState(editor: Editor | null): DocsParagraphSpacingState {
  if (!editor || editor.isDestroyed) {
    return {
      lineHeight: DOCS_DEFAULT_LINE_HEIGHT,
      lineHeightPresetId: "1.15",
      spaceBeforePt: 0,
      spaceAfterPt: 0,
      hasSpaceBefore: false,
      hasSpaceAfter: false,
      keepWithNext: false,
      keepLinesTogether: false,
      preventWidowOrphan: false,
      pageBreakBefore: false,
    }
  }

  const blocks = readBlockSpacingStates(editor)
  if (blocks.length === 0) {
    const styleId = readActiveParagraphStyleId(editor)
    const catalog = getActiveParagraphStyleCatalog()
    const def = resolveParagraphStyleDefinition(catalog, styleId)
    const lineHeight = def?.lineHeight ?? DOCS_DEFAULT_LINE_HEIGHT
    return {
      lineHeight,
      lineHeightPresetId: lineHeightPresetId(lineHeight),
      spaceBeforePt: 0,
      spaceAfterPt: 0,
      hasSpaceBefore: false,
      hasSpaceAfter: false,
      keepWithNext: false,
      keepLinesTogether: false,
      preventWidowOrphan: false,
      pageBreakBefore: false,
    }
  }

  const effectiveHeights = blocks.map((block) => block.effectiveLineHeight)
  const mergedHeight = mergeMixed(effectiveHeights)
  const presetIds = effectiveHeights.map((value) => lineHeightPresetId(value))
  const mergedPreset = mergeMixed(presetIds)

  const spaceBefore = blocks.map((block) => block.attrs.spaceBeforePt)
  const spaceAfter = blocks.map((block) => block.attrs.spaceAfterPt)

  return {
    lineHeight: mergedHeight === "mixed" ? null : mergedHeight,
    lineHeightPresetId: mergedPreset,
    spaceBeforePt: mergeMixed(spaceBefore),
    spaceAfterPt: mergeMixed(spaceAfter),
    hasSpaceBefore: mergeMixed(spaceBefore.map((value) => value >= DOCS_QUICK_PARAGRAPH_SPACE_PT)),
    hasSpaceAfter: mergeMixed(spaceAfter.map((value) => value >= DOCS_QUICK_PARAGRAPH_SPACE_PT)),
    keepWithNext: mergeMixed(blocks.map((block) => block.attrs.keepWithNext)),
    keepLinesTogether: mergeMixed(blocks.map((block) => block.attrs.keepLinesTogether)),
    preventWidowOrphan: mergeMixed(blocks.map((block) => block.attrs.preventWidowOrphan)),
    pageBreakBefore: mergeMixed(blocks.map((block) => block.attrs.pageBreakBefore)),
  }
}

function applyBlockSpacingPatch(
  editor: Editor,
  patch: Partial<DocsParagraphSpacingAttrs>
): boolean {
  const { state } = editor
  const targets = getDocsBlockTargets(state.doc, state.selection.from, state.selection.to)
  if (targets.length === 0) return false

  let tr = state.tr
  let changed = false

  for (const { pos, node } of targets) {
    const current = readParagraphSpacingAttrs(node)
    const next = { ...current, ...patch }
    const same =
      current.lineHeight === next.lineHeight &&
      current.spaceBeforePt === next.spaceBeforePt &&
      current.spaceAfterPt === next.spaceAfterPt &&
      current.keepWithNext === next.keepWithNext &&
      current.keepLinesTogether === next.keepLinesTogether &&
      current.preventWidowOrphan === next.preventWidowOrphan &&
      current.pageBreakBefore === next.pageBreakBefore
    if (same) continue
    tr = tr.setNodeMarkup(pos, undefined, { ...node.attrs, ...next })
    changed = true
  }

  if (!changed) return false
  editor.view.dispatch(tr.scrollIntoView())
  return true
}

export function setDocsLineHeight(editor: Editor | null, lineHeight: number): boolean {
  if (!editor || editor.isDestroyed) return false
  return applyBlockSpacingPatch(editor, { lineHeight })
}

export function setDocsCustomSpacing(
  editor: Editor | null,
  input: Pick<DocsParagraphSpacingAttrs, "lineHeight" | "spaceBeforePt" | "spaceAfterPt">
): boolean {
  if (!editor || editor.isDestroyed) return false
  return applyBlockSpacingPatch(editor, input)
}

export function toggleDocsSpaceBefore(editor: Editor | null): boolean {
  if (!editor || editor.isDestroyed) return false
  const blocks = readBlockSpacingStates(editor)
  if (blocks.length === 0) return false
  const allHaveQuick = blocks.every(
    (block) => block.attrs.spaceBeforePt >= DOCS_QUICK_PARAGRAPH_SPACE_PT
  )
  return applyBlockSpacingPatch(editor, {
    spaceBeforePt: allHaveQuick ? 0 : DOCS_QUICK_PARAGRAPH_SPACE_PT,
  })
}

export function toggleDocsSpaceAfter(editor: Editor | null): boolean {
  if (!editor || editor.isDestroyed) return false
  const blocks = readBlockSpacingStates(editor)
  if (blocks.length === 0) return false
  const allHaveQuick = blocks.every(
    (block) => block.attrs.spaceAfterPt >= DOCS_QUICK_PARAGRAPH_SPACE_PT
  )
  return applyBlockSpacingPatch(editor, {
    spaceAfterPt: allHaveQuick ? 0 : DOCS_QUICK_PARAGRAPH_SPACE_PT,
  })
}

export function toggleDocsKeepWithNext(editor: Editor | null): boolean {
  if (!editor || editor.isDestroyed) return false
  const blocks = readBlockSpacingStates(editor)
  if (blocks.length === 0) return false
  const allOn = blocks.every((block) => block.attrs.keepWithNext)
  return applyBlockSpacingPatch(editor, { keepWithNext: !allOn })
}

export function toggleDocsKeepLinesTogether(editor: Editor | null): boolean {
  if (!editor || editor.isDestroyed) return false
  const blocks = readBlockSpacingStates(editor)
  if (blocks.length === 0) return false
  const allOn = blocks.every((block) => block.attrs.keepLinesTogether)
  return applyBlockSpacingPatch(editor, { keepLinesTogether: !allOn })
}

export function toggleDocsPreventWidowOrphan(editor: Editor | null): boolean {
  if (!editor || editor.isDestroyed) return false
  const blocks = readBlockSpacingStates(editor)
  if (blocks.length === 0) return false
  const allOn = blocks.every((block) => block.attrs.preventWidowOrphan)
  return applyBlockSpacingPatch(editor, { preventWidowOrphan: !allOn })
}

export function toggleDocsPageBreakBefore(editor: Editor | null): boolean {
  if (!editor || editor.isDestroyed) return false
  const blocks = readBlockSpacingStates(editor)
  if (blocks.length === 0) return false
  const allOn = blocks.every((block) => block.attrs.pageBreakBefore)
  return applyBlockSpacingPatch(editor, { pageBreakBefore: !allOn })
}

export function readDocsCustomSpacingDraft(editor: Editor | null): DocsParagraphSpacingAttrs {
  if (!editor || editor.isDestroyed) return { ...DOCS_DEFAULT_PARAGRAPH_SPACING }
  const blocks = readBlockSpacingStates(editor)
  if (blocks.length === 0) return { ...DOCS_DEFAULT_PARAGRAPH_SPACING }
  const first = blocks[0].attrs
  return { ...DOCS_DEFAULT_PARAGRAPH_SPACING, ...first }
}
