/**
 * StyleAwareEditor - Rich text editor with style-specific formatting options
 * 
 * Implements the sophisticated style preservation system described in CLASSES.md:
 * - Automatic style detection from nested elements
 * - Formatting toolbar with developer-style-based options
 * - Multi-property editing for complex elements (links, images, etc.)
 * - Perfect attribute preservation during editing
 */

import { styleDetectionEngine } from '../utils/style-detection.js';
import { htmlPreservationEngine } from '../utils/html-preservation.js';

export class StyleAwareEditor {
    constructor(element, options = {}) {
        this.element = element;
        this.options = {
            showToolbar: true,
            enableMultiProperty: true,
            preserveAttributes: true,
            ...options
        };

        // Core system components
        this.styleEngine = styleDetectionEngine;
        this.htmlEngine = htmlPreservationEngine;

        // Editor state
        this.isInitialized = false;
        this.editorContainer = null;
        this.contentEditor = null;
        this.toolbar = null;
        this.detectedStyles = null;
        this.contentStructure = null;
        this.originalContent = null;

        // Event callbacks
        this.onSave = options.onSave || (() => { });
        this.onCancel = options.onCancel || (() => { });
        this.onChange = options.onChange || (() => { });
    }

    /**
     * Initialize the style-aware editor
     * Analyzes element, detects styles, creates appropriate editing interface
     */
    async initialize() {
        if (this.isInitialized) {
            return;
        }

        try {
            // Analyze element for styles and structure
            const analysis = this.analyzeElement();

            // Create editor interface based on analysis
            this.createEditorInterface(analysis);

            // Set up event handlers
            this.setupEventHandlers();

            this.isInitialized = true;

        } catch (error) {
            console.error('Failed to initialize StyleAwareEditor:', error);
            throw error;
        }
    }

    /**
     * Analyze element to determine editing strategy
     * 
     * @returns {Object} - Analysis results with styles, structure, and editing strategy
     */
    analyzeElement() {
        // Extract content with preservation metadata
        this.originalContent = this.htmlEngine.extractForEditing(this.element);

        // Detect styles and structure
        const detection = this.styleEngine.detectStylesAndStructure(this.element);
        this.detectedStyles = detection.styles;
        this.contentStructure = detection.structure;

        // Determine editing strategy based on complexity
        const editingStrategy = this.determineEditingStrategy(detection);

        return {
            styles: detection.styles,
            structure: detection.structure,
            strategy: editingStrategy,
            hasMultiPropertyElements: false // Removed - not needed for HTML-first approach
        };
    }

    /**
     * Determine the best editing interface based on element type
     * HTML-first approach: detect behavior from element tag, not attributes
     * 
     * @param {Object} detection - Style detection results
     * @returns {string} - Editing interface: 'direct' or 'rich'
     */
    determineEditingStrategy(detection) {
        const tagName = this.element.tagName.toLowerCase();

        // Multi-property elements get direct editing interface
        if (this.isMultiPropertyElement(tagName)) {
            return 'direct';
        }

        // All other elements get rich HTML editing with style preservation
        return 'rich';
    }

    /**
     * Check if element is a multi-property element requiring direct editing
     * 
     * @param {string} tagName - Element tag name
     * @returns {boolean} - True if multi-property element
     */
    isMultiPropertyElement(tagName) {
        const multiPropertyTags = new Set(['a', 'button', 'img']);
        return multiPropertyTags.has(tagName);
    }



    /**
     * Create editor interface based on analysis
     * 
     * @param {Object} analysis - Analysis results
     */
    createEditorInterface(analysis) {
        // Create main editor container
        this.editorContainer = document.createElement('div');
        this.editorContainer.className = 'insertr-style-aware-editor';

        // Create appropriate editor based on strategy
        switch (analysis.strategy) {
            case 'direct':
                this.createDirectEditor(analysis);
                break;
            case 'rich':
                this.createRichEditor(analysis);
                break;
        }

        // Add toolbar if enabled and we have any formatting options (rich editor only)
        // This includes both detected styles and smart defaults
        if (this.options.showToolbar && analysis.strategy === 'rich' && analysis.styles.size > 0) {
            this.createStyleToolbar(analysis.styles, analysis.structure);
        }

        // Add form actions
        this.createFormActions();
    }

    /**
     * Create direct property editor for multi-property elements (links, buttons, images)
     */
    createDirectEditor(analysis) {
        const tagName = this.element.tagName.toLowerCase();

        if (tagName === 'a') {
            this.createLinkEditor();
        } else if (tagName === 'button') {
            this.createButtonEditor();
        } else if (tagName === 'img') {
            this.createImageEditor();
        } else {
            // Fallback to rich editor
            this.createRichEditor(analysis);
        }
    }

    /**
     * Create unified link configuration component
     * 
     * @param {Object} options - Configuration options
     * @param {string} options.text - Link text (optional)
     * @param {string} options.url - Link URL (optional)
     * @param {string} options.target - Link target (optional)
     * @param {boolean} options.showText - Whether to show text field
     * @param {string} options.title - Form title
     * @param {Function} options.onSave - Save callback
     * @param {Function} options.onCancel - Cancel callback
     * @param {Function} options.onRemove - Remove callback (optional)
     * @returns {HTMLElement} - Link configuration form
     */
    createLinkConfiguration(options = {}) {
        const {
            text = '',
            url = '',
            target = '',
            showText = true,
            title = 'Configure Link',
            onSave = () => {},
            onCancel = () => {},
            onRemove = null
        } = options;

        // Create form container
        const form = document.createElement('div');
        form.className = 'insertr-direct-editor insertr-link-editor';
        
        // Create title
        const titleElement = document.createElement('h3');
        titleElement.className = 'insertr-editor-title';
        titleElement.textContent = title;
        titleElement.style.cssText = `
            margin: 0 0 ${getComputedStyle(document.documentElement).getPropertyValue('--insertr-spacing-md') || '16px'} 0;
            font-size: 18px;
            font-weight: 600;
            color: var(--insertr-text-primary);
        `;
        form.appendChild(titleElement);
        
        // Text field (if needed)
        if (showText) {
            const textGroup = document.createElement('div');
            textGroup.className = 'insertr-form-group';
            textGroup.innerHTML = `
                <label class="insertr-form-label">Link Text</label>
                <input type="text" class="insertr-form-input" id="link-text" value="${this.escapeHtml(text)}" placeholder="Enter link text" required>
                <div class="insertr-form-message insertr-info" style="display: none;" id="link-text-message"></div>
            `;
            form.appendChild(textGroup);
        }
        
        // URL field with validation
        const urlGroup = document.createElement('div');
        urlGroup.className = 'insertr-form-group';
        urlGroup.innerHTML = `
            <label class="insertr-form-label">URL</label>
            <input type="url" class="insertr-form-input" id="link-url" value="${this.escapeHtml(url)}" placeholder="https://example.com" required>
            <div class="insertr-form-message insertr-info" style="display: none;" id="link-url-message"></div>
        `;
        form.appendChild(urlGroup);
        
        // Target field
        const targetGroup = document.createElement('div');
        targetGroup.className = 'insertr-form-group';
        targetGroup.innerHTML = `
            <label class="insertr-form-label">Open In</label>
            <select class="insertr-form-select" id="link-target">
                <option value="">Same window</option>
                <option value="_blank" ${target === '_blank' ? 'selected' : ''}>New window/tab</option>
            </select>
            <div class="insertr-form-message insertr-info">Choose how the link opens when clicked</div>
        `;
        form.appendChild(targetGroup);
        
        // Add real-time validation
        this.addLinkValidation(form);

        // Add save/cancel handlers
        form.addEventListener('submit', (e) => {
            e.preventDefault();
            const formData = this.extractLinkFormData(form);
            if (formData) {
                onSave(formData);
            }
        });

        // Store callbacks for later use
        form._onSave = () => {
            const formData = this.extractLinkFormData(form);
            if (formData) {
                onSave(formData);
            }
        };
        form._onCancel = onCancel;
        form._onRemove = onRemove;
        
        return form;
    }

