import { computed, shallowRef } from 'vue'
import { defineStore } from 'pinia'
import type { QuizAttempt } from '@/types/api'
import {
  answerQuestion,
  fetchAttempt,
  fetchWrongQuestions,
  startBankAttempt,
  submitAttempt,
  updateAttemptPosition,
  updateAttemptPositionKeepalive,
} from '@/api/quiz'

export const useQuizStore = defineStore('quiz', () => {
  const attempt = shallowRef<QuizAttempt | null>(null)
  const currentIndex = shallowRef(0)
  const pendingSyncs = new Set<Promise<unknown>>()
  const lastSavedPosition = shallowRef(0)
  const positionDirty = shallowRef(false)
  const autoNextOnCorrect = shallowRef(localStorage.getItem('qq_quiz_auto_next') === '1')
  const animationDuration = shallowRef(Number(localStorage.getItem('qq_quiz_animation_duration') ?? '160'))

  const currentItem = computed(() => attempt.value?.items[currentIndex.value] ?? null)
  const answeredCount = computed(() => attempt.value?.items.filter((item) => item.answer && item.answer.length > 0).length ?? 0)

  function clear() {
    attempt.value = null
    currentIndex.value = 0
    lastSavedPosition.value = 0
    positionDirty.value = false
    pendingSyncs.clear()
  }

  async function start(bankId: number, mode: string, limit = 20) {
    const response = await startBankAttempt(bankId, { mode, limit })
    attempt.value = response.data
    currentIndex.value = 0
    lastSavedPosition.value = response.data.current_index || 0
    positionDirty.value = false
  }

  async function resume(attemptId: number) {
    const response = await fetchAttempt(attemptId)
    attempt.value = await attachWrongQuestionIds(response.data)
    currentIndex.value = response.data.current_index || 0
    lastSavedPosition.value = currentIndex.value
    positionDirty.value = false
  }

  async function answer(answer: Array<number | string>, durationSeconds = 0) {
    if (!attempt.value || !currentItem.value) return
    const currentAttempt = attempt.value
    const currentQuestion = currentItem.value.question
    const questionId = currentItem.value.question_id

    if (currentAttempt.mode !== 'paper') {
      const isCorrect = judgeAnswer(currentQuestion, answer)
      attempt.value = {
        ...currentAttempt,
        correct_count: updateCorrectCount(currentAttempt, questionId, isCorrect),
        items: currentAttempt.items.map((item) => item.question_id === questionId
          ? {
              ...item,
              answer: [...answer] as number[] | string[],
              is_correct: isCorrect,
            }
          : item),
      }

      const sync = answerQuestion(currentAttempt.id, {
        question_id: questionId,
        answer,
        duration_seconds: durationSeconds,
      }).catch(() => undefined).finally(() => {
        pendingSyncs.delete(sync)
      })
      pendingSyncs.add(sync)

      return
    }

    await answerQuestion(attempt.value.id, {
      question_id: questionId,
      answer,
      duration_seconds: durationSeconds,
    })
    await resume(attempt.value.id)
  }

  async function submit() {
    if (!attempt.value) return
    await Promise.allSettled([...pendingSyncs])
    const response = await submitAttempt(attempt.value.id)
    attempt.value = response.data
  }

  function setPosition(index = currentIndex.value) {
    if (!attempt.value) return
    const maxIndex = Math.max(0, attempt.value.items.length - 1)
    const nextIndex = Math.min(Math.max(index, 0), maxIndex)
    currentIndex.value = nextIndex
    positionDirty.value = nextIndex !== lastSavedPosition.value
  }

  async function savePosition(index = currentIndex.value, force = false) {
    if (!attempt.value || attempt.value.status !== 'in_progress') return
    setPosition(index)
    if (!force && (!positionDirty.value || currentIndex.value === lastSavedPosition.value)) return
    await updateAttemptPosition(attempt.value.id, index)
    lastSavedPosition.value = currentIndex.value
    positionDirty.value = false
  }

  function savePositionOnUnload() {
    if (!attempt.value || attempt.value.status !== 'in_progress') return
    if (!positionDirty.value || currentIndex.value === lastSavedPosition.value) return
    void updateAttemptPositionKeepalive(attempt.value.id, currentIndex.value)
    lastSavedPosition.value = currentIndex.value
    positionDirty.value = false
  }

  function updateSettings(settings: { autoNextOnCorrect?: boolean; animationDuration?: number }) {
    if (settings.autoNextOnCorrect !== undefined) {
      autoNextOnCorrect.value = settings.autoNextOnCorrect
      localStorage.setItem('qq_quiz_auto_next', settings.autoNextOnCorrect ? '1' : '0')
    }
    if (settings.animationDuration !== undefined) {
      const duration = Math.min(Math.max(Number(settings.animationDuration) || 0, 0), 600)
      animationDuration.value = duration
      localStorage.setItem('qq_quiz_animation_duration', String(duration))
    }
  }

  async function removeCurrentWrongQuestion() {
    if (!attempt.value || !currentItem.value) return
    const questionId = currentItem.value.question_id
    attempt.value = {
      ...attempt.value,
      total_questions: Math.max(0, attempt.value.total_questions - 1),
      items: attempt.value.items.filter((item) => item.question_id !== questionId),
    }
    if (attempt.value.items.length === 0) {
      currentIndex.value = 0
      return
    }
    currentIndex.value = Math.min(currentIndex.value, attempt.value.items.length - 1)
  }

  async function attachWrongQuestionIds(currentAttempt: QuizAttempt) {
    if (currentAttempt.items.some((item) => item.wrong_question_id)) return currentAttempt
    if (!currentAttempt.question_bank_id && !currentAttempt.items.some((item) => item.question)) return currentAttempt
    if (!currentAttempt.mode.includes('wrong')) return currentAttempt

    const response = await fetchWrongQuestions({
      question_bank_id: currentAttempt.question_bank_id,
      per_page: 100,
    }).catch(() => null)
    const wrongQuestions = response?.data.items ?? []
    if (wrongQuestions.length === 0) return currentAttempt
    const wrongByQuestion = new Map(wrongQuestions.map((wrong) => [wrong.question_id, wrong.id]))

    return {
      ...currentAttempt,
      items: currentAttempt.items.map((item) => ({
        ...item,
        wrong_question_id: wrongByQuestion.get(item.question_id),
      })),
    }
  }

  function updateCorrectCount(currentAttempt: QuizAttempt, questionId: number, isCorrect: boolean) {
    const previousItem = currentAttempt.items.find((item) => item.question_id === questionId)
    const previousCorrect = previousItem?.is_correct === true ? 1 : 0
    const nextCorrect = isCorrect ? 1 : 0

    return Math.max(0, currentAttempt.correct_count - previousCorrect + nextCorrect)
  }

  function judgeAnswer(question: QuizAttempt['items'][number]['question'], answer: Array<number | string>) {
    if (question.type === 'blank') {
      const expected = (question.answers ?? []).map((value) => String(value).trim())
      const actual = answer.map((value) => String(value).trim())

      return expected.length === actual.length && expected.every((value, index) => value === actual[index])
    }

    const expected = question.options
      .filter((option) => option.is_correct)
      .map((option) => Number(option.id))
      .sort((left, right) => left - right)
    const actual = answer
      .map((value) => Number(value))
      .sort((left, right) => left - right)

    return expected.length === actual.length && expected.every((value, index) => value === actual[index])
  }

  return {
    attempt,
    currentIndex,
    currentItem,
    answeredCount,
    clear,
    start,
    resume,
    answer,
    submit,
    setPosition,
    savePosition,
    savePositionOnUnload,
    autoNextOnCorrect,
    animationDuration,
    updateSettings,
    removeCurrentWrongQuestion,
  }
})
