/**
 * LayoutRenderer Module
 * Translates the abstract layout data into concrete visual elements (DOM nodes).
 */
export class LayoutRenderer {
    /**
     * Create a new LayoutRenderer
     * @param {Object} animationQueue - The AnimationQueue instance
     */
    constructor(animationQueue) {
        this.animationQueue = animationQueue;
        this.fastForwardingAll = false;
    }

    /**
     * Render a paragraph based on layout data
     * @param {Object} paragraphData - The layout data from ParagraphLayout
     * @param {number} delay - Initial delay for animations
     * @param {Array<number>} measure - Array of line width measurements
     * @returns {Array} Array containing the paragraph element and the final delay
     */
    renderParagraph(paragraphData, delay = 0, measure = []) {
        const stack = [];
        let left = 0;
        const p = document.createElement("p");
        p.style.position = 'relative';
        p.classList.add("latest-paragraph");
        p.dataset.numberOfLines = paragraphData.breaks.length - 1;
        
        const lineHeight = parseFloat(window.getComputedStyle(document.querySelector('#ruler')).lineHeight);
        const lineWidth = parseFloat(window.getComputedStyle(document.getElementById('story')).width);
        const pageHeight = parseFloat(window.getComputedStyle(document.getElementById('page_right')).height);
        
        p.style.height = lineHeight * (paragraphData.breaks.length - 1) + 'px';
        const paragraphHeight = parseFloat(p.style.height);
        p.dataset.vpc = paragraphHeight * 100 / pageHeight;
        p.style.marginBlockEnd = 0;
        stack.push(p);
        
        for (let i = 1; i < paragraphData.breaks.length; i++) {
            left = measure[measure.length - 1] - measure[Math.min(i - 1, measure.length - 1)];
            let lastChild = null;
            let syllable = "";
            
            for (let j = paragraphData.breaks[i-1].position; j <= paragraphData.breaks[i].position; j++) {
                if (paragraphData.nodes[j].type === 'box' && paragraphData.nodes[j].value !== '' && j < paragraphData.breaks[i].position) {
                    if (j > paragraphData.breaks[i-1].position + 1 && paragraphData.nodes[j-1].type === 'penalty' && lastChild) {
                        syllable += '\u200c' + paragraphData.nodes[j].value;
                        lastChild.innerHTML = syllable;
                        left += paragraphData.nodes[j].width;
                    } else {
                        let word = document.createElement("span");
                        word.style.position = 'absolute';
                        word.classList.add("fade-in");
                        word.style.animationDuration = this.animationQueue.getSpeed() * 10 + 'ms';
                        word.style.top = lineHeight * (i - 1) * 100 / paragraphHeight + '%';
                        word.style.left = left * 100 / lineWidth + '%';
                        syllable = paragraphData.nodes[j].value;
                        word.innerHTML = syllable;
                        lastChild = word;
                        
                        if (!this.fastForwardingAll) {
                            this.insertAfter(delay, stack[stack.length-1], word);
                        }
                        
                        delay += this.animationQueue.getSpeed();
                        left += paragraphData.nodes[j].width;
                    }
                } else if (paragraphData.nodes[j].type === 'tag') {
                    if (paragraphData.nodes[j].value.substr(0, 2) == '</') {
                        stack.pop();
                    } else {
                        let tmp = document.createElement('div');
                        tmp.innerHTML = paragraphData.nodes[j].value;
                        const word = tmp.firstChild;
                        word.style.left = left * 100 / lineWidth + '%';
                        stack[stack.length-1].appendChild(word);
                        stack.push(word);
                    }
                } else if (j > paragraphData.breaks[i-1].position && paragraphData.nodes[j].type === 'glue' && paragraphData.nodes[j].width !== 0 && j <= paragraphData.breaks[i].position) {
                    // Insert space character
                    if (paragraphData.breaks[i].ratio > 0) {
                        left += paragraphData.nodes[j].width + paragraphData.breaks[i].ratio * paragraphData.nodes[j].stretch;
                    } else {
                        left += paragraphData.nodes[j].width + paragraphData.breaks[i].ratio * paragraphData.nodes[j].shrink;
                    }
                    
                    let word = document.createElement("span");
                    word.style.position = 'absolute';
                    word.classList.add("fade-in");
                    word.style.top = lineHeight * (i - 1) * 100 / paragraphHeight + '%';
                    word.style.left = left * 100 / lineWidth + '%';
                    word.innerHTML = " ";
                    
                    if (!this.fastForwardingAll) {
                        this.insertAfter(delay, stack[stack.length-1], word);
                    }
                } else if (paragraphData.nodes[j].type === 'penalty' && paragraphData.nodes[j].penalty === 100 && j === paragraphData.breaks[i].position) {
                    // Create a hyphen at the end of the line if breaking at a hyphenation point
                    let hyphen = document.createElement("span");
                    hyphen.style.position = 'absolute';
                    hyphen.classList.add("fade-in");
                    hyphen.classList.add("hyphen-marker"); // Add a class for easier styling if needed
                    hyphen.style.top = lineHeight * (i - 1) * 100 / paragraphHeight + '%';
                    hyphen.style.left = left * 100 / lineWidth + '%';
                    hyphen.innerHTML = "-";
                    
                    // Ensure hyphen is visible with stronger styling
                    hyphen.style.fontWeight = "normal";
                    hyphen.style.opacity = "1";
                    
                    if (!this.fastForwardingAll) {
                        this.insertAfter(delay, stack[stack.length-1], hyphen);
                        // Log for debugging
                        console.log("Inserted hyphen at line break:", i, "position:", left);
                    }
                    
                    delay += this.animationQueue.getSpeed();
                }
            }
        }
        
        return [p, delay];
    }

