import { BaseModule } from './base-module.js';
import { moduleRegistry } from './module-registry.js';
import { ModuleEvent } from './base-module.js';

class UIDisplayHandler extends BaseModule {
    constructor() {
        super('ui-display-handler');
        
        // Dependencies
        this.dependencies = ['animation-queue', 'tts', 'text-processor', 'paragraph-layout'];
        
        // Display state
        this.container = null;
        this.textBuffer = [];
        this.currentAnimation = null;
        this.textElements = [];
        this.maxParagraphs = 5; // Number of paragraphs to keep in view
        
        // Required module references
        this.animationQueue = null;
        this.tts = null;
        this.textProcessor = null;
        this.paragraphLayout = null;
        
        // Formatting settings
        this.formatting = {
            fontSize: '1.1rem',
            lineHeight: '1.5',
            paragraphSpacing: '1.2rem'
        };
        
        // Resources to preload
        this.cssPath = '/css/style.css';
        this.imagesToPreload = [
            '/images/book-3057904.png',
            '/images/brown-wooden-flooring.jpg'
        ];
        
        // Bind methods used as event handlers or passed as callbacks
        this.handleAnimationEnd = this.handleAnimationEnd.bind(this);
        this.displayText = this.displayText.bind(this);
        this.measureText = this.measureText.bind(this);
        this.typesetParagraph = this.typesetParagraph.bind(this);
        
        // Store a bound version of dispatchEvent for use in methods
        this._dispatchModuleEvent = (name, detail) => {
            document.dispatchEvent(new CustomEvent(name, {
                detail: { moduleId: this.id, ...detail },
                bubbles: true
            }));
        };
        
        // Add flag to track if we're currently animating text
        this.isAnimating = false;
        
        console.log('UIDisplayHandler: Constructor initialized');
    }

    /**
     * Load dependencies and resources
     * @returns {Promise<boolean>} - Resolves when dependencies are loaded
     */
    async loadDependencies() {
        try {
            this.reportProgress(10, "Loading CSS stylesheets");
            
            // Load CSS file
            await this.loadCSS(this.cssPath);
            this.reportProgress(30, "CSS loaded successfully");
            
            // Preload images
            this.reportProgress(40, "Preloading UI images");
            await this.preloadImages(this.imagesToPreload);
            this.reportProgress(80, "UI images preloaded");
            
            return true;
        } catch (error) {
            console.error("Error loading UI display resources:", error);
            return false;
        }
    }
    
    /**
     * Load CSS file asynchronously and wait for it to be applied
     * @param {string} cssPath - Path to CSS file
     * @returns {Promise<void>}
     */
    loadCSS(cssPath) {
        return new Promise((resolve, reject) => {
            // Check if the stylesheet is already loaded
            const existingLinks = document.querySelectorAll('link[rel="stylesheet"]');
            for (const link of existingLinks) {
                if (link.href.includes(cssPath)) {
                    console.log(`UIDisplayHandler: CSS ${cssPath} already loaded`);
                    resolve();
                    return;
                }
            }
            
            // Create link element
            const link = document.createElement('link');
            link.rel = 'stylesheet';
            link.href = cssPath;
            
            // Set up load and error handlers
            link.onload = () => {
                console.log(`UIDisplayHandler: CSS ${cssPath} loaded successfully`);
                
                // Give a small delay for the CSS to be applied
                setTimeout(() => {
                    resolve();
                }, 50);
            };
            
            link.onerror = () => {
                const error = new Error(`Failed to load CSS: ${cssPath}`);
                console.error(error);
                reject(error);
            };
            
            // Append to document head
            document.head.appendChild(link);
        });
    }
    
    /**
     * Preload images
     * @param {Array<string>} imagePaths - Array of image paths to preload
     * @returns {Promise<void>}
     */
    preloadImages(imagePaths) {
        return new Promise((resolve) => {
            if (!imagePaths || imagePaths.length === 0) {
                resolve();
                return;
            }
            
            let loaded = 0;
            const totalImages = imagePaths.length;
            
            const checkAllLoaded = () => {
                loaded++;
                
                // Update progress proportionally
                const percent = Math.round((loaded / totalImages) * 100);
                this.reportProgress(40 + percent * 0.4, `Preloaded ${loaded}/${totalImages} images`);
                
                if (loaded === totalImages) {
                    resolve();
                }
            };
            
            // Preload each image
            imagePaths.forEach(path => {
                const img = new Image();
                img.onload = checkAllLoaded;
                img.onerror = () => {
                    console.warn(`UIDisplayHandler: Failed to preload image: ${path}`);
                    checkAllLoaded();
                };
                img.src = path;
            });
        });
    }

