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, HighlightConfig, InsertHtmlConfig } from './TrainingInterfaces';
import Tip from './Tip';

interface HtmlState {
	question: string;
	additional: string;
	walkthrough: string;
}

const ExerciseWalkthrough: React.FC<ITrainingTip> = (tip: ITrainingTip) => {
	const [isAutoPlaying, setIsAutoPlaying] = useState<boolean>(false);
	const [currentTime, setCurrentTime] = useState<number>(0);
	const [currentHighlightConfigs, setCurrentHighlightConfigs] = useState<HighlightConfig[]>([]);
	const [isPlaying, setIsPlaying] = useState<boolean>(false);
	const [currentSegmentIndex, setCurrentSegmentIndex] = useState<number>(0);
	const timelineRef = useRef<TimelineEvent[]>([]);
	const animationRef = useRef<number | null>(null);
	const segmentsRef = useRef<SegmentRef[]>([]);

	const [questionHtml, setQuestionHtml] = useState(tip.exercise?.question || '');
	const [additionalHtml, setAdditionalHtml] = useState(tip.exercise?.additional || '');
	const [walkthroughHtml, setWalkthroughHtml] = useState<string>('');
	const [htmlStates, setHtmlStates] = useState<HtmlState[]>([]);
	const lastProcessedInsertTime = useRef<number>(-1);

	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 = [];
		const newHtmlStates: HtmlState[] = [];

		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
			});

			if (segment.insertHTML && segment.insertHTML.length > 0) {
				newHtmlStates.push({
					question: questionHtml,
					additional: additionalHtml,
					walkthrough: walkthroughHtml
				});
				timeline.push({
					type: 'insert',
					start: currentTimePosition,
					end: currentTimePosition + segment.holdDelay,
					segmentIndex: index,
					content: segment.insertHTML
				});
			}

			currentTimePosition += segment.holdDelay;
		});

		for (let i = 0; i < timeline.length; i++) {
			if (timeline[i].type === 'insert') {
				const nextInsertIndex = timeline.findIndex((event, index) => index > i && event.type === 'insert');
				if (nextInsertIndex !== -1) {
					timeline[i].end = timeline[nextInsertIndex].start;
				} else {
					timeline[i].end = currentTimePosition;
				}
			}
		}

		timelineRef.current = timeline;
		setHtmlStates(newHtmlStates);
	}, [tip.exercise?.segments, questionHtml, additionalHtml, walkthroughHtml]);

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

		if (currentTime < lastProcessedInsertTime.current) {
			const lastInsertEvent = timelineRef.current
				.filter(event => event.type === 'insert' && event.start <= currentTime)
				.pop();

			if (lastInsertEvent) {
				const stateIndex = timelineRef.current.indexOf(lastInsertEvent);
				if (stateIndex >= 0 && stateIndex < htmlStates.length) {
					const previousState = htmlStates[stateIndex];
					setQuestionHtml(previousState.question);
					setAdditionalHtml(previousState.additional);
					setWalkthroughHtml(previousState.walkthrough);
				}
			} else {
				// If no previous insert event, revert to initial state
				setQuestionHtml(tip.exercise?.question || '');
				setAdditionalHtml(tip.exercise?.additional || '');
				setWalkthroughHtml('');
			}
		}

		currentEvents.forEach(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);
				setCurrentSegmentIndex(currentEvent.segmentIndex);
				setCurrentHighlightConfigs([]);
			} else if (currentEvent.type === 'highlight') {
				const newHtml = segmentsRef.current
					.slice(0, currentEvent.segmentIndex + 1)
					.map(seg => seg.html)
					.join('');
				setWalkthroughHtml(newHtml);
				setCurrentSegmentIndex(currentEvent.segmentIndex);
				setCurrentHighlightConfigs(currentEvent.content as HighlightConfig[] || []);
			} else if (currentEvent.type === 'insert') {
				const insertConfigs = currentEvent.content as InsertHtmlConfig[];
				insertConfigs.forEach(config => {
					switch (config.target) {
						case 'question':
							setQuestionHtml(prevHtml => insertHtmlContent(prevHtml, config));
							break;
						case 'additional':
							setAdditionalHtml(prevHtml => insertHtmlContent(prevHtml, config));
							break;
						case 'segment':
							setWalkthroughHtml(prevHtml => insertHtmlContent(prevHtml, config));
							break;
					}
				});
				lastProcessedInsertTime.current = currentTime;
			}
		});
	}, [currentTime, htmlStates, tip.exercise?.question, tip.exercise?.additional]);

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

	const insertHtmlContent = (prevHtml: string, config: InsertHtmlConfig): string => {
		const tempDiv = document.createElement('div');
		tempDiv.innerHTML = prevHtml;

		const targetElement = tempDiv.querySelector(`#${config.targetId}`);
		if (targetElement) {
			switch (config.position) {
				case 'append':
					targetElement.insertAdjacentHTML('beforeend', config.html);
					break;
				case 'prepend':
					targetElement.insertAdjacentHTML('afterbegin', config.html);
					break;
				case 'replace':
					targetElement.innerHTML = config.html;
					break;
			}
		}

		return tempDiv.innerHTML;
	};


	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);
		}
	};

	return (
		<div className="container mx-auto py-6">
			<Tip category={tip.tipCategory} html={tip.tipHtml} />
			{!tip.standalone && (
				<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 w-full'>
						<div className='flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 w-full'>
							<div className='flex-1 bg-white p-6 rounded-lg shadow space-y-6'>
								<div className="container mx-auto px-4">
									<div id="question-container" className="border p-6 rounded-lg shadow-md">
										<HighlightContent
											html={questionHtml}
											highlightConfigs={currentHighlightConfigs}
											contentType="question"
										/>
									</div>
								</div>
								{tip.exercise?.additional && (<div className="container mx-auto px-4">
									<div id="additional-container" className="border p-6 rounded-lg shadow-md">
										<HighlightContent
											html={additionalHtml}
											highlightConfigs={currentHighlightConfigs}
											contentType="additional"
										/>
									</div>
								</div>
								)}
							</div>
							<div className='flex-1'>
								<div className='bg-gray-50 rounded-lg shadow'>
									<div id="segment-container" className='p-6 space-y-4'>
										<animated.div>
											<HighlightContent
												html={walkthroughHtml}
												highlightConfigs={currentHighlightConfigs.filter(config =>
													config.targets.includes('segment') || config.targets.includes('all')
												)}
												contentType="segment"
												currentSegmentIndex={currentSegmentIndex}
											/>
										</animated.div>
									</div>
								</div>
							</div>
						</div>
					</div>
				</div>
			)}
		</div>
	);
};

export default ExerciseWalkthrough;