import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { AnimatePresence, motion } from 'framer-motion'
import clsx from 'clsx'
import { processPurposePracticeItems } from '@/data/process-purpose-practice'
import { normalizeAnswer, announceToScreenReader } from '@/utils/practice'

type CharStatus = 'pending' | 'correct' | 'error'

type PracticeProgress = {
  order: string[]
  currentIndex: number
  completedIds: string[]
  currentInput: string[]
}

const STORAGE_KEY = 'process-purpose-practice-progress'
const ANSWER_VISIBLE_DURATION = 3000
const NEXT_QUESTION_DELAY = 650

function shuffleItems<T>(items: T[]): T[] {
  const result = [...items]
  for (let i = result.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1))
    ;[result[i], result[j]] = [result[j], result[i]]
  }
  return result
}

function createFreshProgress(): PracticeProgress {
  return {
    order: shuffleItems(processPurposePracticeItems.map((item) => item.id)),
    currentIndex: 0,
    completedIds: [],
    currentInput: [],
  }
}

function normalizeProcessName(value: string): string {
  return normalizeAnswer(value).replace(/wbs/g, 'WBS'.toLowerCase())
}

function getStoredProgress(): PracticeProgress {
  try {
    const saved = localStorage.getItem(STORAGE_KEY)
    if (!saved) return createFreshProgress()

    const parsed = JSON.parse(saved) as Partial<PracticeProgress>
    const validIds = new Set(processPurposePracticeItems.map((item) => item.id))
    const order = Array.isArray(parsed.order)
      ? parsed.order.filter((id): id is string => validIds.has(String(id)))
      : []
    const missingIds = processPurposePracticeItems
      .map((item) => item.id)
      .filter((id) => !order.includes(id))
    const completedIds = Array.isArray(parsed.completedIds)
      ? parsed.completedIds.filter((id): id is string => validIds.has(String(id)))
      : []

    const mergedOrder = [...order, ...shuffleItems(missingIds)]
    if (mergedOrder.length === 0) return createFreshProgress()

    return {
      order: mergedOrder,
      currentIndex: Math.min(
        Math.max(Number(parsed.currentIndex) || 0, 0),
        mergedOrder.length - 1
      ),
      completedIds,
      currentInput: Array.isArray(parsed.currentInput)
        ? parsed.currentInput.map((char) => String(char || ''))
        : [],
    }
  } catch (error) {
    console.error('加载子过程主要作用练习进度失败:', error)
    return createFreshProgress()
  }
}