    async initialize() {
        this.reportProgress(0, 'Initializing UI Display Handler');
        
        try {
            this.reportProgress(20, 'Setting up display container');
            
            // Create book structure first
            this.setupBookStructure();
            
            // Create or get the text display container
            this.container = document.getElementById('story') || this.createDisplayContainer();
            
            this.reportProgress(40, 'Configuring display settings');
            
            // Apply initial formatting
            this.applyFormatting();
            
            this.reportProgress(60, 'Setting up animation and text processing dependencies');
            
            // Get references to required modules
            this.animationQueue = moduleRegistry.getModule('animation-queue');
            this.tts = moduleRegistry.getModule('tts');
            this.textProcessor = moduleRegistry.getModule('text-processor');
            this.paragraphLayout = moduleRegistry.getModule('paragraph-layout');
            
            // Set up our text measuring function for the paragraph layout
            if (this.paragraphLayout) {
                this.paragraphLayout.setMeasureFunction(this.measureText);
            }
            
            // Check if we have all required modules
            if (!this.animationQueue) {
                console.error('UIDisplayHandler: animation-queue module not found');
                return false;
            }
            
            if (!this.textProcessor) {
                console.warn('UIDisplayHandler: text-processor module not found, text will not be formatted properly');
            }
            
            if (!this.paragraphLayout) {
                console.warn('UIDisplayHandler: paragraph-layout module not found, text will not be justified properly');
            }
            
            // Set up event listeners for animation sync
            this.setupEventListeners();
            
            this.reportProgress(100, 'UI Display Handler ready');
            
            // Notify that display handler is ready
            this._dispatchModuleEvent('ui:display:ready', {});
            
            return true;
        } catch (error) {
            console.error('Error initializing UI Display Handler:', error);
            this.changeState('ERROR');
            return false;
        }
    }
    
    setupBookStructure() {
        // Create book structure based on reference HTML
        const book = document.getElementById('book') || this.createBookElement();
        
        // Create page_left if it doesn't exist
        const pageLeft = document.getElementById('page_left') || 
            this.createElement('div', 'page_left', book);
        
        // Create page_right if it doesn't exist
        const pageRight = document.getElementById('page_right') ||
            this.createElement('div', 'page_right', book);
            
        // Create header in page_left if needed
        let header = pageLeft.querySelector('.header');
        if (!header) {
            header = this.createElement('div', null, pageLeft, 'header');
            
            // Add header content
            const byline = this.createElement('h2', null, header, 'byline l10n-by');
            byline.textContent = 'powered by Generative AI';
            
            const title = this.createElement('h1', null, header, 'title');
            title.textContent = 'AI Interactive Fiction';
            
            const subtitle = this.createElement('h3', null, header, 'subtitle');
            subtitle.textContent = 'An open-world text adventure';
            
            const separator = this.createElement('div', null, header, 'separator');
            const double = this.createElement('double', null, separator);
            double.textContent = '❦';
        }
        
        // Create controls if needed
        if (!document.getElementById('controls')) {
            const controls = this.createElement('div', 'controls', pageLeft, 'buttons');
            
            // Add speech toggle
            const speechLink = this.createElement('a', 'speech', controls, 'l10n-speech');
            speechLink.title = 'Toggle text to speech';
            speechLink.disabled = 'disabled';
            speechLink.textContent = 'speech';
            
            // Add speed control
            const speedSpan = this.createElement('span', null, controls);
            const speedReset = this.createElement('a', 'speed_reset', speedSpan);
            const speedSpanInner = this.createElement('span', null, speedReset, 'l10n-speed');
            speedSpanInner.innerHTML = 'speed<sup>*</sup>';
            
            const speedInput = document.createElement('input');
            speedInput.type = 'range';
            speedInput.min = '0';
            speedInput.max = '100';
            speedInput.value = '50';
            speedInput.id = 'speed';
            speedInput.name = 'speed';
            speedSpan.appendChild(speedInput);
            
            // Add restart button
            const restartLink = this.createElement('a', 'rewind', controls, 'l10n-restart');
            restartLink.title = 'Restart story from beginning';
            restartLink.disabled = 'disabled';
            restartLink.textContent = 'restart';
            
            // Add save button
            const saveLink = this.createElement('a', 'save', controls, 'l10n-save');
            saveLink.title = 'Save progress';
            saveLink.textContent = 'save';
            
            // Add load button
            const loadLink = this.createElement('a', 'reload', controls, 'l10n-load');
            loadLink.title = 'Reload from save point';
            loadLink.disabled = 'disabled';
            loadLink.textContent = 'load';
        }
        
        // Create remark section if needed
        if (!document.getElementById('remark')) {
            const remark = this.createElement('div', 'remark', pageLeft, 'l10n-remark');
            remark.innerHTML = '<i><sup>*</sup>click on page or press spacebar to fast forward text animation</i>';
        }
        
        // Create story container in page_right if needed
        if (!document.getElementById('story')) {
            const story = this.createElement('div', 'story', pageRight, 'container');
        }
        
        // Create lighting element if needed
        if (!document.getElementById('lighting')) {
            const lighting = this.createElement('div', 'lighting', document.body);
        }
        
        // Create ruler and indent elements if needed
        if (!document.getElementById('ruler')) {
            this.createElement('div', 'ruler', document.body);
        }
        
        if (!document.getElementById('indent')) {
            const indent = this.createElement('div', 'indent', document.body, 'l10n-prompt');
            indent.textContent = 'What do you want to do next?';
        }
    }
    