    /**
     * Extract form data from link configuration form
     */
    extractLinkFormData(form) {
        const textInput = form.querySelector('#link-text');
        const urlInput = form.querySelector('#link-url');
        const targetSelect = form.querySelector('#link-target');

        const text = textInput ? textInput.value.trim() : '';
        const url = urlInput ? urlInput.value.trim() : '';
        const target = targetSelect ? targetSelect.value : '';

        // Validation
        if (textInput && !text) {
            alert('Please enter link text');
            textInput.focus();
            return null;
        }

        if (!url) {
            alert('Please enter a valid URL');
            urlInput.focus();
            return null;
        }

        return { text, url, target };
    }

    /**
     * Create link editor with URL and text fields
     */
    createLinkEditor() {
        const linkConfig = this.createLinkConfiguration({
            text: this.element.textContent,
            url: this.element.href || '',
            target: this.element.target || '',
            showText: true,
            title: 'Edit Link',
            onSave: (data) => {
                // Data will be extracted in extractDirectEditorContent
            }
        });
        
        this.contentEditor = linkConfig;
        this.editorContainer.appendChild(linkConfig);
        
        // Focus the first input
        setTimeout(() => {
            const textInput = linkConfig.querySelector('#link-text');
            if (textInput) {
                textInput.focus();
                textInput.select();
            }
        }, 100);
    }

    /**
     * Create button editor with text field
     */
    createButtonEditor() {
        const form = document.createElement('div');
        form.className = 'insertr-direct-editor insertr-button-editor';
        
        // Create a title for the editor
        const title = document.createElement('h3');
        title.className = 'insertr-editor-title';
        title.textContent = 'Edit Button';
        title.style.cssText = `
            margin: 0 0 ${getComputedStyle(document.documentElement).getPropertyValue('--insertr-spacing-md') || '16px'} 0;
            font-size: 18px;
            font-weight: 600;
            color: var(--insertr-text-primary);
        `;
        form.appendChild(title);
        
        // Text field
        const textGroup = document.createElement('div');
        textGroup.className = 'insertr-form-group';
        textGroup.innerHTML = `
            <label class="insertr-form-label">Button Text</label>
            <input type="text" class="insertr-form-input" id="button-text" value="${this.escapeHtml(this.element.textContent)}" placeholder="Enter button text" required>
            <div class="insertr-form-message insertr-info">This text will appear on the button</div>
        `;
        
        form.appendChild(textGroup);
        
        this.contentEditor = form;
        this.editorContainer.appendChild(form);
        
        // Focus the input
        setTimeout(() => {
            const textInput = form.querySelector('#button-text');
            if (textInput) {
                textInput.focus();
                textInput.select();
            }
        }, 100);
    }

    /**
     * Create image editor with src and alt fields
     */
    createImageEditor() {
        const form = document.createElement('div');
        form.className = 'insertr-direct-editor insertr-image-editor';
        
        // Create a title for the editor
        const title = document.createElement('h3');
        title.className = 'insertr-editor-title';
        title.textContent = 'Edit Image';
        title.style.cssText = `
            margin: 0 0 ${getComputedStyle(document.documentElement).getPropertyValue('--insertr-spacing-md') || '16px'} 0;
            font-size: 18px;
            font-weight: 600;
            color: var(--insertr-text-primary);
        `;
        form.appendChild(title);
        
        // Source field
        const srcGroup = document.createElement('div');
        srcGroup.className = 'insertr-form-group';
        srcGroup.innerHTML = `
            <label class="insertr-form-label">Image URL</label>
            <input type="url" class="insertr-form-input" id="image-src" value="${this.escapeHtml(this.element.src || '')}" placeholder="https://example.com/image.jpg" required>
            <div class="insertr-form-message insertr-info">Enter the full URL to the image file</div>
        `;
        
        // Alt text field
        const altGroup = document.createElement('div');
        altGroup.className = 'insertr-form-group';
        altGroup.innerHTML = `
            <label class="insertr-form-label">Alt Text</label>
            <input type="text" class="insertr-form-input" id="image-alt" value="${this.escapeHtml(this.element.alt || '')}" placeholder="Describe this image for accessibility" required>
            <div class="insertr-form-message insertr-info">Describe the image for screen readers and accessibility</div>
        `;
        
        form.appendChild(srcGroup);
        form.appendChild(altGroup);
        
        // Add image preview if src exists
        if (this.element.src) {
            const previewGroup = document.createElement('div');
            previewGroup.className = 'insertr-form-group';
            previewGroup.innerHTML = `
                <label class="insertr-form-label">Current Image</label>
                <img src="${this.escapeHtml(this.element.src)}" alt="${this.escapeHtml(this.element.alt || '')}" style="max-width: 200px; max-height: 150px; border: 1px solid var(--insertr-border-color); border-radius: var(--insertr-border-radius);">
            `;
            form.appendChild(previewGroup);
        }
        
        this.contentEditor = form;
        this.editorContainer.appendChild(form);
        
        // Focus the first input
        setTimeout(() => {
            const srcInput = form.querySelector('#image-src');
            if (srcInput) {
                srcInput.focus();
                srcInput.select();
            }
        }, 100);
    }

    /**
     * Create rich text editor with style preservation
     * 
     * @param {Object} analysis - Analysis results
     */
    createRichEditor(analysis) {
        // Create contentEditable div
        const editor = document.createElement('div');
        editor.className = 'insertr-rich-editor';
        editor.contentEditable = true;
        editor.innerHTML = this.originalContent.html;

        this.contentEditor = editor;
        this.editorContainer.appendChild(editor);
    }

