import BlankQuestionsModal from "@/components/BlankQuestionsModal";
import { renderExercise } from "@/components/Exercises";
import HighlightContent from "@/components/HighlightContent";
import Button from "@/components/Low/Button";
import ModuleTitle from "@/components/Medium/ModuleTitle";
import { renderSolution } from "@/components/Solutions";
import { infoButtonStyle } from "@/constants/buttonStyles";
import { Module } from "@/interfaces";
import { Exercise, FillBlanksExercise, FillBlanksMCOption, LevelExam, LevelPart, MultipleChoiceExercise, ShuffleMap, UserSolution, WritingExam } from "@/interfaces/exam";
import useExamStore from "@/stores/examStore";
import { defaultUserSolutions } from "@/utils/exams";
import { countExercises } from "@/utils/moduleUtils";
import { mdiArrowRight } from "@mdi/js";
import Icon from "@mdi/react";
import clsx from "clsx";
import { Dispatch, Fragment, SetStateAction, use, useEffect, useMemo, useRef, useState } from "react";
import { BsChevronDown, BsChevronUp } from "react-icons/bs";
import { toast } from "react-toastify";

interface Props {
	exam: LevelExam;
	showSolutions?: boolean;
	onFinish: (userSolutions: UserSolution[]) => void;
	editing?: boolean;
}

function TextComponent({
	part, highlightPhrases, contextWord, setContextWordLine
}: {
	part: LevelPart, highlightPhrases: string[], contextWord: string | undefined, setContextWordLine: React.Dispatch<React.SetStateAction<number | undefined>>
}) {
	const textRef = useRef<HTMLDivElement>(null);
	const [lineNumbers, setLineNumbers] = useState<number[]>([]);
	const [lineHeight, setLineHeight] = useState<number>(0);
	part.showContextLines = true;

	const calculateLineNumbers = () => {
		if (textRef.current) {
			const computedStyle = window.getComputedStyle(textRef.current);
			const lineHeightValue = parseFloat(computedStyle.lineHeight);
			const containerWidth = textRef.current.clientWidth;
			setLineHeight(lineHeightValue);

			const offscreenElement = document.createElement('div');
			offscreenElement.style.position = 'absolute';
			offscreenElement.style.top = '-9999px';
			offscreenElement.style.left = '-9999px';
			offscreenElement.style.whiteSpace = 'pre-wrap';
			offscreenElement.style.width = `${containerWidth}px`;
			offscreenElement.style.font = computedStyle.font;
			offscreenElement.style.lineHeight = computedStyle.lineHeight;
			offscreenElement.style.wordWrap = 'break-word';
			offscreenElement.style.textAlign = computedStyle.textAlign as CanvasTextAlign;

			const textContent = textRef.current.textContent || '';
			textContent.split(/(\s+)/).forEach((word: string) => {
				const span = document.createElement('span');
				span.textContent = word;
				offscreenElement.appendChild(span);
			});

			document.body.appendChild(offscreenElement);

			const lines: string[][] = [[]];
			let currentLine = 1;
			let currentLineTop: number | undefined;
			let contextWordLine: number | null = null;

			const firstChild = offscreenElement.firstChild as HTMLElement;
			if (firstChild) {
				currentLineTop = firstChild.getBoundingClientRect().top;
			}

			const spans = offscreenElement.querySelectorAll<HTMLSpanElement>('span');

			spans.forEach(span => {
				const rect = span.getBoundingClientRect();
				const top = rect.top;

				if (currentLineTop !== undefined && top > currentLineTop) {
					currentLine++;
					currentLineTop = top;
					lines.push([]);
				}

				lines[lines.length - 1].push(span.textContent?.trim() || '');


				if (contextWord && contextWordLine === null && span.textContent?.includes(contextWord)) {
					contextWordLine = currentLine;
				}
			});

			setLineNumbers(lines.map((_, index) => index + 1));
			if (contextWordLine) {
				setContextWordLine(contextWordLine);
			}

			document.body.removeChild(offscreenElement);
		}
	};

	useEffect(() => {
		calculateLineNumbers();

		const resizeObserver = new ResizeObserver(() => {
			calculateLineNumbers();
		});

		if (textRef.current) {
			resizeObserver.observe(textRef.current);
		}

		return () => {
			if (textRef.current) {
				resizeObserver.unobserve(textRef.current);
			}
		};
	}, [part.context, part.showContextLines, contextWord]);

	if (typeof part.showContextLines === "undefined") {
		return (
			<div className="flex flex-col gap-2 w-full">
				<div className="border border-mti-gray-dim w-full rounded-full opacity-10" />
				{!!part.context &&
					part.context
						.split(/\n|(\\n)/g)
						.filter((x) => x && x.length > 0 && x !== "\\n")
						.map((line, index) => (
							<Fragment key={index}>
								<p key={index}>{line}</p>
							</Fragment>
						))}
			</div>
		);
	}

	return (
		<div className="flex flex-col gap-2 w-full">
			<div className="border border-mti-gray-dim w-full rounded-full opacity-10" />
			<div className="flex mt-2">
				<div className="flex-shrink-0 w-8 pr-2">
					{lineNumbers.map(num => (
						<div key={num} className="text-gray-400 flex justify-end" style={{ lineHeight: `${lineHeight}px` }}>
							{num}
						</div>
					))}
				</div>
				<div ref={textRef} className="h-fit whitespace-pre-wrap ml-2">
					<HighlightContent html={part.context!} highlightPhrases={highlightPhrases} firstOccurence={true} />
				</div>
			</div>
		</div>
	);
}