    createBookElement() {
        const book = this.createElement('div', 'book', document.body);
        return book;
    }
    
    createElement(tagName, id, parent, className) {
        const element = document.createElement(tagName);
        if (id) element.id = id;
        if (className) element.className = className;
        if (parent) parent.appendChild(element);
        return element;
    }
    
    createDisplayContainer() {
        const storyContainer = document.getElementById('story');
        if (storyContainer) return storyContainer;
        
        // If not found, create necessary structure
        const book = document.getElementById('book') || this.createBookElement();
        const pageRight = document.getElementById('page_right') || 
            this.createElement('div', 'page_right', book);
            
        // Create story container
        return this.createElement('div', 'story', pageRight, 'container');
    }
    
    setupEventListeners() {
        // Use the bound method directly as the listener
        document.addEventListener('animationend', this.handleAnimationEnd);
    }
    
    handleAnimationEnd(event) {
        // Check if the event target is a story paragraph before proceeding
        if (!event.target.classList.contains('story-paragraph')) {
            return; 
        }
        
        const paragraph = event.target;
        paragraph.classList.remove('fade-in');
        
        // Notify that text display is complete
        this._dispatchModuleEvent('ui:text:complete', {});
    }
    
    applyFormatting() {
        if (this.container) {
            this.container.style.fontSize = this.formatting.fontSize;
            this.container.style.lineHeight = this.formatting.lineHeight;
        }
    }
    
    /**
     * Measure text width for paragraph layout
     * @param {string} text - Text to measure
     * @param {string} [style] - Optional CSS style
     * @returns {number} - Text width in pixels
     */
    measureText(text, style = '') {
        // Create a temporary span for text measurement
        const ruler = document.getElementById('ruler') || this.createRuler();
        
        // Apply any custom style if provided
        if (style) {
            ruler.style.cssText = style;
        }
        
        // Set text and measure
        ruler.textContent = text;
        return ruler.offsetWidth;
    }
    
    /**
     * Create a ruler element for text measurement
     * @returns {HTMLElement} - The ruler element
     */
    createRuler() {
        const ruler = document.createElement('div');
        ruler.id = 'ruler';
        ruler.style.position = 'absolute';
        ruler.style.visibility = 'hidden';
        ruler.style.whiteSpace = 'nowrap';
        ruler.style.font = window.getComputedStyle(this.container || document.body).font;
        document.body.appendChild(ruler);
        return ruler;
    }
    