    /**
     * Create formatting toolbar with detected style buttons and link button
     * 
     * @param {Map} styles - Detected styles
     * @param {Array} structure - Content structure (to detect links)
     */
    createStyleToolbar(styles, structure = []) {
        const toolbar = document.createElement('div');
        toolbar.className = 'insertr-style-toolbar';

        const title = document.createElement('span');
        title.textContent = 'Formatting:';
        title.className = 'insertr-toolbar-title';
        toolbar.appendChild(title);

        // Store references to style buttons for state updates
        this.styleButtons = new Map();

        // Add button for each detected style (except links - those use the link popup)
        for (const [styleId, styleInfo] of styles) {
            // Skip link styles - they should be handled by the link popup, not toolbar buttons
            if (styleInfo.tagName.toLowerCase() === 'a') {
                continue;
            }

            const button = this.createStyleButton(styleId, styleInfo);
            toolbar.appendChild(button);
            this.styleButtons.set(styleId, button);
        }

        // Add link button if we have links in content or always for rich editor
        const hasLinks = structure.some(piece =>
            piece.type === 'styled' && piece.element && piece.element.tagName.toLowerCase() === 'a'
        );

        if (hasLinks || styles.size > 0) {
            const linkButton = this.createLinkButton();
            toolbar.appendChild(linkButton);
        }

        this.toolbar = toolbar;
        this.editorContainer.insertBefore(toolbar, this.contentEditor);

        // Set up selection change listener to update button states
        this.setupSelectionChangeListener();
    }

    /**
     * Set up listener for selection changes to update button states
     */
    setupSelectionChangeListener() {
        if (!this.contentEditor || !this.styleButtons) {
            return;
        }

        // Listen for selection changes
        const updateButtonStates = () => {
            this.updateStyleButtonStates();
        };

        // Multiple events can trigger selection changes
        document.addEventListener('selectionchange', updateButtonStates);
        this.contentEditor.addEventListener('keyup', updateButtonStates);
        this.contentEditor.addEventListener('mouseup', updateButtonStates);
        this.contentEditor.addEventListener('input', updateButtonStates);

        // Store cleanup function
        this._selectionChangeCleanup = () => {
            document.removeEventListener('selectionchange', updateButtonStates);
            this.contentEditor.removeEventListener('keyup', updateButtonStates);
            this.contentEditor.removeEventListener('mouseup', updateButtonStates);
            this.contentEditor.removeEventListener('input', updateButtonStates);
        };
    }

    /**
     * Update style button states based on current selection
     */
    updateStyleButtonStates() {
        if (!this.styleButtons || !this.detectedStyles) {
            return;
        }

        const selection = window.getSelection();
        if (selection.rangeCount === 0) {
            // No selection - reset all buttons to normal state
            this.styleButtons.forEach(button => {
                button.classList.remove('insertr-style-active');
            });
            return;
        }

        const range = selection.getRangeAt(0);

        // Check each style button
        for (const [styleId, button] of this.styleButtons) {
            const styleInfo = this.detectedStyles.get(styleId);
            if (!styleInfo) continue;

            const analysis = this.analyzeSelectionFormatting(range, styleInfo);
            
            // Update button state based on analysis
            if (analysis.coverage > 0.5) {
                button.classList.add('insertr-style-active');
                button.title = `Remove ${styleInfo.name} formatting`;
            } else {
                button.classList.remove('insertr-style-active');
                button.title = `Apply ${styleInfo.name} formatting`;
            }
        }
    }

    /**
     * Create unified formatting button
     * 
     * @param {Object} config - Button configuration
     * @param {string} config.type - Button type: 'style', 'link', 'action'
     * @param {string} config.title - Button title/tooltip
     * @param {string} config.text - Button preview text
     * @param {Function} config.onClick - Click handler
     * @param {Object} config.styleInfo - Style information (for style buttons)
     * @param {string} config.styleId - Style ID (for style buttons)
     * @returns {HTMLElement} - Formatted button
     */
    createFormattingButton(config) {
        const {
            type = 'action',
            title = '',
            text = '',
            onClick = () => {},
            styleInfo = null,
            styleId = null
        } = config;

        // Create button element
        const button = document.createElement('button');
        button.type = 'button';
        button.className = 'insertr-style-btn';
        button.title = title;

        if (styleId) {
            button.dataset.styleId = styleId;
        }

        // Create three-layer structure for style isolation
        const buttonFrame = document.createElement('span');
        buttonFrame.className = 'insertr-button-frame';
        
        const styleSample = document.createElement('span');
        styleSample.className = 'insertr-style-sample';
        styleSample.textContent = text;

        // Apply type-specific styling
        switch (type) {
            case 'style':
                this.applyStyleButtonStyling(button, styleSample, styleInfo);
                break;
            case 'link':
                this.applyLinkButtonStyling(button, styleSample);
                break;
            case 'action':
            default:
                // Default action button styling - add fallback class
                styleSample.classList.add('insertr-fallback-style');
                break;
        }

        // Build three-layer structure: button → frame → sample
        buttonFrame.appendChild(styleSample);
        button.appendChild(buttonFrame);

        // Add click handler
        button.addEventListener('click', (e) => {
            e.preventDefault();
            onClick(e);
        });

        return button;
    }

    /**
     * Apply styling for style buttons
     */
    applyStyleButtonStyling(button, styleSample, styleInfo) {
        if (!styleInfo) return;

        // ALL buttons use the same unified three-layer architecture
        button.classList.add('insertr-style-preview');

        // Apply appropriate preview styling to the isolated sample
        if (styleInfo.isDefault) {
            // Default semantic formatting - apply styling to sample
            if (styleInfo.tagName === 'strong') {
                styleSample.style.fontWeight = 'bold';
            } else if (styleInfo.tagName === 'em') {
                styleSample.style.fontStyle = 'italic';
            } else if (styleInfo.tagName === 'a') {
                styleSample.style.textDecoration = 'underline';
                styleSample.style.color = '#0066cc';
            }
            
            // Add helpful description to title
            button.title = `${styleInfo.name}: ${styleInfo.description || 'Default formatting option'}`;
        } else if (styleInfo.element && styleInfo.classes && styleInfo.classes.length > 0) {
            // Detected style - apply original classes to style sample (isolated preview)
            styleInfo.classes.forEach(className => {
                styleSample.classList.add(className);
            });
            
            button.title = `Apply ${styleInfo.classes.join(' ')} styling`;
        } else {
            // No meaningful styles detected - use fallback
            styleSample.classList.add('insertr-fallback-style');
        }
    }

    /**
     * Apply styling for link buttons
     */
    applyLinkButtonStyling(button, styleSample) {
        // Add link-specific styling to the isolated sample
        styleSample.style.textDecoration = 'underline';
        styleSample.style.color = '#0066cc';
        button.title = 'Create hyperlink';
    }

    /**
     * Create button for applying detected style
     * 
     * @param {string} styleId - Style identifier
     * @param {Object} styleInfo - Style information
     * @returns {HTMLElement} - Style button
     */
    createStyleButton(styleId, styleInfo) {
        return this.createFormattingButton({
            type: 'style',
            title: `Apply ${styleInfo.name} style`,
            text: styleInfo.name,
            onClick: () => this.applyStyle(styleId),
            styleInfo: styleInfo,
            styleId: styleId
        });
    }

