import { useEffect, useMemo, useRef, useState } from 'react'
import type { PointerEvent, WheelEvent } from 'react'
import { motion } from 'framer-motion'
import { ChevronLeft, ChevronRight, Clipboard, Download, RotateCcw, X, ZoomIn } from 'lucide-react'
import { clsx } from 'clsx'

type LearningMapImage = {
  src: string
  fileName: string
  title: string
}

type Point = {
  x: number
  y: number
}

const IMAGE_DIRECTORY = '/learning-images/'
const IMAGE_EXTENSIONS = /\.(png|jpe?g|webp|avif|gif)$/i

function imageHrefToItem(href: string): LearningMapImage | null {
  const cleanHref = href.split('?')[0].split('#')[0]
  const rawFileName = cleanHref.split('/').pop()

  if (!rawFileName || rawFileName === '..' || !IMAGE_EXTENSIONS.test(rawFileName)) {
    return null
  }

  const fileName = decodeURIComponent(rawFileName)
  const title = fileName.replace(/\.[^.]+$/, '')

  return {
    src: `${IMAGE_DIRECTORY}${encodeURIComponent(fileName)}`,
    fileName,
    title,
  }
}

async function loadLearningMapImages() {
  const response = await fetch(IMAGE_DIRECTORY, { cache: 'no-store' })
  if (!response.ok) {
    throw new Error('无法读取学习图谱')
  }

  const html = await response.text()
  const documentHtml = new DOMParser().parseFromString(html, 'text/html')

  return Array.from(documentHtml.querySelectorAll<HTMLAnchorElement>('a'))
    .map((link) => imageHrefToItem(link.getAttribute('href') || ''))
    .filter((item): item is LearningMapImage => Boolean(item))
    .sort((a, b) => a.fileName.localeCompare(b.fileName, 'zh-CN', { numeric: true }))
}

function getDistance(a: Point, b: Point) {
  return Math.hypot(a.x - b.x, a.y - b.y)
}

function getMidpoint(a: Point, b: Point) {
  return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }
}

function clampScale(value: number) {
  if (!Number.isFinite(value)) return 1
  return Math.min(5, Math.max(1, value))
}

async function blobToPngBlob(blob: Blob) {
  const bitmap = await createImageBitmap(blob)
  const canvas = document.createElement('canvas')
  canvas.width = bitmap.width
  canvas.height = bitmap.height

  const context = canvas.getContext('2d')
  if (!context) throw new Error('无法读取图片')

  context.drawImage(bitmap, 0, 0)
  bitmap.close()

  return new Promise<Blob>((resolve, reject) => {
    canvas.toBlob((pngBlob) => {
      if (pngBlob) resolve(pngBlob)
      else reject(new Error('无法复制图片'))
    }, 'image/png')
  })
}

function LazyLearningMapImage({ image }: { image: LearningMapImage }) {
  const containerRef = useRef<HTMLDivElement | null>(null)
  const [shouldLoad, setShouldLoad] = useState(false)
  const [isLoaded, setIsLoaded] = useState(false)

  useEffect(() => {
    const container = containerRef.current
    if (!container || shouldLoad) return

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (!entry.isIntersecting) return
        setShouldLoad(true)
        observer.disconnect()
      },
      {
        rootMargin: '700px 0px',
        threshold: 0.01,
      }
    )

    observer.observe(container)

    return () => observer.disconnect()
  }, [shouldLoad])

  return (
    <div ref={containerRef} className="relative min-h-64 w-full overflow-hidden bg-gray-100 dark:bg-gray-900">
      {!isLoaded && (
        <div className="absolute inset-0 animate-pulse bg-gradient-to-br from-gray-100 via-gray-50 to-gray-200 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900" />
      )}
      {shouldLoad && (
        <img
          src={image.src}
          alt={image.title}
          loading="lazy"
          decoding="async"
          onLoad={() => setIsLoaded(true)}
          className={clsx(
            'w-full object-contain transition duration-300 group-hover:scale-[1.01]',
            isLoaded ? 'opacity-100' : 'opacity-0'
          )}
        />
      )}
    </div>
  )
}

