/**
 * Layout Renderer Module
 * Renders calculated paragraph layouts into the DOM with proper animations
 */
import { BaseModule } from './base-module.js';

class LayoutRendererModule extends BaseModule {
    constructor() {
        super('layout-renderer', 'Layout Renderer');
        
        // Module dependencies
        this.dependencies = [];
        
        // Configuration
        this.updateConfig({
            animation: {
                defaultSpeed: 1.0,
                wordAnimationClass: 'animate-word'
            }
        });
        
        // Bind methods
        this.bindMethods([
            'renderParagraph',
            'initializeContainers',
            'adjustJustification'
        ]);
    }
    
    /**
     * Initialize the module
     * @returns {Promise<boolean>} - Resolves with success status
     */
    async initialize() {
        try {
            this.reportProgress(10, "Initializing Layout Renderer");
            this.reportProgress(100, "Layout Renderer ready");
            return true;
        } catch (error) {
            console.error("Error initializing Layout Renderer:", error);
            return false;
        }
    }
    
    /**
     * Initialize default containers
     */
    initializeContainers() {
        // Check if story container exists
        const storyContainer = document.getElementById('story');
        if (!storyContainer) {
            console.log('Story container not found, creating it');
            const container = document.createElement('div');
            container.id = 'story';
            document.body.appendChild(container);
        }
    }
    