    /**
     * Create link button for opening link configuration popup
     * 
     * @returns {HTMLElement} - Link button
     */
    createLinkButton() {
        return this.createFormattingButton({
            type: 'link',
            title: 'Add/Edit Link',
            text: '🔗 Link',
            onClick: () => this.openLinkPopup()
        });
    }

    /**
     * Create form action buttons (Save, Cancel)
     */
    createFormActions() {
        const actions = document.createElement('div');
        actions.className = 'insertr-form-actions';

        const saveBtn = document.createElement('button');
        saveBtn.type = 'button';
        saveBtn.className = 'insertr-btn-save';
        saveBtn.textContent = 'Save';

        const cancelBtn = document.createElement('button');
        cancelBtn.type = 'button';
        cancelBtn.className = 'insertr-btn-cancel';
        cancelBtn.textContent = 'Cancel';

        actions.appendChild(saveBtn);
        actions.appendChild(cancelBtn);

        this.editorContainer.appendChild(actions);
    }

    /**
     * Set up event handlers for editor interaction
     */
    setupEventHandlers() {
        // Save button
        const saveBtn = this.editorContainer.querySelector('.insertr-btn-save');
        saveBtn?.addEventListener('click', () => this.handleSave());

        // Cancel button
        const cancelBtn = this.editorContainer.querySelector('.insertr-btn-cancel');
        cancelBtn?.addEventListener('click', () => this.handleCancel());

        // Content change events
        if (this.contentEditor) {
            this.contentEditor.addEventListener('input', () => this.handleChange());
        }

        // Multi-property form changes
        const propertyFields = this.editorContainer.querySelectorAll('.insertr-property-field');
        propertyFields.forEach(field => {
            field.addEventListener('input', () => this.handleChange());
        });
    }

    /**
     * Apply detected style to selected text or current position
     * 
     * @param {string} styleId - Style to apply
     */
    applyStyle(styleId) {
        const styleInfo = this.detectedStyles.get(styleId);
        if (!styleInfo) {
            return;
        }

        if (this.contentEditor.contentEditable === 'true') {
            // Rich text editor - apply style to selection
            this.applyStyleToSelection(styleInfo);
        } else {
            // Other editor types - could expand functionality here
            console.log('Style application for this editor type not yet implemented');
        }
    }

    /**
     * Apply style to current selection with intelligent toggle and merging
     * 
     * @param {Object} styleInfo - Style information
     */
    applyStyleToSelection(styleInfo) {
        const selection = window.getSelection();
        if (selection.rangeCount === 0) {
            return;
        }

        const range = selection.getRangeAt(0);
        
        // Expand selection to include adjacent whitespace for better word-level operations
        const expandedRange = this.expandRangeToIncludeWhitespace(range);
        
        // Check if we should toggle or apply the style
        const selectionAnalysis = this.analyzeSelectionFormatting(expandedRange, styleInfo);
        
        if (selectionAnalysis.shouldToggle) {
            this.removeFormattingFromSelection(expandedRange, styleInfo);
        } else {
            this.applyFormattingToSelection(expandedRange, styleInfo, selectionAnalysis);
        }

        // Clear selection and trigger change
        selection.removeAllRanges();
        this.handleChange();
    }

    /**
     * Expand range to include adjacent whitespace for better word-level formatting
     * This prevents orphaned spaces when formatting partial words
     * 
     * @param {Range} range - Original selection range
     * @returns {Range} Expanded range including adjacent whitespace
     */
    expandRangeToIncludeWhitespace(range) {
        const expandedRange = range.cloneRange();
        
        try {
            // Check if we should expand the start
            const startContainer = range.startContainer;
            if (startContainer.nodeType === Node.TEXT_NODE) {
                const text = startContainer.textContent;
                const startOffset = range.startOffset;
                
                // Look backwards for whitespace to include
                let newStartOffset = startOffset;
                while (newStartOffset > 0 && /\s/.test(text[newStartOffset - 1])) {
                    newStartOffset--;
                }
                
                // Only expand if we found whitespace and we're at a word boundary
                if (newStartOffset < startOffset && this.isAtWordBoundary(text, startOffset)) {
                    expandedRange.setStart(startContainer, newStartOffset);
                }
            }
            
            // Check if we should expand the end
            const endContainer = range.endContainer;
            if (endContainer.nodeType === Node.TEXT_NODE) {
                const text = endContainer.textContent;
                const endOffset = range.endOffset;
                
                // Look forwards for whitespace to include
                let newEndOffset = endOffset;
                while (newEndOffset < text.length && /\s/.test(text[newEndOffset])) {
                    newEndOffset++;
                }
                
                // Only expand if we found whitespace and we're at a word boundary
                if (newEndOffset > endOffset && this.isAtWordBoundary(text, endOffset)) {
                    expandedRange.setEnd(endContainer, newEndOffset);
                }
            }
            
        } catch (e) {
            // If expansion fails, return original range
            console.warn('Failed to expand range for whitespace:', e);
            return range;
        }
        
        return expandedRange;
    }

    /**
     * Check if a position in text is at a word boundary
     * 
     * @param {string} text - Text content
     * @param {number} offset - Position to check
     * @returns {boolean} True if at word boundary
     */
    isAtWordBoundary(text, offset) {
        const before = offset > 0 ? text[offset - 1] : '';
        const at = offset < text.length ? text[offset] : '';
        
        // Word boundary if transitioning between word character and non-word character
        const beforeIsWord = /\w/.test(before);
        const atIsWord = /\w/.test(at);
        
        return beforeIsWord !== atIsWord;
    }

    /**
     * Analyze selection to determine current formatting state
     * 
     * @param {Range} range - Selection range
     * @param {Object} styleInfo - Style information
     * @returns {Object} Analysis results
     */
    analyzeSelectionFormatting(range, styleInfo) {
        // Guard against invalid inputs
        if (!range || !styleInfo || !styleInfo.tagName) {
            return {
                shouldToggle: false,
                coverage: 0,
                existingElements: [],
                canMerge: false
            };
        }

        const targetTag = styleInfo.tagName.toLowerCase();
        const targetClasses = styleInfo.classes || [];
        
        // Get all nodes in selection
        const selectedNodes = this.getNodesInRange(range);
        
        let formattedNodes = 0;
        let totalTextNodes = 0;
        let existingElements = [];
        
        // Check if selection has formatted content by examining parent elements
        let hasFormattedContent = false;
        let formattedElements = [];
        
        // Check start and end containers for formatting
        const containers = [range.startContainer, range.endContainer];
        containers.forEach(container => {
            let parent = container.nodeType === Node.TEXT_NODE ? container.parentElement : container;
            while (parent && parent !== this.contentEditor) {
                if (this.elementMatchesStyle(parent, targetTag, targetClasses)) {
                    hasFormattedContent = true;
                    if (!formattedElements.includes(parent)) {
                        formattedElements.push(parent);
                    }
                    break; // Found formatting, no need to go higher
                }
                parent = parent.parentElement;
            }
        });
        
        // For partial selections within formatted elements, we should toggle off
        // For selections that span unformatted content, we should toggle on
        let shouldToggle = false;
        
        if (hasFormattedContent) {
            // Check if the entire selection is within formatted elements
            const allElementsFormatted = formattedElements.every(element => {
                try {
                    const elementRange = document.createRange();
                    elementRange.selectNodeContents(element);
                    
                    // Check if our selection is completely within this element
                    return range.compareBoundaryPoints(Range.START_TO_START, elementRange) >= 0 &&
                           range.compareBoundaryPoints(Range.END_TO_END, elementRange) <= 0;
                } catch (e) {
                    return false;
                }
            });
            
            shouldToggle = allElementsFormatted;
        }
        
        existingElements = formattedElements;
        
        return {
            shouldToggle: shouldToggle,
            coverage: hasFormattedContent ? 1 : 0,
            existingElements: existingElements,
            canMerge: !hasFormattedContent && this.canMergeAdjacent(range, targetTag, targetClasses)
        };
    }