    /**
     * Insert an element after a delay
     * @param {number} delay - The delay in milliseconds
     * @param {HTMLElement} target - The target element to append to
     * @param {HTMLElement} el - The element to insert
     * @param {boolean} fadeIn - Whether to fade in the element
     */
    insertAfter(delay, target, el, fadeIn = true) {
        if (fadeIn) {
            el.classList.add("fade-in");
            this.animationQueue.schedule(function() {
                target.appendChild(el);
            }, delay);
        } else {
            this.animationQueue.schedule(function() {
                target.appendChild(el);
            }, delay);
        }
    }

    /**
     * Show an element after a delay
     * @param {number} delay - The delay in milliseconds
     * @param {HTMLElement} el - The element to show
     */
    showAfter(delay, el) {
        el.classList.add("hide");
        setTimeout(function() {
            setTimeout(function() { el.classList.remove("hide") }, delay);
        });
    }

    /**
     * Render a visual tag
     * @param {string} tagType - The type of tag (IMAGE, BACKGROUND, etc.)
     * @param {string} tagValue - The value of the tag
     * @param {HTMLElement} container - The container to append to
     * @param {number} delay - The delay in milliseconds
     * @returns {HTMLElement|null} The created element or null
     */
    renderVisualTag(tagType, tagValue, container, delay = 0) {
        switch (tagType) {
            case "IMAGE":
                const imageElement = document.createElement('img');
                imageElement.src = tagValue;
                container.appendChild(imageElement);
                this.showAfter(delay, imageElement);
                return imageElement;
                
            case "BACKGROUND":
                const outerScrollContainer = document.querySelector('#book');
                outerScrollContainer.style.backgroundImage = 'url(' + tagValue + ')';
                return null;
                
            case "CHAPTER":
                const h = document.createElement('H2');
                h.appendChild(document.createTextNode(tagValue));
                h.classList.add("chapter-heading");
                h.classList.add("fade-in");
                container.appendChild(h);
                return h;
                
            case "SEPARATOR":
                const d = document.createElement('double');
                d.appendChild(document.createTextNode('\u2766'));
                d.classList.add("fade-in");
                d.classList.add("separator");
                container.appendChild(d);
                return d;
                
            default:
                return null;
        }
    }

    /**
     * Set the fast forwarding state
     * @param {boolean} state - The fast forwarding state
     */
    setFastForwardingAll(state) {
        this.fastForwardingAll = state;
    }

    /**
     * Get the fast forwarding state
     * @returns {boolean} The fast forwarding state
     */
    getFastForwardingAll() {
        return this.fastForwardingAll;
    }

    /**
     * Smooth scroll to an element
     * @param {HTMLElement} target - The target element to scroll to
     * @param {number} duration - The duration of the scroll animation
     */
    smoothScroll(target, duration) {
        const display = document.getElementById('page_right');
        const targetPosition = target.getBoundingClientRect().top;
        const startPosition = display.scrollTop;
        const distance = targetPosition;
        let startTime = null;
        
        if (duration < 5) {
            display.scrollTo(0, targetPosition);
            return;
        }
        
        function animation(currentTime) {
            if (startTime === null) startTime = currentTime;
            const timeElapsed = currentTime - startTime;
            const run = ease(timeElapsed, startPosition, distance, duration);
            display.scrollTo(0, run);
            if (timeElapsed < duration) requestAnimationFrame(animation);
        }
        
        function ease(t, b, c, d) {
            t /= d / 2;
            if (t < 1) return c / 2 * t * t + b;
            t--;
            return -c / 2 * (t * (t - 2) - 1) + b;
        }
        
        requestAnimationFrame(animation);
    }
}