const typeCheckWordsMC = (words: any[]): words is FillBlanksMCOption[] => {
	return Array.isArray(words) && words.every(
		word => word && typeof word === 'object' && 'id' in word && 'options' in word
	);
}


export default function Level({ exam, showSolutions = false, onFinish, editing = false }: Props) {
	const [multipleChoicesDone, setMultipleChoicesDone] = useState<{ id: string; amount: number }[]>([]);
	const [showBlankModal, setShowBlankModal] = useState(false);

	const { userSolutions, setUserSolutions } = useExamStore((state) => state);
	const { hasExamEnded, setHasExamEnded } = useExamStore((state) => state);
	const { partIndex, setPartIndex } = useExamStore((state) => state);
	const { exerciseIndex, setExerciseIndex } = useExamStore((state) => state);
	const [storeQuestionIndex, setStoreQuestionIndex] = useExamStore((state) => [state.questionIndex, state.setQuestionIndex]);
	const [shuffleMaps, setShuffleMaps] = useExamStore((state) => [state.shuffleMaps, state.setShuffleMaps])
	const [currentExercise, setCurrentExercise] = useState<Exercise>();

	const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));

	const [highlightPhrases, setContextHighlight] = useState<string[]>([]);
	const [contextWord, setContextWord] = useState<string | undefined>(undefined);
	const [contextWordLine, setContextWordLine] = useState<number | undefined>(undefined);

	useEffect(() => {
		if (showSolutions && userSolutions[exerciseIndex].shuffleMaps) {
			setShuffleMaps(userSolutions[exerciseIndex].shuffleMaps as ShuffleMap[])
		}
	}, [showSolutions])

	useEffect(() => {
		if (hasExamEnded && exerciseIndex === -1) {
			setExerciseIndex(exerciseIndex + 1);
		}
	}, [hasExamEnded, exerciseIndex, setExerciseIndex]);

	const confirmFinishModule = (keepGoing?: boolean) => {
		if (!keepGoing) {
			setShowBlankModal(false);
			return;
		}

		onFinish(userSolutions);
	};


	const getExercise = () => {
		if (exerciseIndex === -1) {
			return undefined;
		}
		let exercise = exam.parts[partIndex]?.exercises[exerciseIndex];
		if (!exercise) return undefined;

		exercise = {
			...exercise,
			userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [],
		};

		if (exam.shuffle && exercise.type === "multipleChoice") {
			if (shuffleMaps.length == 0 && !showSolutions) {
				const newShuffleMaps: ShuffleMap[] = [];

				exercise.questions = exercise.questions.map(question => {
					const options = [...question.options];
					let shuffledOptions = [...options].sort(() => Math.random() - 0.5);

					const newOptions = options.map((option, index) => ({
						id: option.id,
						text: shuffledOptions[index].text
					}));

					const optionMapping = options.reduce<{ [key: string]: string }>((acc, originalOption) => {
						const shuffledPosition = newOptions.find(newOpt => newOpt.text === originalOption.text)?.id;
						if (shuffledPosition) {
							acc[shuffledPosition] = originalOption.id;
						}
						return acc;
					}, {});

					newShuffleMaps.push({ id: question.id, map: optionMapping });

					return { ...question, options: newOptions };
				});

				setShuffleMaps(newShuffleMaps);
			} else {
				exercise.questions = exercise.questions.map(question => {
					const questionShuffleMap = shuffleMaps.find(map => map.id === question.id);
					if (questionShuffleMap) {
						const newOptions = question.options.map(option => ({
							id: option.id,
							text: question.options.find(o => questionShuffleMap.map[o.id] === option.id)?.text || option.text
						}));
						return { ...question, options: newOptions };
					}
					return question;
				});
			}
		} else if (exam.shuffle && exercise.type === "fillBlanks" && typeCheckWordsMC(exercise.words)) {
			if (shuffleMaps.length === 0 && !showSolutions) {
				const newShuffleMaps: ShuffleMap[] = [];

				exercise.words = exercise.words.map(word => {
					if ('options' in word) {
						const options = { ...word.options };
						const shuffledKeys = Object.keys(options).sort(() => Math.random() - 0.5);

						const newOptions = shuffledKeys.reduce((acc, key, index) => {
							acc[key as keyof typeof options] = options[shuffledKeys[index] as keyof typeof options];
							return acc;
						}, {} as { [key in keyof typeof options]: string });

						const optionMapping = shuffledKeys.reduce((acc, key, index) => {
							acc[key as keyof typeof options] = Object.keys(options)[index] as keyof typeof options;
							return acc;
						}, {} as { [key in keyof typeof options]: string });

						newShuffleMaps.push({ id: word.id, map: optionMapping });

						return { ...word, options: newOptions };
					}
					return word;
				});

				setShuffleMaps(newShuffleMaps);
			}
		}

		return exercise;
	};

	useEffect(() => {
		const newExercise = getExercise();
		setCurrentExercise(newExercise);
	}, [partIndex, exerciseIndex]);


	//useShuffledMultipleChoiceOptions(currentExercise, exam.shuffle, storeQuestionIndex, shuffleMaps, setShuffleMaps, setCurrentExercise);
	//useShuffledFillBlanksOptions(currentExercise, exam.shuffle, storeQuestionIndex, shuffleMaps, setShuffleMaps, setCurrentExercise);


	useEffect(() => {
		const regex = /.*?['"](.*?)['"] in line (\d+)\?$/;
		if (currentExercise && currentExercise.type === "multipleChoice") {
			const match = currentExercise.questions[storeQuestionIndex].prompt.match(regex);
			if (match) {
				const word = match[1];
				const originalLineNumber = match[2];
				setContextHighlight([word]);

				if (word !== contextWord) {
					setContextWord(word);
				}

				const updatedPrompt = currentExercise.questions[storeQuestionIndex].prompt.replace(
					`in line ${originalLineNumber}`,
					`in line ${contextWordLine || originalLineNumber}`
				);

				currentExercise.questions[storeQuestionIndex].prompt = updatedPrompt;
			} else {
				setContextHighlight([]);
				setContextWord(undefined);
			}
		}
	}, [storeQuestionIndex, contextWordLine, exerciseIndex, partIndex, shuffleMaps]);

	const nextExercise = (solution?: UserSolution) => {
		scrollToTop();
		if (solution) {
			setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "level", exam: exam.id }]);
		}

		if (storeQuestionIndex > 0) {
			setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: storeQuestionIndex }]);
		}
		setStoreQuestionIndex(0);

		if (exerciseIndex + 1 < exam.parts[partIndex].exercises.length && !hasExamEnded) {
			setExerciseIndex(exerciseIndex + 1);
			return;
		}

		if (partIndex + 1 < exam.parts.length && !hasExamEnded) {
			setPartIndex(partIndex + 1);
			setExerciseIndex(!!exam.parts[partIndex + 1].context ? -1 : 0);
			return;
		}

		if (
			solution &&
			![...userSolutions.filter((x) => x.exercise !== solution?.exercise).map((x) => x.score.missing), solution?.score.missing].every(
				(x) => x === 0,
			) &&
			!showSolutions &&
			!editing &&
			!hasExamEnded
		) {
			setShowBlankModal(true);
			return;
		}

		setHasExamEnded(false);

		if (solution) {
			let stat = { ...solution, module: "level" as Module, exam: exam.id }
			if (exam.shuffle) {
				stat.shuffleMaps = shuffleMaps
			}
			onFinish([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...stat }]);
		} else {
			onFinish(userSolutions);
		}
	};

	const previousExercise = (solution?: UserSolution) => {
		scrollToTop();
		if (solution) {
			setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "level", exam: exam.id }]);
		}

		if (storeQuestionIndex > 0) {
			setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: storeQuestionIndex }]);
		}
		setStoreQuestionIndex(0);

		setExerciseIndex(exerciseIndex - 1);
	};

	const calculateExerciseIndex = () => {
		if (partIndex === 0)
			return (
				(exerciseIndex === -1 ? 0 : exerciseIndex + 1) + storeQuestionIndex + multipleChoicesDone.reduce((acc, curr) => acc + curr.amount, 0)
			);

		const exercisesPerPart = exam.parts.map((x) => x.exercises.length);
		const exercisesDone = exercisesPerPart.filter((_, index) => index < partIndex).reduce((acc, curr) => curr + acc, 0);
		return (
			exercisesDone +
			(exerciseIndex === -1 ? 0 : exerciseIndex + 1) +
			storeQuestionIndex +
			multipleChoicesDone.reduce((acc, curr) => acc + curr.amount, 0)
		);
	};

	const renderText = () => (
		<div className={clsx("flex flex-col gap-6 w-full bg-mti-gray-seasalt rounded-xl mt-4 relative py-8 px-16")}>
			<>
				<div className="flex flex-col w-full gap-2">
					<h4 className="text-xl font-semibold">
						Please read the following excerpt attentively, you will then be asked questions about the text you&apos;ve read.
					</h4>
					<span className="text-base">You will be allowed to read the text while doing the exercises</span>
				</div>
				<TextComponent
					part={exam.parts[partIndex]}
					highlightPhrases={highlightPhrases}
					contextWord={contextWord}
					setContextWordLine={setContextWordLine}
				/>
			</>
		</div>
	);

	return (
		<>
			<div className="flex flex-col h-full w-full gap-8 items-center">
				<BlankQuestionsModal isOpen={showBlankModal} onClose={confirmFinishModule} />
				<ModuleTitle
					minTimer={exam.minTimer}
					exerciseIndex={calculateExerciseIndex()}
					module="level"
					totalExercises={countExercises(exam.parts.flatMap((x) => x.exercises))}
					disableTimer={showSolutions || editing}
				/>
				<div
					className={clsx(
						"mb-20 w-full",
						partIndex > -1 && exerciseIndex > -1 && !!exam.parts[partIndex].context && "grid grid-cols-2 gap-4",
					)}>
					{partIndex > -1 && !!exam.parts[partIndex].context && renderText()}

					{exerciseIndex > -1 &&
						partIndex > -1 &&
						exerciseIndex < exam.parts[partIndex].exercises.length &&
						!showSolutions &&
						!editing &&
						currentExercise &&
						renderExercise(currentExercise, exam.id, nextExercise, previousExercise)}

					{exerciseIndex > -1 &&
						partIndex > -1 &&
						exerciseIndex < exam.parts[partIndex].exercises.length &&
						(showSolutions || editing) &&
						renderSolution(exam.parts[partIndex].exercises[exerciseIndex], nextExercise, previousExercise)}
				</div>
				{exerciseIndex === -1 && partIndex > 0 && (
					<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
						<Button
							color="purple"
							variant="outline"
							onClick={() => {
								setExerciseIndex(exam.parts[partIndex - 1].exercises.length - 1);
								setPartIndex(partIndex - 1);
							}}
							className="max-w-[200px] w-full">
							Back
						</Button>

						<Button color="purple" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full">
							Next
						</Button>
					</div>
				)}
				{exerciseIndex === -1 && partIndex === 0 && (
					<Button color="purple" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full">
						Start now
					</Button>
				)}
			</div>
		</>
	);
}