    /**
     * Render a paragraph from layout data (pure DOM creation, no animation)
     * @param {Object} layoutData - Layout data containing breaks, nodes, and measures
     * @param {Object} options - Rendering options
     * @returns {HTMLElement} - The created paragraph element
     */
    renderParagraph(layoutData, options = {}) {
        const { id = `p-${Date.now()}` } = options;
        const { breaks, nodes, measures, fontSize, fontFamily, lineHeightPx } = layoutData;
        
        if (!breaks || !nodes) {
            console.error('LayoutRenderer: Invalid layout data');
            return null;
        }

        // Create paragraph container
        const paragraph = document.createElement('p');
        paragraph.id = id;
        paragraph.className = [
            layoutData.role ? `story-${layoutData.role}` : '',
            layoutData.addTopSpace ? 'story-textblock-start' : '',
            layoutData.dropCap ? 'story-dropcap-paragraph' : ''
        ].filter(Boolean).join(' ');
        paragraph.style.position = 'absolute';
        paragraph.style.margin = '0';
        paragraph.style.left = '0';
        const globalLineStart = Math.max(0, Number(layoutData.lineStart || 0));
        const windowOriginLine = Math.max(0, Number(layoutData.windowOriginLine || 0));
        paragraph.style.top = `${(globalLineStart - windowOriginLine) * Number(lineHeightPx || 0)}px`;
        if (fontSize) paragraph.style.fontSize = fontSize;
        if (fontFamily) paragraph.style.fontFamily = fontFamily;
        // Calculate paragraph height
        const storyElement = document.getElementById('story');
        if (!storyElement) {
            console.error('LayoutRenderer: Story container not found');
            return null;
        }

        const pageWidth = Number(layoutData.pageWidth || storyElement.clientWidth);
        paragraph.style.width = `${pageWidth}px`;
        paragraph.style.maxWidth = '100%';

        if (!Number.isFinite(Number(lineHeightPx)) || Number(lineHeightPx) <= 0) {
            throw new Error('LayoutRenderer: Missing canonical lineHeightPx for story layout.');
        }
        const lineHeight = Number(lineHeightPx);
        const contentTopLines = Math.max(0, Number(layoutData.contentTopLines || 0));
        const maxLineWidth = Array.isArray(measures) && measures.length > 0
            ? Math.max(pageWidth, ...measures)
            : pageWidth;
        // Height should include all lines (breaks.length represents number of lines)
        const numLines = Math.max(1, breaks.length - 1);
        const totalLines = Math.max(1, Number(layoutData.lineCount || (numLines + contentTopLines)));
        paragraph.style.height = `${lineHeight * totalLines}px`;
        paragraph.dataset.heightLines = String(totalLines);
        paragraph.dataset.lineStart = String(globalLineStart);
        paragraph.dataset.lineCount = String(totalLines);

        console.log(`LayoutRenderer: Rendering paragraph ${id} - ${breaks.length} breaks (${numLines} lines), lineHeight: ${lineHeight}px, total height: ${lineHeight * numLines}px`);

        // Debug: log break ratios
        breaks.forEach((brk, idx) => {
            if (idx > 0) {
                console.log(`  Line ${idx - 1}: break position ${brk.position}, ratio ${brk.ratio ? brk.ratio.toFixed(3) : 'undefined'}`);
            }
        });

        // Position words according to layout with proper justification
        let wordCount = 0;
        let lastChild = null;
        let syllable = "";
        const stack = [paragraph];

        if (layoutData.dropCap && layoutData.dropCapText) {
            const dropCap = document.createElement('span');
            dropCap.className = 'drop-cap story-drop-cap';
            dropCap.textContent = layoutData.dropCapText;
            dropCap.style.top = `${contentTopLines * lineHeight}px`;
            paragraph.appendChild(dropCap);
        }

        for (let i = 1; i < breaks.length; i++) {
            const lineIndex = i - 1;
            const lineWidth = measures[Math.min(lineIndex, measures.length - 1)];
            const currentBreak = breaks[i];
            const isFinalLine = i === breaks.length - 1;
            const isCentered = layoutData.align === 'center' ||
                layoutData.role === 'chapter-heading' ||
                layoutData.role === 'section-heading';
            const ratio = (isFinalLine || isCentered) ? 0 : (currentBreak.ratio || 0);
            const naturalLineWidth = isCentered
                ? this.measureNaturalLineWidth(nodes, breaks[i - 1].position, currentBreak.position)
                : lineWidth;
            const lineOffset = isCentered
                ? Math.max(0, (maxLineWidth - naturalLineWidth) / 2)
                : Array.isArray(layoutData.lineOffsets)
                    ? (layoutData.lineOffsets[Math.min(lineIndex, layoutData.lineOffsets.length - 1)] || 0)
                    : maxLineWidth - lineWidth;

            let currentLeft = 0;
            lastChild = null;

            // Iterate through nodes on this line (break positions are inclusive)
            for (let j = breaks[i-1].position; j <= currentBreak.position; j++) {
                const node = nodes[j];

                if (node.type === 'box' && node.value !== '' && j < currentBreak.position) {
                    const followsGlue = j > 0 && nodes[j - 1].type === 'glue';
                    const isTrailingPunctuation = /^[,.;:!?…)]$/.test(node.value) && !followsGlue;

                    // Check if this box follows a penalty (hyphenation point)
                    if (lastChild && isTrailingPunctuation) {
                        syllable += node.value;
                        lastChild.innerHTML = syllable;
                        currentLeft += node.width;
                    } else if (j > breaks[i-1].position + 1 &&
                        nodes[j-1].type === 'penalty' &&
                        lastChild) {
                        // Combine with previous syllable using zero-width non-joiner
                        syllable += '\u200c' + node.value;
                        lastChild.innerHTML = syllable;
                        currentLeft += node.width;
                    } else {
                        // Create new word span
                        const word = document.createElement('span');
                        word.className = 'word';
                        word.style.position = 'absolute';
                        word.style.display = 'inline-block';
                        word.style.whiteSpace = 'nowrap';
                        word.dataset.line = String(lineIndex);
                        word.dataset.lineStart = String(lineOffset);
                        word.dataset.lineWidth = String(lineWidth);

                        // Calculate position with proper line and justification
                        const topPercent = ((contentTopLines + lineIndex) * lineHeight * 100) / parseFloat(paragraph.style.height);
                        const leftPercent = ((lineOffset + currentLeft) * 100) / maxLineWidth;

                        word.style.top = `${topPercent}%`;
                        word.style.left = `${leftPercent}%`;
                        word.style.opacity = '0'; // Hidden until animated
                        word.style.visibility = 'hidden';
                        word.style.clipPath = 'inset(0 100% 0 0)';
                        syllable = node.value;
                        word.innerHTML = syllable;
                        lastChild = word;

                        if (wordCount < 5 || (wordCount % 20 === 0)) {
                            console.log(`  Word ${wordCount} "${node.value}" at line ${lineIndex}, top: ${topPercent.toFixed(1)}%, left: ${leftPercent.toFixed(1)}%`);
                        }
                        wordCount++;

                        stack[stack.length - 1].appendChild(word);
                        currentLeft += node.width;
                    }

                } else if (node.type === 'tag') {
                    if (node.value.substr(0, 2) === '</') {
                        if (stack.length > 1) stack.pop();
                    } else {
                        const template = document.createElement('div');
                        template.innerHTML = node.value;
                        const tag = template.firstChild;
                        if (tag) {
                            tag.style.display = 'contents';
                            stack[stack.length - 1].appendChild(tag);
                            stack.push(tag);
                        }
                    }

                } else if (node.type === 'glue' && j > breaks[i-1].position && node.width !== 0 && j <= currentBreak.position) {
                    // Apply justification: adjust glue width based on line's ratio
                    let adjustedWidth = node.width;

                    if (ratio > 0) {
                        // Line needs stretching
                        adjustedWidth = node.width + (node.stretch * ratio);
                    } else if (ratio < 0) {
                        // Line needs shrinking
                        adjustedWidth = node.width + (node.shrink * ratio);
                    }
                    // If ratio === 0, line fits perfectly, use natural width

                    if (wordCount < 3) {
                        // Debug first line's glue adjustments
                        console.log(`    Glue at position ${j}: natural=${node.width.toFixed(2)}px, adjusted=${adjustedWidth.toFixed(2)}px, ratio=${ratio.toFixed(3)}, left before: ${currentLeft.toFixed(2)}px`);
                    }

                    // Increment position by the adjusted glue width
                    currentLeft += adjustedWidth;

                } else if (node.type === 'penalty' && node.penalty === 100 && j === currentBreak.position) {
                    // Add hyphen at line break
                    if (lastChild) {
                        lastChild.innerHTML = `${lastChild.innerHTML}-`;
                        continue;
                    }

                    const word = document.createElement('span');
                    word.className = 'word';
                    word.style.position = 'absolute';
                    word.style.display = 'inline-block';
                    word.style.whiteSpace = 'nowrap';
                    word.dataset.line = String(lineIndex);
                    word.dataset.lineStart = String(lineOffset);
                    word.dataset.lineWidth = String(lineWidth);
                    const topPercent = ((contentTopLines + lineIndex) * lineHeight * 100) / parseFloat(paragraph.style.height);
                    const leftPercent = ((lineOffset + currentLeft) * 100) / maxLineWidth;
                    word.style.top = `${topPercent}%`;
                    word.style.left = `${leftPercent}%`;
                    word.style.opacity = '0';
                    word.style.visibility = 'hidden';
                    word.style.clipPath = 'inset(0 100% 0 0)';
                    word.innerHTML = "-";
                    stack[stack.length - 1].appendChild(word);
                }
            }
        }

        return paragraph;
    }

    measureNaturalLineWidth(nodes, startPosition, endPosition) {
        let width = 0;
        for (let j = startPosition; j <= endPosition; j++) {
            const node = nodes[j];
            if (!node) continue;
            if (node.type === 'box' || node.type === 'glue') {
                width += node.width || 0;
            } else if (node.type === 'penalty' && node.penalty === 100 && j === endPosition) {
                width += node.width || 0;
            }
        }
        return width;
    }

    /**
     * Paragraph positions are already computed from browser DOM measurements.
     * Keep this hook for callers that still invoke it, but do not reflow the
     * prototype layout after rendering.
     * @param {HTMLElement} paragraph - Rendered paragraph element
     */
    adjustJustification(paragraph) {
        return paragraph;
    }
    
}

// Create the singleton instance
const LayoutRenderer = new LayoutRendererModule();

// Export the module
export { LayoutRenderer };

// Register with the module registry
if (window.moduleRegistry) {
    window.moduleRegistry.register(LayoutRenderer);
}

// Keep a reference in window for loader system
window.LayoutRenderer = LayoutRenderer;