export function LearningMapsPage() {
  const [activeIndex, setActiveIndex] = useState<number | null>(null)
  const [scale, setScale] = useState(1)
  const [offset, setOffset] = useState<Point>({ x: 0, y: 0 })
  const [isDragging, setIsDragging] = useState(false)
  const [learningMapImages, setLearningMapImages] = useState<LearningMapImage[]>([])
  const [isLoadingImages, setIsLoadingImages] = useState(true)
  const [message, setMessage] = useState('')
  const pointersRef = useRef(new Map<number, Point>())
  const lastDragPointRef = useRef<Point | null>(null)
  const pinchRef = useRef<{ distance: number; scale: number; midpoint: Point } | null>(null)

  const activeImage = activeIndex === null ? null : learningMapImages[activeIndex]
  const hasPrevious = activeIndex !== null && activeIndex > 0
  const hasNext = activeIndex !== null && activeIndex < learningMapImages.length - 1

  const viewerStyle = useMemo(
    () => ({ transform: `translate3d(${offset.x}px, ${offset.y}px, 0) scale(${scale})` }),
    [offset.x, offset.y, scale]
  )


  useEffect(() => {
    let isMounted = true

    loadLearningMapImages()
      .then((images) => {
        if (!isMounted) return
        setLearningMapImages(images)
      })
      .catch(() => {
        if (!isMounted) return
        setLearningMapImages([])
      })
      .finally(() => {
        if (!isMounted) return
        setIsLoadingImages(false)
      })

    return () => {
      isMounted = false
    }
  }, [])

  const showMessage = (text: string) => {
    setMessage(text)
    window.setTimeout(() => setMessage(''), 1800)
  }

  const resetView = () => {
    setScale(1)
    setOffset({ x: 0, y: 0 })
    pointersRef.current.clear()
    lastDragPointRef.current = null
    pinchRef.current = null
    setIsDragging(false)
  }

  const openViewer = (index: number) => {
    setActiveIndex(index)
    resetView()
  }

  const closeViewer = () => {
    setActiveIndex(null)
    resetView()
  }

  const goPrevious = () => {
    setActiveIndex((current) => (current === null || current <= 0 ? current : current - 1))
    resetView()
  }

  const goNext = () => {
    setActiveIndex((current) => (current === null || current >= learningMapImages.length - 1 ? current : current + 1))
    resetView()
  }

  const handleWheel = (event: WheelEvent<HTMLDivElement>) => {
    event.preventDefault()
    const delta = event.deltaY > 0 ? -0.18 : 0.18
    setScale((current) => {
      const nextScale = clampScale(Number((current + delta).toFixed(2)))
      if (nextScale === 1) setOffset({ x: 0, y: 0 })
      return nextScale
    })
  }

  const handlePointerDown = (event: PointerEvent<HTMLDivElement>) => {
    event.currentTarget.setPointerCapture(event.pointerId)
    const point = { x: event.clientX, y: event.clientY }
    pointersRef.current.set(event.pointerId, point)

    if (pointersRef.current.size === 1) {
      lastDragPointRef.current = point
      setIsDragging(true)
    }

    if (pointersRef.current.size === 2) {
      const [first, second] = Array.from(pointersRef.current.values())
      pinchRef.current = { distance: getDistance(first, second), scale, midpoint: getMidpoint(first, second) }
      setIsDragging(false)
    }
  }

  const handlePointerMove = (event: PointerEvent<HTMLDivElement>) => {
    if (!pointersRef.current.has(event.pointerId)) return

    const point = { x: event.clientX, y: event.clientY }
    pointersRef.current.set(event.pointerId, point)

    if (pointersRef.current.size === 2 && pinchRef.current) {
      const [first, second] = Array.from(pointersRef.current.values())
      const distance = getDistance(first, second)
      const midpoint = getMidpoint(first, second)
      const pinch = pinchRef.current

      if (!Number.isFinite(distance) || distance <= 0 || pinch.distance <= 0) {
        return
      }

      const nextScale = clampScale(pinch.scale * (distance / pinch.distance))
      const deltaX = midpoint.x - pinch.midpoint.x
      const deltaY = midpoint.y - pinch.midpoint.y

      setScale(nextScale)
      setOffset((current) => ({
        x: current.x + deltaX * 0.8,
        y: current.y + deltaY * 0.8,
      }))
      pinchRef.current.midpoint = midpoint
      return
    }

    if (pointersRef.current.size === 1 && lastDragPointRef.current && scale > 1) {
      const previous = lastDragPointRef.current
      setOffset((current) => ({ x: current.x + point.x - previous.x, y: current.y + point.y - previous.y }))
      lastDragPointRef.current = point
    }
  }

  const handlePointerEnd = (event: PointerEvent<HTMLDivElement>) => {
    pointersRef.current.delete(event.pointerId)
    pinchRef.current = null

    if (pointersRef.current.size === 1) {
      lastDragPointRef.current = Array.from(pointersRef.current.values())[0]
      setIsDragging(true)
    } else {
      lastDragPointRef.current = null
      setIsDragging(false)
    }

    if (scale <= 1) {
      setScale(1)
      setOffset({ x: 0, y: 0 })
    }
  }

  const downloadImage = () => {
    if (!activeImage) return
    const link = document.createElement('a')
    link.href = activeImage.src
    link.download = activeImage.fileName
    document.body.appendChild(link)
    link.click()
    link.remove()
  }

  const copyImage = async () => {
    if (!activeImage) return

    try {
      if (!navigator.clipboard || !window.ClipboardItem) {
        showMessage('可下载后保存使用')
        return
      }

      const response = await fetch(activeImage.src)
      const blob = await response.blob()
      const pngBlob = blob.type === 'image/png' ? blob : await blobToPngBlob(blob)
      await navigator.clipboard.write([new ClipboardItem({ [pngBlob.type]: pngBlob })])
      showMessage('图片已复制')
    } catch {
      showMessage('可下载后保存使用')
    }
  }

  useEffect(() => {
    if (activeIndex === null) return

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape') closeViewer()
      if (event.key === 'ArrowLeft') goPrevious()
      if (event.key === 'ArrowRight') goNext()
    }

    document.body.style.overflow = 'hidden'
    window.addEventListener('keydown', handleKeyDown)

    return () => {
      document.body.style.overflow = ''
      window.removeEventListener('keydown', handleKeyDown)
    }
  }, [activeIndex])

  return (
    <div className="mx-auto max-w-7xl space-y-5">
      <div>
        <h1 className="text-2xl font-bold text-gray-900 dark:text-white">学习图谱</h1>
        <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">用一张图看清知识结构。</p>
      </div>

      {isLoadingImages ? (
        <div className="rounded-2xl bg-white p-8 text-center text-sm text-gray-500 shadow-sm ring-1 ring-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:ring-gray-700">
          正在加载学习图谱
        </div>
      ) : learningMapImages.length > 0 ? (
        <motion.div
          initial="hidden"
          animate="visible"
          variants={{ hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.04 } } }}
          className="columns-1 gap-4 sm:columns-2 xl:columns-3"
        >
          {learningMapImages.map((image, index) => (
            <motion.button
              key={image.fileName}
              type="button"
              variants={{ hidden: { opacity: 0, y: 12 }, visible: { opacity: 1, y: 0 } }}
              onClick={() => openViewer(index)}
              className="group mb-4 block w-full break-inside-avoid overflow-hidden rounded-2xl bg-white text-left shadow-sm ring-1 ring-gray-200 transition duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:ring-indigo-200 dark:bg-gray-800 dark:ring-gray-700 dark:hover:ring-indigo-500/50"
            >
              <LazyLearningMapImage image={image} />
            </motion.button>
          ))}
        </motion.div>
      ) : (
        <div className="rounded-2xl bg-white p-8 text-center text-sm text-gray-500 shadow-sm ring-1 ring-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:ring-gray-700">
          暂无学习图谱
        </div>
      )}

      {activeImage && activeIndex !== null && (
        <div className="fixed inset-0 z-50 bg-gray-950/95 text-white">
          <div className="absolute left-0 right-0 top-0 z-10 flex items-center justify-between gap-3 px-4 py-3">
            <button
              type="button"
              onClick={closeViewer}
              className="flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur transition hover:bg-white/20"
              aria-label="关闭"
            >
              <X size={22} />
            </button>

            <div className="min-w-0 flex-1 text-center text-sm text-white/80">
              {activeIndex + 1} / {learningMapImages.length}
            </div>

            <div className="flex items-center gap-2">
              <button
                type="button"
                onClick={resetView}
                className="hidden h-10 items-center gap-2 rounded-full bg-white/10 px-3 text-sm text-white backdrop-blur transition hover:bg-white/20 sm:flex"
              >
                <RotateCcw size={17} />
                重置
              </button>
              <button
                type="button"
                onClick={copyImage}
                className="flex h-10 items-center gap-2 rounded-full bg-white/10 px-3 text-sm text-white backdrop-blur transition hover:bg-white/20"
              >
                <Clipboard size={17} />
                <span className="hidden sm:inline">复制</span>
              </button>
              <button
                type="button"
                onClick={downloadImage}
                className="flex h-10 items-center gap-2 rounded-full bg-white/10 px-3 text-sm text-white backdrop-blur transition hover:bg-white/20"
              >
                <Download size={17} />
                <span className="hidden sm:inline">下载</span>
              </button>
            </div>
          </div>

          {message && (
            <div className="absolute left-1/2 top-16 z-20 -translate-x-1/2 rounded-full bg-white px-4 py-2 text-sm font-medium text-gray-900 shadow-xl">
              {message}
            </div>
          )}

          <button
            type="button"
            onClick={goPrevious}
            disabled={!hasPrevious}
            className={clsx(
              'absolute left-3 top-1/2 z-10 hidden h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur transition hover:bg-white/20 md:flex',
              !hasPrevious && 'cursor-not-allowed opacity-30'
            )}
            aria-label="上一张"
          >
            <ChevronLeft size={30} />
          </button>

          <button
            type="button"
            onClick={goNext}
            disabled={!hasNext}
            className={clsx(
              'absolute right-3 top-1/2 z-10 hidden h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur transition hover:bg-white/20 md:flex',
              !hasNext && 'cursor-not-allowed opacity-30'
            )}
            aria-label="下一张"
          >
            <ChevronRight size={30} />
          </button>

          <div
            className={clsx(
              'flex h-full w-full items-center justify-center overflow-hidden px-3 pb-20 pt-20 touch-none',
              scale > 1 && isDragging ? 'cursor-grabbing' : scale > 1 ? 'cursor-grab' : 'cursor-zoom-in'
            )}
            onWheel={handleWheel}
            onPointerDown={handlePointerDown}
            onPointerMove={handlePointerMove}
            onPointerUp={handlePointerEnd}
            onPointerCancel={handlePointerEnd}
            onDoubleClick={() => {
              setScale((current) => (current === 1 ? 2.4 : 1))
              if (scale !== 1) setOffset({ x: 0, y: 0 })
            }}
          >
            <img
              src={activeImage.src}
              alt={activeImage.title}
              draggable={false}
              style={viewerStyle}
              className="max-h-full max-w-full select-none object-contain transition-transform duration-75"
            />
          </div>

          <div className="absolute bottom-4 left-1/2 flex -translate-x-1/2 items-center gap-2 rounded-full bg-white/10 px-3 py-2 text-xs text-white/75 backdrop-blur">
            <ZoomIn size={15} />
            <span>双指缩放 · 拖拽移动</span>
          </div>
        </div>
      )}
    </div>
  )
}
