import { createContext, useContext, useState, useCallback, useRef, forwardRef, useImperativeHandle, useEffect } from 'react'
import type { ReactNode, Ref, MutableRefObject, Dispatch, SetStateAction } from 'react'
import { Chat3Line, ArrowUpLine, ArrowDownSLine, SparklingLine } from '../../basic/icons-inline'
import {
  DEFAULT_LABELS,
  NO_PREFERENCE_VALUE,
  type UserQuestionItem,
  type UserQuestionLabels,
  type UserQuestionHandle,
} from './types'

// ===== Context Value =====

export interface UserQuestionContextValue {
  // 数据
  questions: UserQuestionItem[]
  labels: Required<UserQuestionLabels>
  // 状态
  currentQuestionIndex: number
  setCurrentQuestionIndex: (i: number) => void
  answers: Record<string, string[]>
  setAnswers: Dispatch<SetStateAction<Record<string, string[]>>>
  customInputs: Record<string, string>
  setCustomInputs: Dispatch<SetStateAction<Record<string, string>>>
  focusedOptionIndex: number
  setFocusedOptionIndex: (i: number) => void
  isSubmitting: boolean
  setIsSubmitting: (v: boolean) => void
  maxQuestionHeight: number | undefined
  // Refs
  scrollContainerRef: MutableRefObject<HTMLDivElement | null>
  questionRefs: MutableRefObject<Map<number, HTMLDivElement>>
  customInputRefs: MutableRefObject<Map<string, HTMLInputElement>>
  // Ref helpers
  setShouldScrollToQuestion: (v: boolean) => void
  // 派生状态
  currentQuestion: UserQuestionItem | undefined
  currentOptions: { label: string; description?: string }[]
  allQuestionsAnswered: boolean
  currentQuestionHasAnswer: boolean
  // 回调
  getSelectedOptionIndex: (i: number) => number
  handleOptionClick: (questionText: string, optionLabel: string, questionIndex: number) => void
  handlePrevious: () => void
  handleNext: () => void
  handleContinue: () => void
  handleSkip: () => void
  handleRecommend: () => void
  // 图标
  chat4Icon: ReactNode
  arrowUpSIcon: ReactNode
  arrowDownSIcon: ReactNode
  sparklingIcon: ReactNode
  // 配置
  hasCustomText: boolean
}

// ===== Context =====

const UserQuestionContext = createContext<UserQuestionContextValue | null>(null)
UserQuestionContext.displayName = 'UserQuestionContext'

export function useUserQuestionContext() {
  const ctx = useContext(UserQuestionContext)
  if (!ctx) throw new Error('UserQuestion compound components must be used within UserQuestion.Root')
  return ctx
}

// ===== Root Props =====

export interface UserQuestionRootProps {
  questions: UserQuestionItem[]
  resetKey?: string
  onAnswer: (answers: Record<string, string>) => void
  onSkip: () => void
  hasCustomText?: boolean
  labels?: UserQuestionLabels
  chat4Icon?: ReactNode
  arrowUpSIcon?: ReactNode
  arrowDownSIcon?: ReactNode
  sparklingIcon?: ReactNode
  children?: ReactNode
}

// ===== Provider =====

const defaultChat4Icon: ReactNode = <Chat3Line className="w-4 h-4 shrink-0 text-text-tertiary" />
const defaultArrowUpSIcon = <ArrowUpLine />
const defaultArrowDownSIcon = <ArrowDownSLine />
const defaultSparklingIcon = <SparklingLine className="w-4 h-4 mr-1" />