    /**
     * Get all nodes within a range
     * 
     * @param {Range} range - Selection range
     * @returns {Array} Array of nodes
     */
    getNodesInRange(range) {
        // Guard against invalid range
        if (!range || !range.commonAncestorContainer) {
            return [];
        }

        const nodes = [];
        
        try {
            const iterator = document.createNodeIterator(
                range.commonAncestorContainer,
                NodeFilter.SHOW_ALL,
                {
                    acceptNode: (node) => {
                        try {
                            return range.intersectsNode(node) ? 
                                NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
                        } catch (e) {
                            // If intersectsNode fails, exclude the node
                            return NodeFilter.FILTER_REJECT;
                        }
                    }
                }
            );
            
            let node;
            while (node = iterator.nextNode()) {
                nodes.push(node);
            }
        } catch (e) {
            // If iterator creation fails, return empty array
            console.warn('Failed to create node iterator:', e);
        }
        
        return nodes;
    }

    /**
     * Find parent element with matching style
     * 
     * @param {Node} node - Starting node
     * @param {string} targetTag - Target tag name
     * @param {Array} targetClasses - Target classes
     * @returns {Element|null} Matching parent element
     */
    findParentWithStyle(node, targetTag, targetClasses) {
        let parent = node.parentElement;
        
        while (parent && parent !== this.contentEditor) {
            if (this.elementMatchesStyle(parent, targetTag, targetClasses)) {
                return parent;
            }
            parent = parent.parentElement;
        }
        
        return null;
    }

    /**
     * Check if element matches style criteria
     * 
     * @param {Element} element - Element to check
     * @param {string} targetTag - Target tag name
     * @param {Array} targetClasses - Target classes
     * @returns {boolean} True if matches
     */
    elementMatchesStyle(element, targetTag, targetClasses) {
        // Guard against null/undefined elements or missing tagName
        if (!element || !element.tagName || typeof element.tagName !== 'string') {
            return false;
        }
        
        if (element.tagName.toLowerCase() !== targetTag) {
            return false;
        }
        
        // For default styles (no classes), just match the tag
        if (!targetClasses || targetClasses.length === 0) {
            return true;
        }
        
        // Guard against missing classList
        if (!element.classList) {
            return false;
        }
        
        // For styled elements, match classes
        return targetClasses.every(className => element.classList.contains(className));
    }

    /**
     * Get all text nodes within an element
     * 
     * @param {Element} element - Element to search
     * @returns {Array} Array of text nodes
     */
    getTextNodesInElement(element) {
        const textNodes = [];
        const walker = document.createTreeWalker(
            element,
            NodeFilter.SHOW_TEXT,
            null,
            false
        );
        
        let node;
        while (node = walker.nextNode()) {
            if (node.textContent.trim()) {
                textNodes.push(node);
            }
        }
        
        return textNodes;
    }

    /**
     * Check if adjacent elements can be merged
     * 
     * @param {Range} range - Selection range
     * @param {string} targetTag - Target tag name
     * @param {Array} targetClasses - Target classes
     * @returns {boolean} True if can merge
     */
    canMergeAdjacent(range, targetTag, targetClasses) {
        // Guard against invalid range
        if (!range || !range.startContainer || !range.endContainer) {
            return false;
        }

        // Check if selection starts/ends next to matching elements
        const startContainer = range.startContainer;
        const endContainer = range.endContainer;
        
        // Check previous sibling of start
        let prevElement = null;
        if (startContainer.nodeType === Node.TEXT_NODE) {
            prevElement = startContainer.previousSibling;
        } else if (range.startOffset > 0 && startContainer.childNodes) {
            prevElement = startContainer.childNodes[range.startOffset - 1];
        }
        
        // Check next sibling of end
        let nextElement = null;
        if (endContainer.nodeType === Node.TEXT_NODE) {
            nextElement = endContainer.nextSibling;
        } else if (range.endOffset < endContainer.childNodes.length && endContainer.childNodes) {
            nextElement = endContainer.childNodes[range.endOffset];
        }
        
        // Only check elements that are actually Element nodes
        const prevMatches = prevElement && 
                           prevElement.nodeType === Node.ELEMENT_NODE && 
                           this.elementMatchesStyle(prevElement, targetTag, targetClasses);
        const nextMatches = nextElement && 
                           nextElement.nodeType === Node.ELEMENT_NODE && 
                           this.elementMatchesStyle(nextElement, targetTag, targetClasses);
        
        return prevMatches || nextMatches;
    }

    /**
     * Remove formatting from selection - properly handles partial selections
     * 
     * @param {Range} range - Selection range
     * @param {Object} styleInfo - Style information
     */
    removeFormattingFromSelection(range, styleInfo) {
        const targetTag = styleInfo.tagName.toLowerCase();
        const targetClasses = styleInfo.classes || [];
        
        // Find all parent elements that match our target style and contain the selection
        const matchingParents = this.findMatchingParentsInRange(range, targetTag, targetClasses);
        
        // Process each matching parent element
        matchingParents.forEach(parentElement => {
            this.splitElementAroundRange(parentElement, range);
        });
        
        // Also handle direct element matches within the selection
        const selectedNodes = this.getNodesInRange(range);
        const elementsToUnwrap = [];
        
        selectedNodes.forEach(node => {
            if (node.nodeType === Node.ELEMENT_NODE && 
                this.elementMatchesStyle(node, targetTag, targetClasses) &&
                this.rangeFullyContainsElement(range, node)) {
                elementsToUnwrap.push(node);
            }
        });
        
        // Unwrap elements that are fully contained in the selection
        elementsToUnwrap.forEach(element => {
            this.unwrapElement(element);
        });
    }

