import { type ReactNode, isValidElement, memo, useEffect, useId, useMemo, useRef, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'
import remarkGfm from 'remark-gfm'
import { PhotoProvider, PhotoView } from 'react-photo-view'
import { Image } from '@heroui/react'

interface MarkdownDetailProps {
  children: ReactNode
  /** Disable mermaid/pie rendering (fallback to normal code block). */
  disableMermaid?: boolean
  /** Disable only pie rendering (fallback to normal code block). */
  disablePie?: boolean
}

const sanitizeSchema = {
  ...defaultSchema,
  attributes: {
    ...(defaultSchema as any).attributes,
    a: [...(((defaultSchema as any).attributes?.a ?? []) as any[]), 'target', 'rel'],
  },
}

function toText(node: any): string {
  if (node == null)
    return ''
  if (typeof node === 'string')
    return node
  if (Array.isArray(node))
    return node.map(toText).join('')
  return ''
}

function normalizeMermaid(code: string): string {
  const raw = String(code ?? '')
    .replace(/\r\n/g, '\n')
    .replace(/\u00A0/g, ' ')
    .replace(/[\u2000-\u200B\u202F\u205F\u3000]/g, ' ')
    .trim()

  const lines = raw
    .split('\n')
    .map(l => l.trim().replace(/。$/g, ''))
    .filter(Boolean)

  return lines.join('\n').trim()
}

let mermaidInitPromise: Promise<any> | null = null
async function getMermaid() {
  if (!mermaidInitPromise) {
    mermaidInitPromise = (async () => {
      const mod: any = await import('mermaid')
      const mermaid = mod?.default ?? mod
      if (!mermaid)
        throw new Error('Mermaid not available')
      mermaid.initialize({ startOnLoad: false, suppressErrorRendering: true })
      return mermaid
    })()
  }
  return mermaidInitPromise
}

const mermaidSvgCache = new Map<string, string>()
const mermaidHeightCache = new Map<string, number>()

function MermaidBlock({ chart, cacheKey }: { chart: string, cacheKey: string }) {
  const reactId = useId()
  const renderId = useMemo(() => `mermaid-${reactId.replace(/:/g, '-')}`, [reactId])
  const [svg, setSvg] = useState<string>(() => mermaidSvgCache.get(cacheKey) ?? '')
  const [minHeight, setMinHeight] = useState<number>(() => mermaidHeightCache.get(cacheKey) ?? 0)
  const svgContainerRef = useRef<HTMLDivElement | null>(null)
  const lastGoodSvgRef = useRef<string>('')

  useEffect(() => {
    const cached = mermaidSvgCache.get(cacheKey)
    if (cached) {
      lastGoodSvgRef.current = cached
      setSvg(cached)
    }
    const cachedH = mermaidHeightCache.get(cacheKey)
    if (cachedH)
      setMinHeight(cachedH)
  }, [cacheKey])

  useEffect(() => {
    let cancelled = false
    const code = normalizeMermaid(chart)
    // During streaming, chart text is often incomplete. To avoid page flicker:
    // - debounce render work (avoid per-token rerenders)
    // - only update SVG on successful parse+render
    // - keep the last successful SVG visible when parse fails temporarily
    const t = window.setTimeout(() => {
      ;(async () => {
        if (!code) {
          if (!cancelled && !lastGoodSvgRef.current)
            setSvg('')
          return
        }
        try {
          const mermaid = await getMermaid()
          try {
            await mermaid.parse(code)
          }
          catch {
            // Keep previous SVG if any.
            if (!cancelled && !lastGoodSvgRef.current)
              setSvg('')
            return
          }

          const res = await mermaid.render(renderId, code)
          if (cancelled)
            return

          const outSvg = String(res?.svg || '')
          if (!outSvg || /Syntax error in text/i.test(outSvg)) {
            if (!lastGoodSvgRef.current)
              setSvg('')
            return
          }

          lastGoodSvgRef.current = outSvg
          mermaidSvgCache.set(cacheKey, outSvg)
          setSvg(outSvg)
        }
        catch {
          if (!cancelled && !lastGoodSvgRef.current)
            setSvg('')
        }
      })()
    }, 150)

    return () => {
      cancelled = true
      window.clearTimeout(t)
    }
  }, [chart, renderId])

  useEffect(() => {
    const el = svgContainerRef.current
    if (!el)
      return
    el.innerHTML = svg
    if (!svg)
      return
    // Measure height after DOM update to reduce layout shift.
    const raf = window.requestAnimationFrame(() => {
      const h = Math.ceil(el.getBoundingClientRect().height)
      if (h > 0) {
        mermaidHeightCache.set(cacheKey, h)
        setMinHeight(prev => (h > prev ? h : prev))
      }
    })
    return () => {
      window.cancelAnimationFrame(raf)
    }
  }, [svg])

  return (
    <div className="my-[8px] overflow-x-auto" style={{ minHeight: minHeight || undefined }}>
      <div ref={svgContainerRef} />
    </div>
  )
}

export const MarkdownDetail: React.FC<MarkdownDetailProps> = memo(({ children, disableMermaid, disablePie }) => {
  return (
    <ReactMarkdown
      rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema as any]]}
      remarkPlugins={[remarkGfm]}
      className="markdown-body flex flex-col text-[#27353C] text-[15px]"
      components={{
        a: ({ href, children, ...props }) => {
          const link = String(href || '').trim()
          const isHashLink = link.startsWith('#')
          const isExternal = /^https?:\/\//i.test(link) || link.startsWith('//')
          return (
            <a
              {...props}
              href={href}
              target={(link && isExternal && !isHashLink) ? '_blank' : undefined}
              rel={(link && isExternal && !isHashLink) ? 'noopener noreferrer' : undefined}
            >
              {children}
            </a>
          )
        },
        pre: ({ children, ...props }) => {
          const arr = Array.isArray(children) ? children : [children]
          const first = arr.find(Boolean)
          if (isValidElement(first)) {
            const className = String((first.props as any)?.className || '')
            const lang = className.match(/language-(\S+)/)?.[1]?.toLowerCase()
            const isMermaid = !!lang?.startsWith('mermaid')
            const isPie = lang === 'pie'
            // Keep behavior explicit: only mermaid is guarded by disableMermaid, only pie is guarded by disablePie.
            if (
              (isMermaid && !disableMermaid)
              || (isPie && !disablePie)
            ) {
              const chart = toText((first.props as any)?.children)
              const node: any = (props as any)?.node
              const startOffset = node?.position?.start?.offset
              const key = `${lang || 'mermaid'}:${String(startOffset ?? 'na')}`
              return <MermaidBlock chart={chart} cacheKey={key} />
            }
          }
          return <pre {...props}>{children}</pre>
        },
        img: ({ ...data }): JSX.Element => {
          return (
            <PhotoProvider maskOpacity={0.5} bannerVisible={false}>
              <PhotoView {...(data as any)}>
                <Image width={300} height={300} className="cursor-pointer mt-[24px] mb-[8px]" src={data.src} />
              </PhotoView>
            </PhotoProvider>
          )
        },
        // p(data): JSX.Element {
        //   return <p className="leading-[24px] break-words w-full" {...data} />
        // },
        // ul(data): JSX.Element {
        //   return <ul className="mb-[24px]" {...data} />
        // },
        // strong(data): JSX.Element {
        //   return <strong className="text-[#27353C]" {...data} />
        // },
      }}
    >
      {children as string}
    </ReactMarkdown>
  )
})