export const UserQuestionRootProvider = forwardRef<UserQuestionHandle, UserQuestionRootProps>(
  function UserQuestionRootProvider(
    {
      questions,
      resetKey,
      onAnswer,
      onSkip,
      hasCustomText = false,
      labels: labelsProp,
      chat4Icon = defaultChat4Icon,
      arrowUpSIcon = defaultArrowUpSIcon,
      arrowDownSIcon = defaultArrowDownSIcon,
      sparklingIcon = defaultSparklingIcon,
      children,
    }: UserQuestionRootProps,
    ref: Ref<UserQuestionHandle>
  ) {
    const labels = { ...DEFAULT_LABELS, ...labelsProp }

    // ===== State =====
    const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
    const [answers, setAnswers] = useState<Record<string, string[]>>({})
    const [customInputs, setCustomInputs] = useState<Record<string, string>>({})
    const [focusedOptionIndex, setFocusedOptionIndex] = useState(0)
    const [isSubmitting, setIsSubmitting] = useState(false)
    const [maxQuestionHeight, setMaxQuestionHeight] = useState<number | undefined>(undefined)

    // ===== Refs =====
    const prevResetKeyRef = useRef(resetKey)
    const scrollContainerRef = useRef<HTMLDivElement>(null)
    const questionRefs = useRef<Map<number, HTMLDivElement>>(new Map())
    const currentIndexRef = useRef(currentQuestionIndex)
    const isProgrammaticScrollRef = useRef(false)
    const shouldScrollToQuestionRef = useRef(false)
    const customInputRefs = useRef<Map<string, HTMLInputElement>>(new Map())
    const prevIndexRef = useRef(currentQuestionIndex)

    // ===== 派生状态 =====
    const currentQuestion = questions[currentQuestionIndex]
    const currentOptions = currentQuestion?.options || []
    const allQuestionsAnswered = questions.every(
      (q) =>
        (answers[q.question] || []).length > 0 ||
        (customInputs[q.question]?.trim() || '').length > 0
    )
    const currentQuestionHasAnswer =
      (answers[currentQuestion?.question] || []).length > 0 ||
      (customInputs[currentQuestion?.question]?.trim() || '').length > 0

    // ===== 工具函数 =====
    const getSelectedOptionIndex = useCallback(
      (questionIndex: number) => {
        const question = questions[questionIndex]
        if (!question) return 0
        const selectedLabels = answers[question.question] || []
        if (selectedLabels.length === 0) return 0
        const options = question.options || []
        const selectedIndex = options.findIndex((opt) => selectedLabels.includes(opt.label))
        return selectedIndex >= 0 ? selectedIndex : 0
      },
      [questions, answers]
    )

    // ===== Imperative Handle =====
    useImperativeHandle(
      ref,
      () => ({
        getAnswers: () => {
          const formattedAnswers: Record<string, string> = {}
          for (const question of questions) {
            const selected = answers[question.question] || []
            const customInput = customInputs[question.question]?.trim()
            const allParts: string[] = []
            if (selected.length > 0) allParts.push(...selected)
            if (customInput) allParts.push(customInput)
            if (allParts.length > 0) {
              formattedAnswers[question.question] = allParts.join(', ')
            }
          }
          return formattedAnswers
        },
      }),
      [answers, customInputs, questions]
    )

    // ===== Effects =====
    useEffect(() => {
      if (prevResetKeyRef.current !== resetKey) {
        prevResetKeyRef.current = resetKey
        questionRefs.current.clear()
        customInputRefs.current.clear()
        currentIndexRef.current = 0
        // Use requestAnimationFrame to avoid synchronous setState in effect
        requestAnimationFrame(() => {
          setIsSubmitting(false)
          setCurrentQuestionIndex(0)
          setAnswers({})
          setCustomInputs({})
          setFocusedOptionIndex(0)
        })
      }
    }, [resetKey])

    useEffect(() => {
      if (prevIndexRef.current !== currentQuestionIndex) {
        currentIndexRef.current = currentQuestionIndex
        if (shouldScrollToQuestionRef.current) {
          isProgrammaticScrollRef.current = true
          const questionEl = questionRefs.current.get(currentQuestionIndex)
          const container = scrollContainerRef.current
          if (questionEl && container) {
            const handleScrollEnd = () => {
              isProgrammaticScrollRef.current = false
              shouldScrollToQuestionRef.current = false
              container.removeEventListener('scrollend', handleScrollEnd)
            }
            container.addEventListener('scrollend', handleScrollEnd, { once: true })
            questionEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
          } else {
            isProgrammaticScrollRef.current = false
            shouldScrollToQuestionRef.current = false
          }
        }
        prevIndexRef.current = currentQuestionIndex
      }
    }, [currentQuestionIndex])

    useEffect(() => {
      const timer = setTimeout(() => {
        let maxHeight = 0
        questionRefs.current.forEach((el) => {
          const h = el.getBoundingClientRect().height
          if (h > maxHeight) maxHeight = h
        })
        if (maxHeight > 0) setMaxQuestionHeight(maxHeight)
      }, 50)
      return () => clearTimeout(timer)
    }, [questions, resetKey])

    useEffect(() => {
      const container = scrollContainerRef.current
      if (!container) return
      const observer = new IntersectionObserver(
        (entries) => {
          if (isProgrammaticScrollRef.current) return
          let maxRatio = 0
          let visibleIndex = currentIndexRef.current
          entries.forEach((entry) => {
            if (entry.isIntersecting && entry.intersectionRatio > maxRatio) {
              maxRatio = entry.intersectionRatio
              questionRefs.current.forEach((el, index) => {
                if (el === entry.target) visibleIndex = index
              })
            }
          })
          if (visibleIndex !== currentIndexRef.current && maxRatio > 0.5) {
            currentIndexRef.current = visibleIndex
            setCurrentQuestionIndex(visibleIndex)
            setFocusedOptionIndex(getSelectedOptionIndex(visibleIndex))
          }
        },
        { root: container, threshold: [0, 0.25, 0.5, 0.75, 1] }
      )
      questionRefs.current.forEach((el) => {
        if (el?.isConnected) observer.observe(el)
      })
      return () => observer.disconnect()
    }, [questions, getSelectedOptionIndex])

    // ===== Handlers =====
    const handleOptionClick = useCallback(
      (questionText: string, optionLabel: string, questionIndex: number) => {
        const question = questions[questionIndex]
        const allowMultiple = question?.multiSelect ?? false
        const isLastQuestion = questionIndex === questions.length - 1
        const currentAnswers = answers[questionText] || []
        const isDeselecting = currentAnswers.includes(optionLabel)

        setAnswers((prev) => {
          const cur = prev[questionText] || []
          if (allowMultiple) {
            if (cur.includes(optionLabel)) {
              return { ...prev, [questionText]: cur.filter((l) => l !== optionLabel) }
            }
            return { ...prev, [questionText]: [...cur, optionLabel] }
          }
          if (cur.includes(optionLabel)) return { ...prev, [questionText]: [] }
          return { ...prev, [questionText]: [optionLabel] }
        })

        if (!allowMultiple) {
          setCustomInputs((prev) => ({ ...prev, [questionText]: '' }))
        }

        const shouldAutoNext = !allowMultiple && !isLastQuestion && !isDeselecting
        if (shouldAutoNext) {
          setTimeout(() => {
            const nextIndex = questionIndex + 1
            shouldScrollToQuestionRef.current = true
            setCurrentQuestionIndex(nextIndex)
            setFocusedOptionIndex(getSelectedOptionIndex(nextIndex))
          }, 150)
        }
      },
      [questions, answers, getSelectedOptionIndex]
    )

    const handlePrevious = useCallback(() => {
      if (currentQuestionIndex > 0) {
        const prevIndex = currentQuestionIndex - 1
        shouldScrollToQuestionRef.current = true
        setCurrentQuestionIndex(prevIndex)
        setFocusedOptionIndex(getSelectedOptionIndex(prevIndex))
      }
    }, [currentQuestionIndex, getSelectedOptionIndex])

    const handleNext = useCallback(() => {
      if (currentQuestionIndex < questions.length - 1) {
        const nextIndex = currentQuestionIndex + 1
        shouldScrollToQuestionRef.current = true
        setCurrentQuestionIndex(nextIndex)
        setFocusedOptionIndex(getSelectedOptionIndex(nextIndex))
      }
    }, [currentQuestionIndex, questions.length, getSelectedOptionIndex])

    const handleContinue = useCallback(() => {
      if (isSubmitting) return
      if (allQuestionsAnswered) {
        setIsSubmitting(true)
        const formatted: Record<string, string> = {}
        for (const q of questions) {
          const selected = answers[q.question] || []
          const custom = customInputs[q.question]?.trim()
          const parts: string[] = []
          if (selected.length > 0) parts.push(...selected)
          if (custom) parts.push(custom)
          formatted[q.question] = parts.join(', ')
        }
        onAnswer(formatted)
      } else {
        const isUnanswered = (q: UserQuestionItem) =>
          (answers[q.question] || []).length === 0 &&
          (customInputs[q.question]?.trim() || '').length === 0
        const len = questions.length
        let nextUnansweredIndex = -1
        for (let i = 1; i < len; i++) {
          const idx = (currentQuestionIndex + i) % len
          if (isUnanswered(questions[idx])) {
            nextUnansweredIndex = idx
            break
          }
        }
        if (nextUnansweredIndex >= 0) {
          shouldScrollToQuestionRef.current = true
          setCurrentQuestionIndex(nextUnansweredIndex)
          setFocusedOptionIndex(getSelectedOptionIndex(nextUnansweredIndex))
        }
      }
    }, [
      isSubmitting,
      allQuestionsAnswered,
      questions,
      answers,
      customInputs,
      currentQuestionIndex,
      onAnswer,
      getSelectedOptionIndex,
    ])

    const handleSkip = useCallback(() => {
      if (isSubmitting) return
      setIsSubmitting(true)
      const finalAnswers: Record<string, string> = {}
      for (const q of questions) {
        const selected = answers[q.question] || []
        const custom = customInputs[q.question]?.trim()
        const hasAnswer = selected.length > 0 || !!custom
        if (hasAnswer) {
          const parts: string[] = []
          if (selected.length > 0) parts.push(...selected)
          if (custom) parts.push(custom)
          finalAnswers[q.question] = parts.join(', ')
        } else {
          finalAnswers[q.question] = NO_PREFERENCE_VALUE
        }
      }
      onAnswer(finalAnswers)
      onSkip()
    }, [isSubmitting, questions, answers, customInputs, onAnswer, onSkip])

    const handleRecommend = useCallback(() => {
      if (isSubmitting) return
      const recommended: Record<string, string[]> = {}
      for (const q of questions) {
        const first = q.options?.[0]
        if (first) recommended[q.question] = [first.label]
      }
      setAnswers(recommended)
      setCustomInputs({})
      shouldScrollToQuestionRef.current = true
      setCurrentQuestionIndex(questions.length - 1)
      setFocusedOptionIndex(0)
      setTimeout(() => {
        if (scrollContainerRef.current) {
          scrollContainerRef.current.scrollTo({
            top: scrollContainerRef.current.scrollHeight,
            behavior: 'smooth',
          })
        }
      }, 50)
    }, [isSubmitting, questions])

    // ===== Ref helpers =====
    const setShouldScrollToQuestion = useCallback((v: boolean) => {
      shouldScrollToQuestionRef.current = v
    }, [])

    // ===== Context Value =====
    const ctxValue: UserQuestionContextValue = {
      questions,
      labels,
      currentQuestionIndex,
      setCurrentQuestionIndex,
      answers,
      setAnswers,
      customInputs,
      setCustomInputs,
      focusedOptionIndex,
      setFocusedOptionIndex,
      isSubmitting,
      setIsSubmitting,
      maxQuestionHeight,
      scrollContainerRef,
      questionRefs,
      setShouldScrollToQuestion,
      customInputRefs,
      currentQuestion,
      currentOptions,
      allQuestionsAnswered,
      currentQuestionHasAnswer,
      getSelectedOptionIndex,
      handleOptionClick,
      handlePrevious,
      handleNext,
      handleContinue,
      handleSkip,
      handleRecommend,
      chat4Icon,
      arrowUpSIcon,
      arrowDownSIcon,
      sparklingIcon,
      hasCustomText,
    }

    if (questions.length === 0) return null

    return (
      <UserQuestionContext.Provider value={ctxValue}>
        {children}
      </UserQuestionContext.Provider>
    )
  }
)

UserQuestionRootProvider.displayName = 'UserQuestionRoot'