    /**
     * Typeset a paragraph based on calculated line breaks
     * @param {Object} paragraphData - Line breaking data from ParagraphLayout
     * @param {number} delay - Initial delay for animation
     * @param {Array} measures - Line width measurements
     * @returns {Array} - [Paragraph element, final delay]
     */
    typesetParagraph(paragraphData, delay = 0, measures = []) {
        // Create paragraph element
        const p = document.createElement('p');
        p.className = 'story-paragraph';
        
        // Set up initial styling
        p.style.position = 'relative';
        p.style.width = '100%';
        
        let lineHeight = parseInt(this.formatting.lineHeight) || 1.5;
        let lineTop = 0;
        
        // Iterate through lines from paragraph_data.breaks
        for(let i = 1; i < paragraphData.breaks.length; i++) {
            // Get the current line (from the previous break position to the current break position)
            let lineStart = paragraphData.breaks[i-1].position;
            let lineEnd = paragraphData.breaks[i].position;
            
            // Process each node (word, space, tag) within the line
            for(let j = lineStart; j <= lineEnd; j++) {
                const node = paragraphData.nodes[j];
                
                if (!node || !node.type) continue; // Skip invalid nodes
                
                // Handle different node types
                if (node.type === 'box' || node.type === 'tag') {
                    // Create span for word or tag
                    const span = document.createElement('span');
                    span.style.position = 'absolute';
                    span.style.left = `${node.left || 0}px`;
                    span.style.top = `${lineTop}px`;
                    span.style.opacity = '0'; // Start invisible for fade-in
                    
                    // Set content based on node type
                    if (node.type === 'box') {
                        span.textContent = node.value;
                    } else if (node.type === 'tag') {
                        // Handle HTML tags (e.g., <b>, <i>, etc.)
                        span.innerHTML = node.value;
                    }
                    
                    // Add to paragraph
                    p.appendChild(span);
                    
                    // Schedule animation using AnimationQueue
                    if (this.animationQueue) {
                        const wordLength = node.value ? node.value.length : 1;
                        this.animationQueue.schedule(() => {
                            span.style.opacity = '1'; // Fade in
                            span.classList.add('animated');
                        }, delay);
                        
                        // Calculate delay for next element based on word length
                        delay += (wordLength * 50); // Adjust timing as needed
                    } else {
                        // Without animation queue, make visible immediately
                        span.style.opacity = '1';
                    }
                }
                // Glue (spaces) don't need visible elements
            }
            
            // Update line top position for next line
            lineTop += lineHeight * 16; // Assuming 1em = 16px, adjust based on font size
        }
        
        // Set paragraph height based on final line position
        p.style.height = `${lineTop + lineHeight}px`;
        
        return [p, delay];
    }
    
    /**
     * Display text with formatting, animation, and optional TTS
     * @param {string} text - Text to display
     * @param {Object} options - Display options
     * @returns {Promise} - Resolves when text display is complete
     */
    async displayText(text, options = {}) {
        if (!this.container || !text) return false;
        
        console.log(`UIDisplayHandler: Processing text for display: "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
        
        // Set animating flag
        this.isAnimating = true;
        
        // Process text
        let processedText = text;
        if (this.textProcessor) {
            try {
                processedText = this.textProcessor.process(text, true);
                console.log('UIDisplayHandler: Text processed with typography enhancements');
            } catch (error) {
                console.error('Error processing text:', error);
                // Continue with unprocessed text
            }
        }
        
        // Create a simple paragraph to display the text
        const paragraph = document.createElement('p');
        paragraph.className = 'story-paragraph fade-in';
        paragraph.textContent = processedText;
        
        // Apply any custom styling from options
        if (options.style && paragraph) {
            Object.assign(paragraph.style, options.style);
        }
        
        // Add to DOM
        this.container.appendChild(paragraph);
        this.textElements.push(paragraph);
        
        // Limit the number of paragraphs
        this.limitParagraphs();
        
        // Scroll to the new paragraph
        this.scrollToBottom();
        
        // If TTS is available and enabled, speak the text
        if (this.tts) {
            console.log('UIDisplayHandler: Starting TTS playback');
            this.tts.speak(text);
        }
        
        // Return a promise that resolves when animation is complete
        return new Promise(resolve => {
            // Use a simple timeout for animation completion
            setTimeout(() => {
                console.log('UIDisplayHandler: Text animation complete');
                this.isAnimating = false;
                
                // Dispatch text complete event
                document.dispatchEvent(new CustomEvent('ui:text:complete', {
                    detail: { moduleId: this.id }
                }));
                
                resolve();
            }, 1000); // Default animation time
        });
    }
    
    limitParagraphs() {
        while (this.textElements.length > this.maxParagraphs) {
            const oldestElement = this.textElements.shift();
            if (oldestElement && oldestElement.parentElement) {
                oldestElement.parentElement.removeChild(oldestElement);
            }
        }
    }
    
    scrollToBottom() {
        if (this.container) {
            this.container.scrollTop = this.container.scrollHeight;
        }
    }
    
    clear() {
        if (this.container) {
            this.container.innerHTML = '';
            this.textElements = [];
        }
    }
    
    show() {
        if (this.container) {
            this.container.style.display = 'block';
        }
    }
    
    hide() {
        if (this.container) {
            this.container.style.display = 'none';
        }
    }
    
    processCommand(command) {
        switch (command.action) {
            case 'display':
                this.displayText(command.text, command.options);
                break;
            case 'clear':
                this.clear();
                break;
            default:
                console.warn(`Unknown display command: ${command.action}`);
        }
    }
}

// Create the singleton instance
const uiDisplayHandler = new UIDisplayHandler();

// Register with the module registry
moduleRegistry.register(uiDisplayHandler);

// Export the module
export { uiDisplayHandler as UIDisplayHandler };

// Keep a reference in window for loader system
console.log('UIDisplayHandler: Registering with window');
window.UIDisplayHandler = uiDisplayHandler;
