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

export const DOCS_FONT_FAMILIES = [
  { name: "Arial", stack: "Arial, Helvetica, sans-serif" },
  { name: "Calibri", stack: "Calibri, Candara, Segoe, sans-serif" },
  { name: "Comic Sans MS", stack: '"Comic Sans MS", cursive, sans-serif' },
  { name: "Courier New", stack: '"Courier New", Courier, monospace' },
  { name: "Georgia", stack: "Georgia, serif" },
  { name: "Times New Roman", stack: '"Times New Roman", Times, serif' },
  { name: "Trebuchet MS", stack: '"Trebuchet MS", Helvetica, sans-serif' },
  { name: "Verdana", stack: "Verdana, Geneva, sans-serif" },
] as const

export type DocsFontFamilyName = (typeof DOCS_FONT_FAMILIES)[number]["name"]

export const DOCS_DEFAULT_FONT_FAMILY: DocsFontFamilyName = "Arial"

let lastToolbarFontFamily: DocsFontFamilyName = DOCS_DEFAULT_FONT_FAMILY

export type FontFamilyToolbarState =
  | { kind: "single"; name: DocsFontFamilyName }
  | { kind: "unset" }

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

export function fontFamilyNameForStack(stack: string | null | undefined): DocsFontFamilyName | null {
  if (!stack) return null
  const normalized = stack.trim().toLowerCase()
  for (const family of DOCS_FONT_FAMILIES) {
    if (
      family.stack.toLowerCase() === normalized ||
      family.name.toLowerCase() === normalized ||
      normalized.startsWith(`${family.name.toLowerCase()},`) ||
      normalized.startsWith(`"${family.name.toLowerCase()}"`)
    ) {
      return family.name
    }
  }
  return null
}

export function fontFamilyStackForName(name: string): string {
  return DOCS_FONT_FAMILIES.find((f) => f.name === name)?.stack ?? name
}

function resolveCursorFontFamilyName(editor: Editor): DocsFontFamilyName {
  const { state } = editor
  const { $from } = state.selection

  if (state.storedMarks) {
    const stored = fontFamilyNameForStack(textStyleFontFamilyStack(state.storedMarks))
    if (stored) return stored
  }

  const atCursor = fontFamilyNameForStack(textStyleFontFamilyStack($from.marks()))
  if (atCursor) return atCursor

  if ($from.parent.content.size === 0) return lastToolbarFontFamily
  return DOCS_DEFAULT_FONT_FAMILY
}

function effectiveFontFamilyNameAtPos(marks: readonly Mark[]): DocsFontFamilyName {
  return fontFamilyNameForStack(textStyleFontFamilyStack(marks)) ?? DOCS_DEFAULT_FONT_FAMILY
}

function collectEffectiveFontFamiliesInRange(
  editor: Editor,
  from: number,
  to: number
): Set<DocsFontFamilyName> {
  const names = new Set<DocsFontFamilyName>()
  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

    names.add(effectiveFontFamilyNameAtPos(node.marks))
  })

  return names
}

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

  if (empty) {
    return { kind: "single", name: resolveCursorFontFamilyName(editor) }
  }

  const names = collectEffectiveFontFamiliesInRange(editor, from, to)
  if (names.size === 0) {
    return { kind: "single", name: resolveCursorFontFamilyName(editor) }
  }
  if (names.size === 1) {
    return { kind: "single", name: [...names][0]! }
  }
  return { kind: "unset" }
}

export function applyFontFamily(editor: Editor, name: DocsFontFamilyName) {
  lastToolbarFontFamily = name
  editor.chain().focus().setFontFamily(fontFamilyStackForName(name)).run()
}