    /**
     * Find all parent elements that match the style and intersect with the range
     * 
     * @param {Range} range - Selection range
     * @param {string} targetTag - Target tag name
     * @param {Array} targetClasses - Target classes
     * @returns {Array} Array of matching parent elements
     */
    findMatchingParentsInRange(range, targetTag, targetClasses) {
        const matchingParents = [];
        const seenElements = new Set();
        
        // Check all containers in the range
        let container = range.startContainer;
        
        // Walk up from start container
        while (container && container !== this.contentEditor) {
            if (container.nodeType === Node.ELEMENT_NODE &&
                this.elementMatchesStyle(container, targetTag, targetClasses) &&
                !seenElements.has(container)) {
                
                // Check if this element partially overlaps with our selection
                if (this.elementIntersectsRange(container, range)) {
                    matchingParents.push(container);
                    seenElements.add(container);
                }
            }
            container = container.parentElement;
        }
        
        // Also check from end container if it's different
        if (range.endContainer !== range.startContainer) {
            container = range.endContainer;
            while (container && container !== this.contentEditor) {
                if (container.nodeType === Node.ELEMENT_NODE &&
                    this.elementMatchesStyle(container, targetTag, targetClasses) &&
                    !seenElements.has(container)) {
                    
                    if (this.elementIntersectsRange(container, range)) {
                        matchingParents.push(container);
                        seenElements.add(container);
                    }
                }
                container = container.parentElement;
            }
        }
        
        return matchingParents;
    }

    /**
     * Check if an element intersects with a range
     * 
     * @param {Element} element - Element to check
     * @param {Range} range - Range to check against
     * @returns {boolean} True if they intersect
     */
    elementIntersectsRange(element, range) {
        try {
            const elementRange = document.createRange();
            elementRange.selectNode(element);
            
            return range.compareBoundaryPoints(Range.START_TO_END, elementRange) > 0 &&
                   elementRange.compareBoundaryPoints(Range.START_TO_END, range) > 0;
        } catch (e) {
            return false;
        }
    }

    /**
     * Check if range fully contains an element
     * 
     * @param {Range} range - Range to check
     * @param {Element} element - Element to check
     * @returns {boolean} True if range fully contains element
     */
    rangeFullyContainsElement(range, element) {
        try {
            const elementRange = document.createRange();
            elementRange.selectNode(element);
            
            return range.compareBoundaryPoints(Range.START_TO_START, elementRange) <= 0 &&
                   range.compareBoundaryPoints(Range.END_TO_END, elementRange) >= 0;
        } catch (e) {
            return false;
        }
    }

    /**
     * Apply formatting to selection with smart merging
     * 
     * @param {Range} range - Selection range
     * @param {Object} styleInfo - Style information
     * @param {Object} analysis - Selection analysis
     */
    applyFormattingToSelection(range, styleInfo, analysis) {
        const targetTag = styleInfo.tagName.toLowerCase();
        const targetClasses = styleInfo.classes || [];
        
        // Extract selection content
        const selectedContent = range.extractContents();
        
        // Create new styled element
        const styledElement = this.styleEngine.createElementFromTemplate(
            styleInfo,
            { content: '' }
        );
        
        // Move selected content into styled element
        styledElement.appendChild(selectedContent);
        
        // Insert the styled element
        range.insertNode(styledElement);
        
        // Try to merge with adjacent elements
        this.mergeAdjacentElements(styledElement, targetTag, targetClasses);
        
        // Normalize whitespace and clean up
        this.normalizeWhitespace(styledElement.parentNode);
    }

    /**
     * Unwrap an element, moving its children to its parent
     * 
     * @param {Element} element - Element to unwrap
     */
    unwrapElement(element) {
        const parent = element.parentNode;
        while (element.firstChild) {
            parent.insertBefore(element.firstChild, element);
        }
        parent.removeChild(element);
    }

    /**
     * Split element around a range - preserves formatting outside selection
     * Uses DOM-based approach to maintain exact whitespace and structure
     * 
     * @param {Element} element - Element to split
     * @param {Range} range - Range to split around
     */
    splitElementAroundRange(element, range) {
        try {
            // Create a more precise splitting approach using DOM structure
            const parent = element.parentNode;
            const elementRange = document.createRange();
            elementRange.selectNodeContents(element);
            
            // Clone the range to avoid modifying the original
            const workingRange = range.cloneRange();
            
            // Ensure we're working within the element bounds
            if (workingRange.compareBoundaryPoints(Range.START_TO_START, elementRange) < 0) {
                workingRange.setStart(elementRange.startContainer, elementRange.startOffset);
            }
            if (workingRange.compareBoundaryPoints(Range.END_TO_END, elementRange) > 0) {
                workingRange.setEnd(elementRange.endContainer, elementRange.endOffset);
            }
            
            // Create ranges for before and after content
            const beforeRange = document.createRange();
            beforeRange.setStart(elementRange.startContainer, elementRange.startOffset);
            beforeRange.setEnd(workingRange.startContainer, workingRange.startOffset);
            
            const afterRange = document.createRange();
            afterRange.setStart(workingRange.endContainer, workingRange.endOffset);
            afterRange.setEnd(elementRange.endContainer, elementRange.endOffset);
            
            // Extract content fragments while preserving structure
            const beforeFragment = beforeRange.cloneContents();
            const selectedFragment = workingRange.cloneContents();
            const afterFragment = afterRange.cloneContents();
            
            // Extract the actual selected content from the DOM
            workingRange.extractContents();
            
            // Create before element if it has content
            if (this.fragmentHasContent(beforeFragment)) {
                const beforeElement = this.cloneElementStructure(element);
                beforeElement.appendChild(beforeFragment);
                parent.insertBefore(beforeElement, element);
            }
            
            // Insert selected content directly (no wrapper)
            if (this.fragmentHasContent(selectedFragment)) {
                parent.insertBefore(selectedFragment, element);
            }
            
            // Create after element if it has content
            if (this.fragmentHasContent(afterFragment)) {
                const afterElement = this.cloneElementStructure(element);
                afterElement.appendChild(afterFragment);
                parent.insertBefore(afterElement, element);
            }
            
            // Remove the original element
            parent.removeChild(element);
            
        } catch (e) {
            console.warn('Failed to split element around range, falling back to unwrap:', e);
            this.unwrapElement(element);
        }
    }

    /**
     * Check if a document fragment has any content (including whitespace)
     * More permissive than hasSignificantContent - preserves all content
     * 
     * @param {DocumentFragment} fragment - Fragment to check
     * @returns {boolean} True if has any content
     */
    fragmentHasContent(fragment) {
        return fragment && fragment.childNodes && fragment.childNodes.length > 0;
    }

    /**
     * Get text offset of a position within an element
     * 
     * @param {Element} element - Container element
     * @param {Node} container - Node containing the position
     * @param {number} offset - Offset within the container
     * @returns {number} Text offset within element
     */
    getTextOffsetInElement(element, container, offset) {
        const elementRange = document.createRange();
        elementRange.setStart(element, 0);
        elementRange.setEnd(container, offset);
        return elementRange.toString().length;
    }

