import type { Editor } from "@tiptap/react"
import type { Mark, ResolvedPos } from "@tiptap/pm/model"

export const DOCS_FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 72] as const

export const DOCS_DEFAULT_FONT_SIZE = 11

/** Matches .ultidrive-richtext-editor h1–h4 in richtext-editor.css (rem × 16). */
const HEADING_FONT_SIZE_PX: Record<number, number> = {
  1: 28,
  2: 22,
  3: 18,
  4: 16,
}

let lastToolbarFontSize = DOCS_DEFAULT_FONT_SIZE

export type FontSizeToolbarState =
  | { kind: "single"; size: number }
  | { kind: "unset" }

function parseFontSizePx(raw: string | null | undefined): number | null {
  if (!raw) return null
  const parsed = Number.parseInt(String(raw).replace("px", ""), 10)
  return Number.isFinite(parsed) ? parsed : null
}

function nearestFontSizeIndex(current: number): number {
  let best = 0
  let bestDistance = Number.POSITIVE_INFINITY
  DOCS_FONT_SIZES.forEach((size, index) => {
    const distance = Math.abs(size - current)
    if (distance < bestDistance) {
      bestDistance = distance
      best = index
    }
  })
  return best
}

function textStyleFontSizePx(
  marks: ReadonlyArray<{ type: { name: string }; attrs: Record<string, unknown> }>
): number | null {
  const mark = marks.find((m) => m.type.name === "textStyle")
  return parseFontSizePx(mark?.attrs.fontSize as string | undefined)
}

function headingLevelAt($pos: ResolvedPos): number | null {
  for (let depth = $pos.depth; depth > 0; depth--) {
    const node = $pos.node(depth)
    if (node.type.name === "heading") {
      return node.attrs.level as number
    }
  }
  return null
}

function headingFontSizePx(level: number): number {
  return HEADING_FONT_SIZE_PX[level] ?? DOCS_DEFAULT_FONT_SIZE
}

/** Effective size of existing text at a position (no storedMarks). */
function effectiveFontSizeAtPos($pos: ResolvedPos, marks: readonly Mark[]): number {
  const marked = textStyleFontSizePx(marks)
  if (marked != null) return marked

  const level = headingLevelAt($pos)
  if (level != null) return headingFontSizePx(level)

  return DOCS_DEFAULT_FONT_SIZE
}

function resolveCursorFontSizePx(editor: Editor): number {
  const { state } = editor
  const { $from } = state.selection

  if (state.storedMarks) {
    const stored = textStyleFontSizePx(state.storedMarks)
    if (stored != null) return stored
  }

  const atCursor = textStyleFontSizePx($from.marks())
  if (atCursor != null) return atCursor

  const level = headingLevelAt($from)
  if (level != null) return headingFontSizePx(level)

  // Empty block: show last toolbar pick (typing size). Existing body text → default.
  if ($from.parent.content.size === 0) return lastToolbarFontSize
  return DOCS_DEFAULT_FONT_SIZE
}

function collectEffectiveSizesInRange(editor: Editor, from: number, to: number): Set<number> {
  const sizes = new Set<number>()
  const { state } = editor

  state.doc.nodesBetween(from, to, (node, pos) => {
    if (!node.isText || !node.text) return
    const nodeStart = pos
    const nodeEnd = pos + node.text.length
    const start = Math.max(from, nodeStart)
    const end = Math.min(to, nodeEnd)
    if (start >= end) return

    sizes.add(effectiveFontSizeAtPos(state.doc.resolve(start), node.marks))
  })

  return sizes
}

export function readFontSizeToolbarState(editor: Editor): FontSizeToolbarState {
  const { from, to, empty } = editor.state.selection

  if (empty) {
    return { kind: "single", size: resolveCursorFontSizePx(editor) }
  }

  const sizes = collectEffectiveSizesInRange(editor, from, to)
  if (sizes.size === 0) {
    return { kind: "single", size: resolveCursorFontSizePx(editor) }
  }
  if (sizes.size === 1) {
    return { kind: "single", size: [...sizes][0]! }
  }
  return { kind: "unset" }
}

export function readFontSizePxForStep(editor: Editor): number {
  const state = readFontSizeToolbarState(editor)
  return state.kind === "single" ? state.size : DOCS_DEFAULT_FONT_SIZE
}

export function applyFontSizePx(editor: Editor, size: number) {
  lastToolbarFontSize = size
  editor.chain().focus().setFontSize(`${size}px`).run()
}

export function stepFontSizePx(editor: Editor, direction: -1 | 1) {
  const idx = nearestFontSizeIndex(readFontSizePxForStep(editor))
  const nextIdx = Math.min(
    DOCS_FONT_SIZES.length - 1,
    Math.max(0, idx + direction)
  )
  applyFontSizePx(editor, DOCS_FONT_SIZES[nextIdx]!)
}

export function canStepFontSizePx(editor: Editor, direction: -1 | 1): boolean {
  const idx = nearestFontSizeIndex(readFontSizePxForStep(editor))
  if (direction < 0) return idx > 0
  return idx < DOCS_FONT_SIZES.length - 1
}

/** @deprecated Use readFontSizeToolbarState */
export function readFontSizePx(editor: Editor): number {
  const state = readFontSizeToolbarState(editor)
  return state.kind === "single" ? state.size : DOCS_DEFAULT_FONT_SIZE
}