export default function ProcessPurposePracticePage() {
  const [progress, setProgress] = useState<PracticeProgress>(() =>
    getStoredProgress()
  )
  const [userInput, setUserInput] = useState<string[]>([])
  const [charStatuses, setCharStatuses] = useState<CharStatus[]>([])
  const [isComposing, setIsComposing] = useState(false)
  const [inputLocked, setInputLocked] = useState(false)
  const [lastErrorTimestamp, setLastErrorTimestamp] = useState<number | null>(
    null
  )
  const [showAnswer, setShowAnswer] = useState(false)
  const [correctFeedback, setCorrectFeedback] = useState(false)
  const isComposingRef = useRef(false)
  const latestInputRef = useRef<string[]>([])

  const itemMap = useMemo(
    () => new Map(processPurposePracticeItems.map((item) => [item.id, item])),
    []
  )
  const currentItem = itemMap.get(progress.order[progress.currentIndex])
  const totalCount = progress.order.length
  const completedCount = progress.completedIds.length
  const isFinished = completedCount >= totalCount

  const resetInputForItem = useCallback((answer: string, input?: string[]) => {
    const nextInput = new Array(answer.length).fill('')
    if (input) {
      input.slice(0, answer.length).forEach((char, index) => {
        nextInput[index] = char
      })
    }
    latestInputRef.current = nextInput
    setUserInput(nextInput)
    setCharStatuses(new Array(answer.length).fill('pending'))
    setLastErrorTimestamp(null)
  }, [])

  useEffect(() => {
    if (!currentItem) return
    resetInputForItem(currentItem.name, progress.currentInput)
  }, [currentItem?.id])

  useEffect(() => {
    latestInputRef.current = userInput
  }, [userInput])

  useEffect(() => {
    try {
      localStorage.setItem(
        STORAGE_KEY,
        JSON.stringify({
          ...progress,
          currentInput: userInput,
        })
      )
    } catch (error) {
      console.error('保存子过程主要作用练习进度失败:', error)
    }
  }, [progress, userInput])

  const focusFirstEmptyInput = useCallback(() => {
    setTimeout(() => {
      const inputs = document.querySelectorAll('.practice-input-area input')
      const firstEmptyInput = Array.from(inputs).find(
        (input) => !(input as HTMLInputElement).value
      ) as HTMLInputElement | undefined
      ;(firstEmptyInput || (inputs[0] as HTMLInputElement | undefined))?.focus()
    }, 100)
  }, [])

  const moveToNextQuestion = useCallback(() => {
    setShowAnswer(false)
    setCorrectFeedback(false)
    setInputLocked(false)
    setProgress((prev) => {
      const nextIndex = Math.min(prev.currentIndex + 1, prev.order.length - 1)
      return {
        ...prev,
        currentIndex: nextIndex,
        currentInput: [],
      }
    })
    focusFirstEmptyInput()
  }, [focusFirstEmptyInput])

  const validateInput = useCallback(
    (input: string[]) => {
      if (!currentItem || inputLocked || isFinished) return

      const answer = currentItem.name
      const normalizeChar = (char: string) => normalizeAnswer(char) || char
      const newCharStatuses = input.map((char, index) => {
        if (!char) return 'pending' as CharStatus
        return normalizeChar(char) === normalizeChar(answer[index] || '')
          ? ('correct' as CharStatus)
          : ('error' as CharStatus)
      })
      setCharStatuses(newCharStatuses)

      const isComplete = input.every(Boolean) && input.length === answer.length
      const isCorrect =
        isComplete && normalizeProcessName(input.join('')) === normalizeProcessName(answer)

      if (isCorrect) {
        setCorrectFeedback(true)
        setInputLocked(true)
        setProgress((prev) => ({
          ...prev,
          completedIds: prev.completedIds.includes(currentItem.id)
            ? prev.completedIds
            : [...prev.completedIds, currentItem.id],
          currentInput: input,
        }))
        announceToScreenReader('回答正确')
        setTimeout(() => {
          const isLastQuestion = progress.currentIndex >= progress.order.length - 1
          if (isLastQuestion) {
            setInputLocked(false)
            setCorrectFeedback(false)
          } else {
            moveToNextQuestion()
          }
        }, NEXT_QUESTION_DELAY)
      } else if (isComplete) {
        setLastErrorTimestamp(Date.now())
      }
    },
    [currentItem, inputLocked, isFinished, moveToNextQuestion, progress.currentIndex, progress.order.length]
  )

  const handleInputChange = useCallback(
    (newInput: string[]) => {
      latestInputRef.current = newInput
      setUserInput(newInput)
      if (isComposingRef.current) return
      validateInput(newInput)
    },
    [validateInput]
  )

  const handleCompositionStart = useCallback(() => {
    isComposingRef.current = true
    setIsComposing(true)
  }, [])

  const handleCompositionEnd = useCallback(
    (index: number, value: string) => {
      isComposingRef.current = false
      setIsComposing(false)
      requestAnimationFrame(() => {
        const newInput = [...latestInputRef.current]
        const chars = value.split('')
        if (chars.length > 0) {
          for (let i = 0; i < chars.length && index + i < newInput.length; i++) {
            newInput[index + i] = chars[i]
          }
        } else {
          newInput[index] = ''
        }
        handleInputChange(newInput)
      })
    },
    [handleInputChange]
  )

  const handlePaste = useCallback(
    (e: React.ClipboardEvent) => {
      e.preventDefault()
      if (!currentItem || inputLocked) return
      const nextInput = new Array(currentItem.name.length).fill('')
      e.clipboardData
        .getData('text')
        .split('')
        .slice(0, currentItem.name.length)
        .forEach((char, index) => {
          nextInput[index] = char
        })
      handleInputChange(nextInput)
    },
    [currentItem, handleInputChange, inputLocked]
  )

  const handleShowAnswer = useCallback(() => {
    if (!currentItem) return
    setShowAnswer(true)
    setInputLocked(true)
    announceToScreenReader('答案已显示')

    window.setTimeout(() => {
      setShowAnswer(false)
      setInputLocked(false)
      announceToScreenReader('答案已隐藏')
      focusFirstEmptyInput()
    }, ANSWER_VISIBLE_DURATION)
  }, [currentItem, focusFirstEmptyInput])

  const handleRestart = useCallback(() => {
    const fresh = createFreshProgress()
    localStorage.setItem(STORAGE_KEY, JSON.stringify(fresh))
    setProgress(fresh)
    setShowAnswer(false)
    setCorrectFeedback(false)
    setInputLocked(false)
    focusFirstEmptyInput()
  }, [focusFirstEmptyInput])

  if (!currentItem) return null

  return (
    <div className="min-h-screen bg-gray-50 dark:bg-gray-900">
      <div className="sticky top-0 z-20 border-b border-gray-200 bg-white/90 shadow-sm backdrop-blur dark:border-gray-700 dark:bg-gray-800/90">
        <div className="mx-auto max-w-5xl px-4 py-4">
          <div className="mb-3 flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
            <div>
              <h1 className="text-xl font-bold text-gray-900 dark:text-gray-100">
                子过程主要过程专项练习
              </h1>
              <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                根据主要作用写出对应的过程名称
              </p>
            </div>
            <div className="flex items-center gap-3">
              <div className="text-sm text-gray-600 dark:text-gray-400">
                进度：{completedCount} / {totalCount}
              </div>
              <button
                type="button"
                onClick={handleRestart}
                className="rounded-lg px-3 py-1.5 text-sm text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-gray-100"
              >
                重新练习
              </button>
            </div>
          </div>
          <div className="h-2 w-full rounded-full bg-gray-200 dark:bg-gray-700">
            <motion.div
              className="h-2 rounded-full bg-blue-500"
              initial={{ width: 0 }}
              animate={{ width: `${(completedCount / totalCount) * 100}%` }}
              transition={{ duration: 0.3 }}
            />
          </div>
        </div>
      </div>

      <main className="mx-auto flex max-w-5xl flex-col gap-6 px-4 py-8">
        <section className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-800">
          <div className="mb-4 flex items-center justify-between gap-4">
            <span className="rounded-full bg-blue-50 px-3 py-1 text-sm font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-200">
              第 {Math.min(progress.currentIndex + 1, totalCount)} / {totalCount} 题
            </span>
            <span className="text-sm text-gray-400 dark:text-gray-500">
              主要作用
            </span>
          </div>
          <p className="text-xl leading-9 text-gray-900 dark:text-gray-100">
            {currentItem.purpose}
          </p>
        </section>

        <section className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-800">
          {isFinished ? (
            <div className="flex flex-col items-center gap-4 py-10 text-center">
              <div className="text-2xl font-bold text-gray-900 dark:text-gray-100">
                已完成本轮练习
              </div>
              <p className="text-gray-600 dark:text-gray-300">
                继续巩固 49 个项目管理过程。
              </p>
              <button
                type="button"
                onClick={handleRestart}
                className="rounded-xl bg-blue-600 px-5 py-2.5 font-medium text-white transition-colors hover:bg-blue-700"
              >
                再练一轮
              </button>
            </div>
          ) : (
            <>
              <div className="mb-6 flex flex-col items-center gap-4">
                <PracticeCharInputs
                  userInput={userInput}
                  charStatuses={charStatuses}
                  isComposing={isComposing}
                  inputLocked={inputLocked}
                  lastErrorTimestamp={lastErrorTimestamp}
                  onInputChange={handleInputChange}
                  onCompositionStart={handleCompositionStart}
                  onCompositionEnd={handleCompositionEnd}
                  onPaste={handlePaste}
                />

                <AnimatePresence mode="wait">
                  {correctFeedback && (
                    <motion.div
                      key="correct"
                      initial={{ opacity: 0, y: -6 }}
                      animate={{ opacity: 1, y: 0 }}
                      exit={{ opacity: 0, y: -6 }}
                      className="rounded-full bg-green-50 px-4 py-1.5 text-sm font-medium text-green-700 dark:bg-green-900/30 dark:text-green-200"
                    >
                      回答正确
                    </motion.div>
                  )}
                </AnimatePresence>
              </div>

              <div className="flex flex-col items-center gap-3 border-t border-gray-100 pt-5 dark:border-gray-700">
                <button
                  type="button"
                  onClick={handleShowAnswer}
                  disabled={inputLocked}
                  className="rounded-xl border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:border-blue-400 hover:text-blue-600 disabled:cursor-not-allowed disabled:opacity-60 dark:border-gray-600 dark:text-gray-200 dark:hover:border-blue-400 dark:hover:text-blue-300"
                >
                  显示答案
                </button>

                <AnimatePresence>
                  {showAnswer && (
                    <motion.div
                      initial={{ opacity: 0, y: -6 }}
                      animate={{ opacity: 1, y: 0 }}
                      exit={{ opacity: 0, y: -6 }}
                      className="rounded-xl bg-gray-100 px-5 py-3 text-lg font-semibold text-gray-900 dark:bg-gray-700 dark:text-gray-100"
                    >
                      {currentItem.name}
                    </motion.div>
                  )}
                </AnimatePresence>
              </div>
            </>
          )}
        </section>
      </main>

      <div
        id="aria-live-region"
        className="sr-only"
        aria-live="polite"
        aria-atomic="true"
      />
    </div>
  )
}