    /**
     * Clone element structure without content
     * 
     * @param {Element} element - Element to clone
     * @returns {Element} Cloned element
     */
    cloneElementStructure(element) {
        const clone = document.createElement(element.tagName);
        
        // Copy attributes
        for (let i = 0; i < element.attributes.length; i++) {
            const attr = element.attributes[i];
            clone.setAttribute(attr.name, attr.value);
        }
        
        return clone;
    }

    /**
     * Check if document fragment has significant content
     * Preserves whitespace that could be meaningful in the document flow
     * 
     * @param {DocumentFragment} fragment - Fragment to check
     * @returns {boolean} True if has significant content
     */
    hasSignificantContent(fragment) {
        if (!fragment || !fragment.childNodes) {
            return false;
        }
        
        // Check if fragment has any element nodes or text nodes with content
        for (let i = 0; i < fragment.childNodes.length; i++) {
            const node = fragment.childNodes[i];
            if (node.nodeType === Node.ELEMENT_NODE) {
                return true;
            }
            if (node.nodeType === Node.TEXT_NODE && node.textContent.length > 0) {
                // Don't trim - preserve all whitespace as it could be meaningful
                // Only exclude completely empty text nodes
                return true;
            }
        }
        
        return false;
    }

    /**
     * Merge adjacent elements of the same type
     * 
     * @param {Element} element - Element to merge with adjacent siblings
     * @param {string} targetTag - Target tag name
     * @param {Array} targetClasses - Target classes
     */
    mergeAdjacentElements(element, targetTag, targetClasses) {
        // Merge with previous sibling
        let prevSibling = element.previousSibling;
        if (prevSibling && this.elementMatchesStyle(prevSibling, targetTag, targetClasses)) {
            // Move content from current element to previous
            while (element.firstChild) {
                prevSibling.appendChild(element.firstChild);
            }
            element.parentNode.removeChild(element);
            element = prevSibling;
        }
        
        // Merge with next sibling
        let nextSibling = element.nextSibling;
        if (nextSibling && this.elementMatchesStyle(nextSibling, targetTag, targetClasses)) {
            // Move content from next element to current
            while (nextSibling.firstChild) {
                element.appendChild(nextSibling.firstChild);
            }
            nextSibling.parentNode.removeChild(nextSibling);
        }
    }

    /**
     * Normalize whitespace in a container
     * 
     * @param {Element} container - Container to normalize
     */
    normalizeWhitespace(container) {
        // Remove empty text nodes and normalize spacing
        const walker = document.createTreeWalker(
            container,
            NodeFilter.SHOW_TEXT,
            null,
            false
        );
        
        const textNodes = [];
        let node;
        while (node = walker.nextNode()) {
            textNodes.push(node);
        }
        
        textNodes.forEach(textNode => {
            if (!textNode.textContent.trim()) {
                textNode.parentNode.removeChild(textNode);
            }
        });
    }

    /**
     * Handle save action
     */
    handleSave() {
        try {
            const content = this.extractContent();
            const success = this.applyContentToElement(content);

            if (success) {
                this.onSave(content);
            }
        } catch (error) {
            console.error('Save failed:', error);
        }
    }

    /**
     * Handle cancel action
     */
    handleCancel() {
        this.onCancel();
    }

    /**
     * Handle content change
     */
    handleChange() {
        try {
            const content = this.extractContent();
            this.onChange(content);
        } catch (error) {
            console.error('Change handling failed:', error);
        }
    }

    /**
     * Extract current content from editor
     * 
     * @returns {Object} - Extracted content
     */
    extractContent() {
        if (this.contentEditor.className.includes('direct-editor')) {
            // Direct property editor - extract form values and generate HTML
            return this.extractDirectEditorContent();
        } else if (this.contentEditor.className.includes('rich-editor')) {
            // Rich text editor - return HTML as-is
            return {
                type: 'html',
                content: this.contentEditor.innerHTML
            };
        }

        return null;
    }

    /**
     * Extract content from direct property editors
     * 
     * @returns {Object} - Content with generated HTML
     */
    extractDirectEditorContent() {
        const tagName = this.element.tagName.toLowerCase();

        if (tagName === 'a') {
            const text = document.getElementById('link-text').value;
            const url = document.getElementById('link-url').value;
            const target = document.getElementById('link-target').value;

            return {
                type: 'html',
                content: text,
                properties: { href: url, target: target }
            };
        } else if (tagName === 'button') {
            const text = document.getElementById('button-text').value;

            return {
                type: 'html',
                content: text
            };
        } else if (tagName === 'img') {
            const src = document.getElementById('image-src').value;
            const alt = document.getElementById('image-alt').value;

            return {
                type: 'html',
                content: '',
                properties: { src: src, alt: alt }
            };
        }

        return null;
    }



    /**
     * Apply extracted content to the original element
     * HTML-first approach: always use HTML preservation with optional property updates
     * 
     * @param {Object} content - Content to apply
     * @returns {boolean} - Success status
     */
    applyContentToElement(content) {
        try {
            // Apply properties if specified (for direct editors)
            if (content.properties) {
                for (const [property, value] of Object.entries(content.properties)) {
                    if (value) {
                        this.element.setAttribute(property, value);
                    } else {
                        this.element.removeAttribute(property);
                    }
                }
            }

            // Apply HTML content using preservation engine
            if (content.content !== undefined) {
                return this.htmlEngine.applyFromEditing(this.element, content.content);
            }

            return true;
        } catch (error) {
            console.error('Failed to apply content:', error);
            return false;
        }
    }

    /**
     * Get the editor container element
     * 
     * @returns {HTMLElement} - Editor container
     */
    getEditorElement() {
        return this.editorContainer;
    }

    /**
     * Open link configuration popup for selected text
     */
    openLinkPopup() {
        // Get current selection
        const selection = window.getSelection();

        // Check if we have a valid selection in our editor
        if (!selection.rangeCount || !this.contentEditor.contains(selection.anchorNode)) {
            alert('Please select some text to create a link');
            return;
        }

        const range = selection.getRangeAt(0);
        const selectedText = range.toString().trim();

        if (!selectedText) {
            alert('Please select some text to create a link');
            return;
        }

        // Check if selection is inside an existing link
        let existingLink = null;
        let currentNode = range.commonAncestorContainer;

        // Walk up the DOM to find if we're inside a link
        while (currentNode && currentNode !== this.contentEditor) {
            if (currentNode.nodeType === Node.ELEMENT_NODE && currentNode.tagName.toLowerCase() === 'a') {
                existingLink = currentNode;
                break;
            }
            currentNode = currentNode.parentNode;
        }

        // Get existing URL if editing a link
        const currentUrl = existingLink ? existingLink.href : '';
        const currentTarget = existingLink ? existingLink.target : '';

        // Show popup
        this.showLinkConfigPopup(selectedText, currentUrl, currentTarget, (url, target) => {
            this.applyLinkToSelection(range, selectedText, url, target, existingLink);
        });
    }

