import React, {useState, useEffect, useRef, useCallback} from "react";
import {animated} from "@react-spring/web";
import {FaRegCirclePlay, FaRegCircleStop} from "react-icons/fa6";
import HighlightContent from "../HighlightContent";
import {ITrainingTip, SegmentRef, TimelineEvent} from "./TrainingInterfaces";

const ExerciseWalkthrough: React.FC<ITrainingTip> = (tip: ITrainingTip) => {
	const [isAutoPlaying, setIsAutoPlaying] = useState<boolean>(false);
	const [currentTime, setCurrentTime] = useState<number>(0);
	const [walkthroughHtml, setWalkthroughHtml] = useState<string>("");
	const [highlightedPhrases, setHighlightedPhrases] = useState<string[]>([]);
	const [isPlaying, setIsPlaying] = useState<boolean>(false);
	const timelineRef = useRef<TimelineEvent[]>([]);
	const animationRef = useRef<number | null>(null);
	const segmentsRef = useRef<SegmentRef[]>([]);

	const toggleAutoPlay = useCallback(() => {
		setIsAutoPlaying((prev) => {
			if (!prev && currentTime === getMaxTime()) {
				setCurrentTime(0);
			}
			return !prev;
		});
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [currentTime]);

	const handleAnimationComplete = useCallback(() => {
		setIsAutoPlaying(false);
	}, []);

	const handleResetAnimation = useCallback((newTime: number) => {
		setCurrentTime(newTime);
	}, []);

	const getMaxTime = (): number => {
		return (
			tip.exercise?.segments.reduce((sum, segment) => sum + segment.wordDelay * segment.html.split(/\s+/).length + segment.holdDelay, 0) ?? 0
		);
	};

	useEffect(() => {
		const timeline: TimelineEvent[] = [];
		let currentTimePosition = 0;
		segmentsRef.current = [];

		tip.exercise?.segments.forEach((segment, index) => {
			const parser = new DOMParser();
			const doc = parser.parseFromString(segment.html, "text/html");
			const words: string[] = [];
			const walkTree = (node: Node) => {
				if (node.nodeType === Node.TEXT_NODE) {
					words.push(...(node.textContent?.split(/\s+/).filter((word) => word.length > 0) || []));
				} else if (node.nodeType === Node.ELEMENT_NODE) {
					Array.from(node.childNodes).forEach(walkTree);
				}
			};
			walkTree(doc.body);

			const textDuration = words.length * segment.wordDelay;

			segmentsRef.current.push({
				...segment,
				words: words,
				startTime: currentTimePosition,
				endTime: currentTimePosition + textDuration,
			});

			timeline.push({
				type: "text",
				start: currentTimePosition,
				end: currentTimePosition + textDuration,
				segmentIndex: index,
			});

			currentTimePosition += textDuration;

			timeline.push({
				type: "highlight",
				start: currentTimePosition,
				end: currentTimePosition + segment.holdDelay,
				content: segment.highlight,
				segmentIndex: index,
			});

			currentTimePosition += segment.holdDelay;
		});

		timelineRef.current = timeline;
	}, [tip.exercise?.segments]);

	const updateText = useCallback(() => {
		const currentEvent = timelineRef.current.find((event) => currentTime >= event.start && currentTime < event.end);

		if (currentEvent) {
			if (currentEvent.type === "text") {
				const segment = segmentsRef.current[currentEvent.segmentIndex];
				const elapsedTime = currentTime - currentEvent.start;
				const wordsToShow = Math.min(Math.floor(elapsedTime / segment.wordDelay), segment.words.length);

				const previousSegmentsHtml = segmentsRef.current
					.slice(0, currentEvent.segmentIndex)
					.map((seg) => seg.html)
					.join("");

				const parser = new DOMParser();
				const doc = parser.parseFromString(segment.html, "text/html");
				let wordCount = 0;
				const walkTree = (node: Node, action: (node: Node) => void): boolean => {
					if (node.nodeType === Node.TEXT_NODE && node.textContent) {
						const words = node.textContent.split(/(\s+)/).filter((word) => word.length > 0);
						if (wordCount + words.filter((w) => !/\s+/.test(w)).length <= wordsToShow) {
							action(node.cloneNode(true));
							wordCount += words.filter((w) => !/\s+/.test(w)).length;
						} else {
							const remainingWords = wordsToShow - wordCount;
							const newTextContent = words.reduce(
								(acc, word) => {
									if (!/\s+/.test(word) && acc.nonSpaceWords < remainingWords) {
										acc.text += word;
										acc.nonSpaceWords++;
									} else if (/\s+/.test(word) || acc.nonSpaceWords < remainingWords) {
										acc.text += word;
									}
									return acc;
								},
								{text: "", nonSpaceWords: 0},
							).text;
							const newNode = node.cloneNode(false);
							newNode.textContent = newTextContent;
							action(newNode);
							wordCount = wordsToShow;
						}
					} else if (node.nodeType === Node.ELEMENT_NODE) {
						const clone = node.cloneNode(false);
						action(clone);
						Array.from(node.childNodes).some((child) => {
							return walkTree(child, (childNode) => (clone as Node).appendChild(childNode));
						});
					}
					return wordCount >= wordsToShow;
				};
				const fragment = document.createDocumentFragment();
				walkTree(doc.body, (node) => fragment.appendChild(node));

				const serializer = new XMLSerializer();
				const currentSegmentHtml = Array.from(fragment.childNodes)
					.map((node) => serializer.serializeToString(node))
					.join("");
				const newHtml = previousSegmentsHtml + currentSegmentHtml;

				setWalkthroughHtml(newHtml);
				setHighlightedPhrases([]);
			} else if (currentEvent.type === "highlight") {
				const newHtml = segmentsRef.current
					.slice(0, currentEvent.segmentIndex + 1)
					.map((seg) => seg.html)
					.join("");
				setWalkthroughHtml(newHtml);
				setHighlightedPhrases(currentEvent.content || []);
			}
		}
	}, [currentTime]);

	useEffect(() => {
		updateText();
	}, [currentTime, updateText]);

	useEffect(() => {
		if (isAutoPlaying) {
			const lastEvent = timelineRef.current[timelineRef.current.length - 1];
			if (lastEvent && currentTime >= lastEvent.end) {
				setCurrentTime(0);
			}
			setIsPlaying(true);
		} else {
			setIsPlaying(false);
		}
	}, [isAutoPlaying, currentTime]);

	useEffect(() => {
		const animate = () => {
			if (isPlaying) {
				setCurrentTime((prevTime) => {
					const newTime = prevTime + 50;
					const lastEvent = timelineRef.current[timelineRef.current.length - 1];
					if (lastEvent && newTime >= lastEvent.end) {
						setIsPlaying(false);
						handleAnimationComplete();
						return lastEvent.end;
					}
					return newTime;
				});
			}
			animationRef.current = requestAnimationFrame(animate);
		};

		animationRef.current = requestAnimationFrame(animate);

		return () => {
			if (animationRef.current) {
				cancelAnimationFrame(animationRef.current);
			}
		};
	}, [isPlaying, handleAnimationComplete]);

	const handleSliderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
		const newTime = parseInt(e.target.value, 10);
		setCurrentTime(newTime);
		handleResetAnimation(newTime);
	};

	const handleSliderMouseDown = () => {
		setIsPlaying(false);
	};

	const handleSliderMouseUp = () => {
		if (isAutoPlaying) {
			setIsPlaying(true);
		}
	};

	if (tip.standalone || !tip.exercise) {
		return (
			<div className="container mx-auto">
				<h1 className="text-xl font-bold text-red-600">The exercise for this tip is not available yet!</h1>
				<div className="tip-container bg-blue-100 p-4 rounded-lg shadow-md mb-4 mt-10">
					<h3 className="text-xl font-semibold text-blue-800 mb-2">{tip.tipCategory}</h3>
					<div className="text-gray-700" dangerouslySetInnerHTML={{__html: tip.tipHtml}} />
				</div>
			</div>
		);
	}

	return (
		<div className="container mx-auto">
			<div className="tip-container bg-blue-100 p-4 rounded-lg shadow-md mb-4">
				<h3 className="text-xl font-semibold text-blue-800 mb-2">{tip.tipCategory}</h3>
				<div className="text-gray-700" dangerouslySetInnerHTML={{__html: tip.tipHtml}} />
			</div>
			<div className="flex flex-col space-y-4">
				<div className="flex flex-row items-center space-x-4 py-4">
					<button
						onClick={toggleAutoPlay}
						className="p-2 bg-blue-500 text-white rounded-full transition-colors duration-200 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"
						aria-label={isAutoPlaying ? "Pause" : "Play"}>
						{isAutoPlaying ? <FaRegCircleStop className="w-6 h-6" /> : <FaRegCirclePlay className="w-6 h-6" />}
					</button>
					<input
						type="range"
						min="0"
						max={timelineRef.current.length > 0 ? timelineRef.current[timelineRef.current.length - 1].end : 0}
						value={currentTime}
						onChange={handleSliderChange}
						onMouseDown={handleSliderMouseDown}
						onMouseUp={handleSliderMouseUp}
						onTouchStart={handleSliderMouseDown}
						onTouchEnd={handleSliderMouseUp}
						className="flex-grow"
					/>
				</div>
				<div className="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4">
					<div className="flex-1 bg-white p-6 rounded-lg shadow">
						{/*<h2 className="text-xl font-bold mb-4">Question</h2>*/}
						<div className="mb-4" dangerouslySetInnerHTML={{__html: tip.exercise.question}} />
						<HighlightContent html={tip.exercise.highlightable} highlightPhrases={highlightedPhrases} />
					</div>
					<div className="flex-1">
						<div className="bg-gray-50 rounded-lg shadow">
							<div className="p-6 space-y-4">
								<animated.div dangerouslySetInnerHTML={{__html: walkthroughHtml}} />
							</div>
						</div>
					</div>
				</div>
			</div>
		</div>
	);
};

export default ExerciseWalkthrough;