interface PracticeCharInputsProps {
  userInput: string[]
  charStatuses: CharStatus[]
  isComposing: boolean
  inputLocked: boolean
  lastErrorTimestamp: number | null
  onInputChange: (newInput: string[]) => void
  onCompositionStart: () => void
  onCompositionEnd: (index: number, value: string) => void
  onPaste: (e: React.ClipboardEvent) => void
}

function PracticeCharInputs({
  userInput,
  charStatuses,
  isComposing,
  inputLocked,
  lastErrorTimestamp,
  onInputChange,
  onCompositionStart,
  onCompositionEnd,
  onPaste,
}: PracticeCharInputsProps) {
  const inputRefs = useRef<(HTMLInputElement | null)[]>([])

  useEffect(() => {
    if (isComposing || inputLocked) return
    const firstEmptyIndex = userInput.findIndex((char) => !char)
    if (firstEmptyIndex !== -1) {
      inputRefs.current[firstEmptyIndex]?.focus()
    }
  }, [userInput, isComposing, inputLocked])

  const handleCharInput = (
    index: number,
    e: React.ChangeEvent<HTMLInputElement>
  ) => {
    if (inputLocked) return

    const value = e.target.value
    const nativeIsComposing =
      typeof (e.nativeEvent as InputEvent).isComposing === 'boolean'
        ? (e.nativeEvent as InputEvent).isComposing
        : false
    const composing = isComposing || nativeIsComposing
    const newInput = [...userInput]

    if (composing) {
      newInput[index] = value
      onInputChange(newInput)
      return
    }

    if (value.length > 1) {
      value
        .split('')
        .slice(0, userInput.length - index)
        .forEach((char, offset) => {
          newInput[index + offset] = char
        })
      onInputChange(newInput)
      inputRefs.current[Math.min(index + value.length, userInput.length - 1)]?.focus()
    } else {
      newInput[index] = value
      onInputChange(newInput)
      if (value && index < userInput.length - 1) {
        inputRefs.current[index + 1]?.focus()
      }
    }
  }

  const handleKeyDown = (index: number, e: React.KeyboardEvent) => {
    if (inputLocked) return

    if (e.key === 'Backspace') {
      e.preventDefault()
      const newInput = [...userInput]
      if (newInput[index]) {
        newInput[index] = ''
        onInputChange(newInput)
      } else if (index > 0) {
        newInput[index - 1] = ''
        onInputChange(newInput)
        inputRefs.current[index - 1]?.focus()
      }
    } else if (e.key === 'ArrowLeft' && index > 0) {
      e.preventDefault()
      inputRefs.current[index - 1]?.focus()
    } else if (e.key === 'ArrowRight' && index < userInput.length - 1) {
      e.preventDefault()
      inputRefs.current[index + 1]?.focus()
    }
  }

  return (
    <div className="practice-input-area flex flex-wrap justify-center gap-3">
      {userInput.map((char, index) => {
        const status = charStatuses[index] || 'pending'
        const isError = status === 'error'
        const isCorrect = status === 'correct'

        return (
          <motion.div
            key={index}
            className="relative"
            animate={
              isError && lastErrorTimestamp
                ? {
                    x: [0, -8, 8, -8, 8, 0],
                    transition: { duration: 0.35 },
                  }
                : {}
            }
          >
            <input
              ref={(el) => (inputRefs.current[index] = el)}
              type="text"
              value={char}
              onChange={(e) => handleCharInput(index, e)}
              onKeyDown={(e) => handleKeyDown(index, e)}
              onCompositionStart={onCompositionStart}
              onCompositionEnd={(e) => onCompositionEnd(index, e.currentTarget.value)}
              onPaste={onPaste}
              disabled={inputLocked}
              placeholder="_"
              className={clsx(
                'h-12 w-10 border-b-2 bg-transparent text-center text-2xl font-medium transition-all duration-200',
                'text-gray-900 placeholder:text-gray-400 focus:outline-none dark:text-gray-100 dark:placeholder:text-gray-500',
                isComposing && 'border-gray-300 opacity-70 dark:border-gray-600',
                !isComposing && !char && 'border-gray-400 dark:border-gray-500',
                !isComposing && isCorrect && 'border-green-500',
                !isComposing && isError && 'border-red-500',
                inputLocked && 'cursor-not-allowed opacity-50'
              )}
              autoComplete="off"
              autoCorrect="off"
              autoCapitalize="off"
              spellCheck="false"
            />
          </motion.div>
        )
      })}
    </div>
  )
}
