import { useCallback, useDebugValue, useEffect, useMemo, useState } from "react";
import { enqueueSnackbar } from "notistack";
import moment from "moment";

import { isResultQuestionEmpty } from "@/components/ViewPublicationPage/tools/checkEmptyData";
import { useQuizStore } from "@/stores/useQuizStore";

import { useQuizViewStore } from "@stores/quizView";

import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
import { useQuestionTimer } from "./useQuestionTimer";

export function useLinearQuiz() {
  //Получаем инфо о квизе и список вопросов.
  const { settings, questions, quizId, cnt, preview } = useQuizStore();

  //Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page.
  //За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
  const sortedQuestions = useMemo(() => {
    return [...questions].sort((a, b) => a.page - b.page);
  }, [questions]);
  //React сам будет менять визуал - главное говорить из какого вопроса ему брать инфо. Изменение этой переменной меняет визуал.
  const [currentQuestionId, setCurrentQuestionId] = useState<string | null>(getFirstQuestionId);
  //Список ответов на вопрос. Мы записываем ответы локально, параллельно отправляя на бек информацию о ответах
  const answers = useQuizViewStore((state) => state.answers);
  //Список засчитанных баллов для балловых квизов
  const pointsSum = useQuizViewStore((state) => state.pointsSum);
  //Текущий шаг "startpage" | "question" | "contactform"
  const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep);
  //Получение возможности управлять состоянием метрик
  const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber);
  const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber);

  //Изменение стейта (переменной currentQuestionId) ведёт к пересчёту что же за объект сейчас используется. Мы каждый раз просто ищем в списке
  const currentQuestion = sortedQuestions.find((question) => question.id === currentQuestionId) ?? sortedQuestions[0];

  //Индекс текущего вопроса только если квиз линейный
  const linearQuestionIndex = //: number | null
    currentQuestion && sortedQuestions.every(({ content }) => content.rule.parentId !== "root") // null when branching enabled
      ? sortedQuestions.indexOf(currentQuestion)
      : null;

  //Индекс первого вопроса
  function getFirstQuestionId() {
    //: string | null
    if (sortedQuestions.length === 0) return null; //Если нету сортированного списка, то и не рыпаемся

    if (settings.cfg.haveRoot) {
      // Если есть ветвление, то settings.cfg.haveRoot будет заполнен
      //Если заполнен, то дерево растёт с root и это 1 вопрос :)
      const nextQuestion = sortedQuestions.find(
        //Функция ищет первое совпадение по массиву
        (question) => question.id === settings.cfg.haveRoot || question.content.id === settings.cfg.haveRoot
      );
      if (!nextQuestion) return null;

      return nextQuestion.id;
    }

    //Если не возникло исключительных ситуаций - первый вопрос - нулевой элемент сортированного массива
    return sortedQuestions[0].id;
  }

  const nextQuestionIdPointsLogic = useCallback(() => {
    return sortedQuestions.find((question) => question.type === "result" && question.content.rule.parentId === "line");
  }, [sortedQuestions]);

  //Анализируем какой вопрос должен быть следующим. Это главная логика
  const nextQuestionIdMainLogic = useCallback(() => {
    //Список ответов данных этому вопросу. Вернёт QuestionAnswer | undefined
    const questionAnswer = answers.find(({ questionId }) => questionId === currentQuestion.id);

    //Если questionAnswer не undefined и ответ на вопрос не является временем:
    if (questionAnswer && !moment.isMoment(questionAnswer.answer)) {
      //Вопрос типизации. Получаем список строк ответов на этот вопрос
      const userAnswers = Array.isArray(questionAnswer.answer) ? questionAnswer.answer : [questionAnswer.answer];

      //цикл. Перебираем список условий .main и обзываем их переменной branchingRule
      for (const branchingRule of currentQuestion.content.rule.main) {
        // Перебираем список ответов. Если хоть один ответ из списка совпадает с прописанным правилом из условий - этот вопрос нужный нам. Его и дадимкак следующий
        if (userAnswers.some((answer) => branchingRule.rules[0].answers.includes(answer))) {
          return branchingRule.next;
        }
      }
    }

    //Не помню что это, но чёт при первом взгляде оно true только у результатов
    if (!currentQuestion.required) {
      //Готовим себе дефолтный путь
      const defaultNextQuestionId = currentQuestion.content.rule.default;

      //Если строка не пустая и не пробел. (Обычно при получении данных мы сразу чистим пустые строки только с пробелом на просто пустые строки. Это прост доп защита)
      if (defaultNextQuestionId.length > 1 && defaultNextQuestionId !== " ") return defaultNextQuestionId;
      //Вопросы типа страница, ползунок, своё поле для ввода и дата не могут иметь больше 1 ребёнка. Пользователь не может настроить там дефолт
      //Кинуть на ребёнка надо даже если там нет дефолта
      if (
        ["date", "page", "text", "number"].includes(currentQuestion.type) &&
        currentQuestion.content.rule.children.length === 1
      )
        return currentQuestion.content.rule.children[0];
    }

    //ничё не нашли, ищем резулт
    return sortedQuestions.find((q) => {
      return q.type === "result" && q.content.rule.parentId === currentQuestion.content.id;
    })?.id;
  }, [answers, currentQuestion, sortedQuestions]);

  //Анализ следующего вопроса. Это логика для вопроса с баллами
  const nextQuestionId = useMemo(() => {
    if (settings.cfg.score) {
      return nextQuestionIdPointsLogic();
    }
    return nextQuestionIdMainLogic();
  }, [nextQuestionIdMainLogic, nextQuestionIdPointsLogic, settings.cfg.score, questions]);

  //Поиск предыдущго вопроса либо по индексу либо по id родителя
  const prevQuestion =
    linearQuestionIndex !== null
      ? sortedQuestions[linearQuestionIndex - 1]
      : sortedQuestions.find(
          (q) =>
            q.id === currentQuestion?.content.rule.parentId || q.content.id === currentQuestion?.content.rule.parentId
        );

  //Анализ результата по количеству баллов
  const findResultPointsLogic = useCallback(() => {
    //Отбираем из массива только тип резулт И результы с информацией о ожидаемых баллах И те результы, чьи суммы баллов меньше или равны насчитанным баллам юзера

    const results = sortedQuestions.filter(
      (e) => e.type === "result" && e.content.rule.minScore !== undefined && e.content.rule.minScore <= pointsSum
    );
    //Создаём массив строк из результатов. У кого есть инфо о баллах - дают свои, остальные 0
    const numbers = results.map((e) =>
      e.type === "result" && e.content.rule.minScore !== undefined ? e.content.rule.minScore : 0
    );
    //Извлекаем самое большое число
    const indexOfNext = Math.max(...numbers);
    //Отдаём индекс нужного нам результата
    return results[numbers.indexOf(indexOfNext)];
  }, [pointsSum, sortedQuestions]);

  //Ищем следующий вопрос (не его индекс, или id). Сам вопрос
  const nextQuestion = useMemo(() => {
    let next;

    if (settings.cfg.score) {
      //Ессли квиз балловый
      if (linearQuestionIndex !== null) {
        next = sortedQuestions[linearQuestionIndex + 1]; //ищем по индексу
        if (next?.type === "result" || next == undefined) next = findResultPointsLogic(); //если в поисках пришли к результату - считаем нужный
      }
    } else {
      //иначе
      if (linearQuestionIndex !== null) {
        //для линейных ищем по индексу
        next =
          sortedQuestions[linearQuestionIndex + 1] ??
          sortedQuestions.find((question) => question.type === "result" && question.content.rule.parentId === "line");
      } else {
        // для нелинейных ищем по вычесленному id
        next = sortedQuestions.find((q) => q.id === nextQuestionId || q.content.id === nextQuestionId);
      }
    }

    return next;
  }, [nextQuestionId, findResultPointsLogic, linearQuestionIndex, sortedQuestions, settings.cfg.score]);

  // Таймер авто-перехода между вопросами
  useQuestionTimer({
    enabled: Boolean(settings.questionTimerEnabled),
    seconds: settings.cfg.time_of_passing ?? 0,
    quizId,
    preview,
    currentQuestion,
    onNext: () => {
      // Программный переход к следующему вопросу
      moveToNextQuestion();
    },
  });

  //Показать визуалом юзеру результат
  const showResult = useCallback(() => {
    if (nextQuestion?.type !== "result") throw new Error("Current question is not result");

    //Записать в переменную ид текущего вопроса
    setCurrentQuestionId(nextQuestion.id);
    //Смотрим по настройкам показывать ли вообще форму контактов. Показывать ли страницу результатов до или после формы контактов (ФК)
    if (
      settings.cfg.showfc !== false &&
      (settings.cfg.resultInfo.showResultForm === "after" || isResultQuestionEmpty(nextQuestion))
    )
      setCurrentQuizStep("contactform");
  }, [nextQuestion, setCurrentQuizStep, settings.cfg.resultInfo.showResultForm, settings.cfg.showfc]);

  //рычаг управления из визуала в этот контроллер
  const showResultAfterContactForm = useCallback(() => {
    if (currentQuestion?.type !== "result") throw new Error("Current question is not result");
    if (isResultQuestionEmpty(currentQuestion)) return;

    setCurrentQuizStep("question");
  }, [currentQuestion, setCurrentQuizStep]);

  //рычаг управления из визуала в этот контроллер
  const moveToPrevQuestion = useCallback(() => {
    if (!prevQuestion) throw new Error("Previous question not found");

    setCurrentQuestionId(prevQuestion.id);
  }, [prevQuestion]);

  //рычаг управления из визуала в этот контроллер
  const moveToNextQuestion = useCallback(async () => {
    // Если есть следующий вопрос в уже загруженных - используем его

    if (nextQuestion) {
      vkMetrics.questionPassed(currentQuestion.id);
      yandexMetrics.questionPassed(currentQuestion.id);

      if (nextQuestion.type === "result") return showResult();
      setCurrentQuestionId(nextQuestion.id);
      return;
    }
  }, [currentQuestion.id, nextQuestion, showResult, vkMetrics, yandexMetrics, linearQuestionIndex, questions]);

  //рычаг управления из визуала в этот контроллер
  const setQuestion = useCallback(
    (questionId: string) => {
      const question = sortedQuestions.find((q) => q.id === questionId);
      if (!question) return;

      setCurrentQuestionId(question.id);
    },
    [sortedQuestions]
  );

  //Анализ дисаблить ли кнопки навигации
  const isPreviousButtonEnabled = settings.cfg?.backBlocked ? false : Boolean(prevQuestion);

  //Анализ дисаблить ли кнопки навигации
  const isNextButtonEnabled = useMemo(() => {
    const hasAnswer = answers.some(({ questionId }) => questionId === currentQuestion.id);

    if ("required" in currentQuestion.content && currentQuestion.content.required) {
      return hasAnswer;
    }

    if (linearQuestionIndex !== null && questions.length < cnt) return true;
    return Boolean(nextQuestion);
  }, [answers, currentQuestion, nextQuestion]);

  useDebugValue({
    linearQuestionIndex,
    currentQuestion: currentQuestion,
    prevQuestion: prevQuestion,
    nextQuestion: nextQuestion,
  });

  return {
    currentQuestion,
    currentQuestionStepNumber:
      settings.status === "ai" ? null : linearQuestionIndex === null ? null : linearQuestionIndex + 1,
    nextQuestion,
    isNextButtonEnabled,
    isPreviousButtonEnabled,
    moveToPrevQuestion,
    moveToNextQuestion,
    showResultAfterContactForm,
    setQuestion,
  };
}
