import { LevelPart } from "@/interfaces/exam";
import { useEffect, useRef, useState } from "react";

interface Props {
    part: LevelPart,
    contextWords: { match: string, originalLine: string }[] | undefined,
    setContextWordLines: React.Dispatch<React.SetStateAction<number[] | undefined>>
    setTotalLines: React.Dispatch<React.SetStateAction<number>>
}

const TextComponent: React.FC<Props> = ({ part, contextWords, setContextWordLines, setTotalLines }) => {
    const textRef = useRef<HTMLDivElement>(null);
    const [lineNumbers, setLineNumbers] = useState<number[]>([]);
    const [lineHeight, setLineHeight] = useState<number>(0);
    const [addBreaksTo, setAddBreaksTo] = useState<number[]>([]);

    const getBoldTag = (context: string) => {
        const regex = /<b\s+class=['"]([^'"]+)['"]>(\d+)<\/b>/;
        const match = context.match(regex);
        if (match) {
            return {
                className: match[1],
                number: match[2],
                fullTag: match[0]
            };
        }
        return null;
    };

    const bTag = getBoldTag(part.context!);

    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 || '';

            const paragraphs = textContent.split(/\n\n/);
            const betweenParagraphs: string[][] = Array.from({ length: paragraphs.length }, () => []);

            const lines = paragraphs.map((line, lineIndex) => {
                const paragraphWords = line.split(/(\s+)/);
                return paragraphWords.map((word, wordIndex) => {
                    if (lineIndex !== 0 && wordIndex == 0 && lineIndex < paragraphs.length) {
                        betweenParagraphs[lineIndex - 1][1] = word;
                    }

                    if (wordIndex == paragraphWords.length - 1 && lineIndex < paragraphs.length) {
                        betweenParagraphs[lineIndex][0] = word;
                    }
                    
                    const span = document.createElement('span');
                    if (wordIndex === 0 && bTag) {
                        const b = document.createElement('b');
                        b.classList.add(bTag.className);
                        b.textContent = `${lineIndex + 1}`;
                        span.appendChild(b);
                        span.appendChild(document.createTextNode(word.substring(1)));
                    }else {
                        span.appendChild(document.createTextNode(word));
                    }
                    return span;
                })
            }
            );

            lines.forEach(line => {
                line.forEach((span, index) => {
                    offscreenElement.appendChild(span);
                });
                offscreenElement.appendChild(document.createElement('br'));
            });

            document.body.appendChild(offscreenElement);

            const processedLines: string[][] = [[]];
            let currentLine = 1;
            let currentLineTop: number | undefined;

            let contextWordLines: number[] = [];
            if (contextWords) {
                contextWordLines = Array(contextWords.length).fill(-1);
            }
            const firstChild = offscreenElement.firstChild as HTMLElement;
            if (firstChild) {
                currentLineTop = firstChild.getBoundingClientRect().top;
            }

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

            let betweenIndex = 0;
            const addBreaksTo: number[] = [];
            spans.forEach((span, index) => {
                const rect = span.getBoundingClientRect();
                const top = rect.top;

                if (
                    betweenIndex < paragraphs.length - 1 &&
                    span.textContent === betweenParagraphs[betweenIndex][1] &&
                    spans[index - 1].textContent === betweenParagraphs[betweenIndex][0]
                ) {
                    addBreaksTo.push(currentLine);
                    betweenIndex = betweenIndex + 1;
                }

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

                processedLines[processedLines.length - 1].push(span.textContent?.trim() || '');
                if (contextWords && contextWordLines.some(element => element === -1)) {
                    contextWords.forEach((w, index) => {
                        if (span.textContent?.includes(w.match) && contextWordLines[index] == -1) {
                            contextWordLines[index] = currentLine;
                        }
                    })
                }
            });

            setAddBreaksTo(addBreaksTo);

            setLineNumbers(processedLines.map((_, index) => index + 1));
            setTotalLines(currentLine);

            if (contextWordLines.length > 0) {
                setContextWordLines(contextWordLines);
            }

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


    useEffect(() => {
        calculateLineNumbers();

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

        if (textRef.current) {
            // eslint-disable-next-line react-hooks/exhaustive-deps
            resizeObserver.observe(textRef.current);
        }

        return () => {
            if (textRef.current) {
                // eslint-disable-next-line react-hooks/exhaustive-deps
                resizeObserver.unobserve(textRef.current);
            }
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [part.context, contextWords]);

    return (
        <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>
                        {/* Do not delete the space between the span or else the lines get messed up */}
                        {addBreaksTo.includes(num) && <span className={`h-[${lineHeight}px] whitespace-pre-wrap`}> </span>}
                    </>
                ))}
            </div>
            <div ref={textRef} className="h-fit whitespace-pre-wrap ml-2">
                <div dangerouslySetInnerHTML={{ __html: part.context! }} />
            </div>
        </div>
    );
}

export default TextComponent;
