import * as React from 'react'
import mermaid from 'mermaid'

let mermaidInited = false
let mermaidSeq = 0

function ensureMermaid(): void {
  if (mermaidInited) return
  mermaid.initialize({
    startOnLoad: false,
    theme: 'neutral',
    securityLevel: 'loose',
    fontFamily: 'inherit',
    flowchart: { useMaxWidth: true, htmlLabels: true },
    sequence: { useMaxWidth: true, wrap: true },
    er: { useMaxWidth: true },
    gantt: { useMaxWidth: true }
  })
  mermaidInited = true
}

function mermaidSourceFromWrap(wrap: HTMLElement): string {
  const enc = wrap.getAttribute('data-mermaid-src')
  if (enc) {
    try {
      return decodeURIComponent(enc)
    } catch {
      /* fall through */
    }
  }
  const pre = wrap.querySelector<HTMLElement>('pre.mermaid')
  return pre?.textContent ?? ''
}

async function renderMermaidIn(root: HTMLElement, isStale: () => boolean): Promise<void> {
  const wraps = root.querySelectorAll<HTMLElement>('.md-diagram-wrap')
  if (wraps.length === 0) return

  ensureMermaid()

  for (const wrap of wraps) {
    if (isStale()) return

    const source = mermaidSourceFromWrap(wrap).trim()
    if (!source) continue

    delete wrap.dataset.mermaidRendered

    let host = wrap.querySelector<HTMLElement>('.mermaid-render-host')
    const pre = wrap.querySelector<HTMLElement>('pre.mermaid')
    if (pre) {
      host = document.createElement('div')
      host.className = 'mermaid-render-host'
      pre.replaceWith(host)
    } else if (!host) {
      host = document.createElement('div')
      host.className = 'mermaid-render-host'
      wrap.appendChild(host)
    }

    host.innerHTML = '<div class="mermaid-placeholder" aria-hidden></div>'
    host.removeAttribute('data-mermaid-error')

    const id = `mr-mmd-${++mermaidSeq}`
    try {
      const { svg, bindFunctions } = await mermaid.render(id, source)
      if (isStale()) return
      if (!root.isConnected || !wrap.isConnected) return
      host.innerHTML = svg
      bindFunctions?.(host)
      wrap.dataset.mermaidRendered = '1'
    } catch (err) {
      if (isStale()) return
      const msg = err instanceof Error ? err.message : String(err)
      host.setAttribute('data-mermaid-error', msg)
      host.innerHTML = `<pre class="mermaid-error">${escapeHtml(msg)}\n\n${escapeHtml(source.slice(0, 2000))}</pre>`
      console.warn("[Huangzhijun's Reader] mermaid render failed", err)
    }
  }
}

function escapeHtml(s: string): string {
  return s
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
}

function isMarkdownPath(p: string): boolean {
  return /\.(md|markdown|mdown|mkd)$/i.test(p)
}

function linkHref(anchor: HTMLAnchorElement): string | null {
  return anchor.getAttribute('href') ?? anchor.getAttribute('data-md-href')
}

type Props = {
  html: string
  articleRef?: React.Ref<HTMLElement>
  baseFilePath?: string | null
  resolveRelativePath?: (baseFilePath: string, href: string) => Promise<string>
  onOpenMarkdown?: (absolutePath: string, anchorId?: string) => void | Promise<void>
  onOpenExternal?: (url: string) => void | Promise<void>
}

export function PreviewArticle({
  html,
  articleRef,
  baseFilePath,
  resolveRelativePath,
  onOpenMarkdown,
  onOpenExternal
}: Props): React.ReactElement {
  const innerRef = React.useRef<HTMLElement | null>(null)
  const renderGenRef = React.useRef(0)
  const baseFilePathRef = React.useRef(baseFilePath)
  baseFilePathRef.current = baseFilePath
  const resolveRef = React.useRef(resolveRelativePath)
  resolveRef.current = resolveRelativePath
  const openMdRef = React.useRef(onOpenMarkdown)
  openMdRef.current = onOpenMarkdown
  const openExtRef = React.useRef(onOpenExternal)
  openExtRef.current = onOpenExternal

  const setRef = React.useCallback(
    (el: HTMLElement | null) => {
      innerRef.current = el
      if (typeof articleRef === 'function') articleRef(el)
      else if (articleRef && typeof articleRef === 'object' && 'current' in articleRef) {
        ;(articleRef as React.MutableRefObject<HTMLElement | null>).current = el
      }
    },
    [articleRef]
  )

  const scrollToAnchor = React.useCallback((root: HTMLElement, anchorId: string): void => {
    const id = decodeURIComponent(anchorId)
    if (!id) return
    let el: Element | null = null
    try {
      el = root.querySelector(`#${CSS.escape(id)}`)
    } catch {
      el = root.querySelector(`#${id}`)
    }
    if (!el) el = document.getElementById(id)
    el?.scrollIntoView({ behavior: 'smooth', block: 'start' })
  }, [])

  const handleClickCapture = React.useCallback((e: React.MouseEvent<HTMLElement>) => {
    if (e.defaultPrevented || e.button !== 0) return
    if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return

    const root = innerRef.current
    if (!root) return

    const anchor = (e.target as Element | null)?.closest('a')
    if (!anchor || !root.contains(anchor)) return

    const href = linkHref(anchor)
    if (!href) return

    e.preventDefault()
    e.stopPropagation()

    void (async () => {
      if (href.startsWith('#')) {
        scrollToAnchor(root, href.slice(1))
        return
      }

      const hashIdx = href.indexOf('#')
      const pathPart = (hashIdx >= 0 ? href.slice(0, hashIdx) : href).trim()
      const anchorId = hashIdx >= 0 ? href.slice(hashIdx + 1) : ''
      if (!pathPart) {
        if (anchorId) scrollToAnchor(root, anchorId)
        return
      }

      if (/^https?:\/\//i.test(pathPart) || /^mailto:/i.test(pathPart) || /^tel:/i.test(pathPart)) {
        await openExtRef.current?.(pathPart)
        return
      }

      const base = baseFilePathRef.current
      if (!base) {
        window.alert('请先保存当前文件，再打开文内相对链接。')
        return
      }

      const resolve = resolveRef.current
      if (!resolve) return

      const resolved = await resolve(base, pathPart)
      if (!resolved) return

      if (/^https?:\/\//i.test(resolved) || /^mailto:/i.test(resolved) || /^tel:/i.test(resolved)) {
        await openExtRef.current?.(resolved)
        return
      }

      if (isMarkdownPath(resolved)) {
        await openMdRef.current?.(resolved, anchorId || undefined)
        return
      }

      window.alert(`暂不支持打开该类型链接：${resolved}`)
    })()
  }, [scrollToAnchor])

  React.useLayoutEffect(() => {
    const root = innerRef.current
    if (!root) return

    const gen = ++renderGenRef.current
    root.innerHTML = html

    void renderMermaidIn(root, () => gen !== renderGenRef.current).catch((err) => {
      console.warn("[Huangzhijun's Reader] mermaid batch failed", err)
    })

    return () => {
      renderGenRef.current += 1
    }
  }, [html])

  return (
    <article ref={setRef} className="preview" onClickCapture={handleClickCapture} />
  )
}