    /**
     * Show link configuration popup
     * 
     * @param {string} text - Selected text
     * @param {string} currentUrl - Current URL (if editing)
     * @param {string} currentTarget - Current target (if editing)
     * @param {Function} onSave - Callback when user saves
     */
    showLinkConfigPopup(text, currentUrl, currentTarget, onSave) {
        // Create popup overlay
        const overlay = document.createElement('div');
        overlay.className = 'insertr-modal-overlay';
        overlay.style.zIndex = '999999';

        // Create popup container
        const popup = document.createElement('div');
        popup.className = 'insertr-modal-container';
        popup.style.maxWidth = '400px';

        // Create unified link configuration form
        const linkForm = this.createLinkConfiguration({
            url: currentUrl,
            target: currentTarget,
            showText: false,
            title: `${currentUrl ? 'Edit' : 'Add'} Link`,
            onSave: (data) => {
                onSave(data.url, data.target);
                document.body.removeChild(overlay);
            },
            onCancel: () => {
                document.body.removeChild(overlay);
            },
            onRemove: currentUrl ? () => {
                onSave('', ''); // Empty URL removes the link
                document.body.removeChild(overlay);
            } : null
        });

        // Add subtitle with selected text
        const subtitle = document.createElement('p');
        subtitle.style.cssText = `
            margin: -12px 0 16px 0;
            color: var(--insertr-text-secondary);
            font-size: 14px;
        `;
        subtitle.textContent = `Configure link for: "${text}"`;
        
        const title = linkForm.querySelector('.insertr-editor-title');
        title.parentNode.insertBefore(subtitle, title.nextSibling);

        // Add actions section
        const actions = document.createElement('div');
        actions.className = 'insertr-form-actions';

        const saveBtn = document.createElement('button');
        saveBtn.className = 'insertr-btn-save';
        saveBtn.textContent = 'Save Link';

        const cancelBtn = document.createElement('button');
        cancelBtn.className = 'insertr-btn-cancel';
        cancelBtn.textContent = 'Cancel';

        // Remove link button (if editing existing link)
        if (currentUrl && linkForm._onRemove) {
            const removeBtn = document.createElement('button');
            removeBtn.className = 'insertr-btn-cancel';
            removeBtn.textContent = 'Remove Link';
            removeBtn.style.marginRight = 'auto';
            actions.appendChild(removeBtn);

            removeBtn.addEventListener('click', linkForm._onRemove);
        }

        actions.appendChild(cancelBtn);
        actions.appendChild(saveBtn);

        // Add actions to form
        linkForm.appendChild(actions);

        // Event handlers
        saveBtn.addEventListener('click', linkForm._onSave);
        cancelBtn.addEventListener('click', linkForm._onCancel);

        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                linkForm._onCancel();
            }
        });

        // Assemble popup
        popup.appendChild(linkForm);
        overlay.appendChild(popup);

        // Show popup
        document.body.appendChild(overlay);

        // Focus URL input
        setTimeout(() => {
            const urlInput = linkForm.querySelector('#link-url');
            if (urlInput) {
                urlInput.focus();
            }
        }, 100);
    }

    /**
     * Apply link to the current selection
     * 
     * @param {Range} range - Selection range
     * @param {string} text - Selected text
     * @param {string} url - Link URL
     * @param {string} target - Link target
     * @param {HTMLElement} existingLink - Existing link element (if editing)
     */
    applyLinkToSelection(range, text, url, target, existingLink) {
        if (!url) {
            // Remove link if no URL provided
            if (existingLink) {
                const parent = existingLink.parentNode;
                while (existingLink.firstChild) {
                    parent.insertBefore(existingLink.firstChild, existingLink);
                }
                parent.removeChild(existingLink);
            }
            return;
        }

        if (existingLink) {
            // Update existing link
            existingLink.href = url;
            if (target) {
                existingLink.target = target;
            } else {
                existingLink.removeAttribute('target');
            }
        } else {
            // Create new link
            const link = document.createElement('a');
            link.href = url;
            if (target) {
                link.target = target;
            }

            try {
                range.surroundContents(link);
            } catch (e) {
                // Fallback for complex selections
                link.textContent = text;
                range.deleteContents();
                range.insertNode(link);
            }
        }

        // Clear selection
        window.getSelection().removeAllRanges();

        // Trigger change event
        this.onChange();
    }

    /**
     * Add validation to link editor
     */
    addLinkValidation(form) {
        const textInput = form.querySelector('#link-text');
        const urlInput = form.querySelector('#link-url');
        const textMessage = form.querySelector('#link-text-message');
        const urlMessage = form.querySelector('#link-url-message');
        
        // URL validation (URL input should always exist)
        if (urlInput && urlMessage) {
            urlInput.addEventListener('input', () => {
                const url = urlInput.value.trim();
                
                if (!url) {
                    this.setValidationState(urlInput, urlMessage, '', 'info');
                    return;
                }
                
                try {
                    new URL(url);
                    this.setValidationState(urlInput, urlMessage, '✓ Valid URL', 'success');
                } catch (e) {
                    if (url.includes('.') && !url.startsWith('http')) {
                        // Suggest adding protocol
                        this.setValidationState(urlInput, urlMessage, 'Try adding https:// to the beginning', 'info');
                    } else {
                        this.setValidationState(urlInput, urlMessage, 'Please enter a valid URL', 'error');
                    }
                }
            });
        }
        
        // Text validation (only if text input exists - not present in popups)
        if (textInput && textMessage) {
            textInput.addEventListener('input', () => {
                const text = textInput.value.trim();
                
                if (!text) {
                    this.setValidationState(textInput, textMessage, 'Link text is required', 'error');
                } else {
                    this.setValidationState(textInput, textMessage, '', 'info');
                }
            });
        }
    }
    
    /**
     * Set validation state for form elements
     */
    setValidationState(input, messageElement, message, type) {
        // Guard against null elements
        if (!input || !messageElement) {
            return;
        }
        
        // Remove previous states
        input.classList.remove('insertr-error', 'insertr-success');
        messageElement.classList.remove('insertr-error', 'insertr-success', 'insertr-info');
        
        // Add new state
        if (type === 'error') {
            input.classList.add('insertr-error');
            messageElement.classList.add('insertr-error');
        } else if (type === 'success') {
            input.classList.add('insertr-success');
            messageElement.classList.add('insertr-success');
        } else {
            messageElement.classList.add('insertr-info');
        }
        
        // Set message
        messageElement.textContent = message;
        messageElement.style.display = message ? 'block' : 'none';
    }
    
    /**
     * HTML escape utility
     */
    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    /**
     * Destroy the editor and clean up
     */
    destroy() {
        // Clean up selection change listeners
        if (this._selectionChangeCleanup) {
            this._selectionChangeCleanup();
            this._selectionChangeCleanup = null;
        }

        if (this.editorContainer && this.editorContainer.parentNode) {
            this.editorContainer.parentNode.removeChild(this.editorContainer);
        }

        this.isInitialized = false;
        this.editorContainer = null;
        this.contentEditor = null;
        this.toolbar = null;
        this.styleButtons = null;
    }
}
