/**
 * XSS Protection utilities for Local Deep Research
 *
 * This module provides secure HTML sanitization functions to prevent
 * cross-site scripting (XSS) attacks when rendering dynamic content.
 * Uses DOMPurify for proven, security-reviewed HTML sanitization.
 *
 * This module intentionally provides HTML sanitization utilities. All innerHTML
 * operations use DOMPurify sanitization or escapeHtml encoding, and each is
 * suppressed by an inline directive at its own call site (a module-level
 * suppression in this block comment would be ignored by the scanner anyway).
 *
 * ARCHITECTURE NOTE: Inline Fallback Pattern
 * ------------------------------------------
 * A single global `escapeHtmlFallback` is defined in services/ui.js (loaded
 * via base.html on all pages). This provides defense-in-depth XSS protection
 * if this xss-protection.js file fails to load.
 *
 * IMPORTANT: Only services/ui.js should define escapeHtmlFallback at top-level
 * scope. Other files must NOT redeclare it — doing so causes SyntaxError
 * ("Identifier has already been declared") since const/var cannot coexist
 * in the same global scope across <script> tags.
 *
 * Files wrapped in IIFEs (components/results.js, components/settings.js,
 * components/details.js, components/fallback/ui.js) may safely define their
 * own scoped fallback.
 *
 * The usage pattern is: `(window.escapeHtml || escapeHtmlFallback)(text)`
 * This ensures the global function is preferred when available, with the
 * inline fallback as a safety net.
 */

(function() {
    'use strict';

    // Check if DOMPurify is available dynamically (loaded via app.js/Vite module)
    // Must be a function since Vite modules are deferred and load after this script
    function hasDOMPurify() {
        return typeof DOMPurify !== 'undefined';
    }

    // Configure DOMPurify hooks to prevent tabnabbing attacks
    // This must be done at module load time, before any sanitization occurs
    if (hasDOMPurify()) {
        DOMPurify.addHook('afterSanitizeAttributes', function(node) {
            // Enforce rel="noopener noreferrer" on all links with target="_blank"
            // This prevents the opened page from accessing window.opener
            if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {
                node.setAttribute('rel', 'noopener noreferrer');
            }
        });
    }

    /**
     * HTML entity encoding map for XSS prevention
     */
    const HTML_ESCAPE_MAP = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#39;',
        '/': '&#x2F;'
    };

    /**
     * DOMPurify configuration for secure sanitization
     */
    const SANITIZE_CONFIG = {
        ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'span', 'br', 'p', 'div', 'ul', 'ol', 'li', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
        ALLOWED_ATTR: ['class', 'id', 'href', 'title', 'alt', 'target', 'rel'],
        ALLOW_DATA_ATTR: false,
        FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'style', 'meta', 'link'],
        FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange', 'onsubmit', 'on*'],
        KEEP_CONTENT: true,
        SANITIZE_DOM: true,
        SANITIZE_NAMED_PROPS: true,
        SAFE_FOR_TEMPLATES: true,
        WHOLE_DOCUMENT: false,
        RETURN_DOM: false,
        RETURN_DOM_FRAGMENT: false,
        RETURN_DOM_IMPORT: false,
        CUSTOM_ELEMENT_HANDLING: {
            tagNameCheck: null,
            attributeNameCheck: null,
            allowCustomizedBuiltInElements: false
        }
    };

    /**
     * Escape HTML entities in a string to prevent XSS
     * @param {string} text - The text to escape
     * @returns {string} - The escaped text safe for HTML content
     */
    function escapeHtml(text) {
        if (text === null || text === undefined) {
            return '';
        }
        if (typeof text !== 'string') {
            text = String(text);
        }

        // bearer:disable javascript_lang_manual_html_sanitization
        return text.replace(/[&<>"'/]/g, (match) => HTML_ESCAPE_MAP[match]);
    }

/**
 * Escape HTML attributes to prevent XSS
 * @param {string} text - The text to escape for attribute context
 * @returns {string} - The escaped text safe for HTML attributes
 */
// This IS the sanitization function
// bearer:disable javascript_lang_manual_html_sanitization
function escapeHtmlAttribute(text) {
    if (typeof text !== 'string') {
        text = String(text);
    }

    // For attributes, we need to escape quotes and ampersands
    // bearer:disable javascript_lang_manual_html_sanitization
    return text.replace(/["&'<>]/g, (match) => HTML_ESCAPE_MAP[match]);
}

/**
 * Safely set innerHTML with content sanitization using DOMPurify
 * @param {Element} element - The DOM element to update
 * @param {string} content - The content to set (will be sanitized)
 * @param {boolean} allowHtmlTags - If true, allows basic HTML tags, otherwise escapes everything
 */
// Content is sanitized by DOMPurify before insertion
// bearer:disable javascript_lang_dangerous_insert_html
function safeSetInnerHTML(element, content, allowHtmlTags = false) {
    if (!element) {
        return;
    }

    if (!content) {
        element.textContent = '';
        return;
    }

    const contentString = String(content);

    if (allowHtmlTags && hasDOMPurify()) {
        // Use DOMPurify for secure HTML sanitization
        const sanitized = DOMPurify.sanitize(contentString, SANITIZE_CONFIG);
        // bearer:disable javascript_lang_dangerous_insert_html
        // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: content already sanitized by DOMPurify.sanitize() above
        element.innerHTML = sanitized;
    } else if (allowHtmlTags) {
        // DOMPurify not available but HTML requested - escape all HTML for safety
        SafeLogger.warn('DOMPurify not available, escaping HTML instead of sanitizing');
        element.textContent = contentString;
    } else {
        // Escape all HTML - use textContent for maximum security
        element.textContent = contentString;
    }
}

/**
 * Create a safe DOM element with text content
 * @param {string} tagName - The HTML tag name
 * @param {string} text - The text content (will be escaped)
 * @param {Object} attributes - Optional attributes object
 * @param {string[]} classNames - Optional CSS class names
 * @returns {Element} - The created DOM element
 */
function safeCreateElement(tagName, text = '', attributes = {}, classNames = []) {
    // Tag whitelist for programmatic DOM creation (broader than DOMPurify's
    // SANITIZE_CONFIG which applies to untrusted HTML sanitization)
    const SAFE_TAGS = new Set([
        'b', 'i', 'em', 'strong', 'span', 'br', 'p', 'div', 'small',
        'ul', 'ol', 'li', 'a',
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
        'hr', 'label', 'nav', 'button',
        'table', 'thead', 'tbody', 'tr', 'th', 'td',
        'select', 'option', 'optgroup', 'canvas'
    ]);
    const normalizedTag = String(tagName).toLowerCase();
    if (!SAFE_TAGS.has(normalizedTag)) {
        throw new Error('safeCreateElement: disallowed tag "' + tagName + '"');
    }
    if (!hasDOMPurify()) {
        throw new Error('safeCreateElement requires DOMPurify to be loaded');
    }

    // normalizedTag is validated against the SAFE_TAGS whitelist above (throws
    // otherwise) and createElement does not parse HTML -- Bearer false positive.
    // bearer:disable javascript_lang_dangerous_insert_html
    const element = document.createElement(normalizedTag);

    if (text) {
        element.textContent = text;
    }

    Object.entries(attributes).forEach(([key, value]) => {
        if (key && value !== null && value !== undefined) {
            element.setAttribute(String(key), String(value));
        }
    });

    if (classNames.length > 0) {
        element.className = classNames.join(' ');
    }

    // Let DOMPurify sanitize attributes (on*, javascript:/data: URIs, etc.)
    const clean = DOMPurify.sanitize(element.outerHTML, {
        ALLOWED_TAGS: [normalizedTag],
        RETURN_DOM_FRAGMENT: true,
    });

    return clean.firstChild || element;
}

/**
 * Safe text content setter that prevents XSS
 * @param {Element} element - The DOM element to update
 * @param {string} content - The content to set safely
 */
function safeSetTextContent(element, content) {
    if (!element) {
        return;
    }

    element.textContent = String(content || '');
}

/**
 * Create a safe alert message element
 * @param {string} message - The alert message (will be escaped)
 * @param {string} type - Alert type (success, error, warning, info)
 * @returns {Element} - The created alert element
 */
function createSafeAlertElement(message, type = 'info') {
    const alert = document.createElement('div');
    const alertType = window.LdrAlertHelpers.mapAlertType(type);
    alert.className = `alert alert-${alertType}`;
    alert.setAttribute('role', 'alert');
    alert.setAttribute('aria-atomic', 'true');

    // Create icon
    const iconMap = {
        'success': 'fa-check-circle',
        'error': 'fa-exclamation-circle',
        'warning': 'fa-exclamation-triangle',
        'info': 'fa-info-circle'
    };

    const icon = document.createElement('i');
    icon.className = `fas ${iconMap[type] || iconMap['info']}`;
    icon.setAttribute('aria-hidden', 'true');

    // Create message text (escaped)
    const messageText = document.createElement('span');
    messageText.textContent = message; // Safe - textContent prevents HTML injection

    // Create close button (use button element for accessibility)
    const closeBtn = document.createElement('button');
    closeBtn.className = 'ldr-alert-close';
    closeBtn.setAttribute('aria-label', 'Dismiss alert');
    closeBtn.type = 'button';
    closeBtn.textContent = '×';
    closeBtn.addEventListener('click', () => {
        alert.remove();
    });

    // Assemble alert
    alert.appendChild(icon);
    alert.appendChild(messageText);
    alert.appendChild(closeBtn);

    return alert;
}

/**
 * Secure HTML sanitization using DOMPurify
 * @param {string} dirty - The potentially dirty HTML string
 * @param {Object} config - Optional DOMPurify configuration overrides
 * @returns {string} - The sanitized HTML string
 */
function sanitizeHtml(dirty, config = {}) {
    if (!dirty) return '';

    if (hasDOMPurify()) {
        const finalConfig = { ...SANITIZE_CONFIG, ...config };
        return DOMPurify.sanitize(String(dirty), finalConfig);
    }
    // Fallback: escape all HTML if DOMPurify is not available
    SafeLogger.warn('DOMPurify not available, falling back to HTML escaping');
    return escapeHtml(String(dirty));
}

/**
 * Validate and sanitize user input for safe display
 * @param {any} input - The input to validate and sanitize
 * @param {Object} options - Sanitization options
 * @returns {string} - The sanitized string
 */
function sanitizeUserInput(input, options = {}) {
    const {
        maxLength = 10000,
        allowLineBreaks = true,
        trimWhitespace = true,
        allowHtml = false
    } = options;

    if (input === null || input === undefined) {
        return '';
    }

    let sanitized = String(input);

    // Trim if requested
    if (trimWhitespace) {
        sanitized = sanitized.trim();
    }

    // Enforce max length
    if (sanitized.length > maxLength) {
        sanitized = sanitized.substring(0, maxLength);
    }

    // Handle line breaks
    if (allowLineBreaks) {
        sanitized = sanitized.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
    }

    // Either escape HTML or sanitize it
    if (allowHtml) {
        return sanitizeHtml(sanitized);
    }
    return escapeHtml(sanitized);
}

  // Export to global scope
    window.XSSProtection = {
        escapeHtml,
        escapeHtmlAttribute,
        safeSetInnerHTML,
        safeCreateElement,
        safeSetTextContent,
        createSafeAlertElement,
        sanitizeUserInput,
        sanitizeHtml
    };

    /**
     * Safely update button content with icon and text
     * @param {HTMLElement} button - The button element to update
     * @param {string} iconClass - Font Awesome icon class (without 'fas')
     * @param {string} text - Button text content
     * @param {boolean} addSpinner - Whether to add spinner animation
     */
    function safeUpdateButton(button, iconClass, text, addSpinner = false) {
        if (!button) return;

        // Clear existing content
        while (button.firstChild) {
            button.removeChild(button.firstChild);
        }

        // Create icon (aria-hidden for accessibility — decorative icons beside text)
        const icon = document.createElement('i');
        icon.className = `fas ${iconClass}`;
        icon.setAttribute('aria-hidden', 'true');
        if (addSpinner) {
            icon.className += ' fa-spin';
        }

        // Add icon and text
        button.appendChild(icon);
        if (text) {
            button.appendChild(document.createTextNode(text));
        }
    }

    /**
     * Create a loading overlay with safe DOM manipulation
     * @param {Object} options - Loading overlay options
     * @returns {HTMLElement} - The created loading overlay element
     */
    function createSafeLoadingOverlay(options = {}) {
        const {
            iconClass = 'fa-spinner fa-spin fa-3x',
            title = 'Loading...',
            description = 'Please wait...',
            iconMarginBottom = '20px',
            titleMargin = '10px 0',
            textOpacity = '0.8'
        } = options;

        const overlay = document.createElement('div');
        overlay.className = 'ldr-loading-overlay';

        const content = document.createElement('div');
        content.className = 'ldr-loading-content';
        content.style.textAlign = 'center';

        // Icon
        const icon = document.createElement('i');
        icon.className = `fas ${iconClass}`;
        icon.style.marginBottom = iconMarginBottom;

        // Title
        const titleElement = document.createElement('h3');
        titleElement.style.margin = titleMargin;
        titleElement.textContent = title;

        // Description
        const descriptionElement = document.createElement('p');
        descriptionElement.style.opacity = textOpacity;
        descriptionElement.textContent = description;

        // Assemble overlay
        content.appendChild(icon);
        content.appendChild(titleElement);
        content.appendChild(descriptionElement);
        overlay.appendChild(content);

        return overlay;
    }

    /**
     * Safely set CSS styles on an element
     * @param {HTMLElement} element - The element to style
     * @param {Object} styles - Style object with CSS properties
     */
    function safeSetStyles(element, styles) {
        if (!element || !styles) return;

        Object.entries(styles).forEach(([property, value]) => {
            element.style[property] = value;
        });
    }

    /**
     * Show a safe alert message
     * @param {string} containerId - ID of the alert container
     * @param {string} message - Alert message (will be sanitized)
     * @param {string} type - Alert type (success, error, warning, info)
     */
    function showSafeAlert(containerId, message, type = 'info') {
        const alertContainer = document.getElementById(containerId);
        if (!alertContainer) return;

        // Clear existing alerts
        while (alertContainer.firstChild) {
            alertContainer.removeChild(alertContainer.firstChild);
        }

        // Create alert using our secure method
        const alert = createSafeAlertElement(message, type);
        alertContainer.appendChild(alert);
        alertContainer.style.display = 'block';
    }

    // Also export individual functions for convenience
    window.escapeHtml = escapeHtml;
    window.escapeHtmlAttribute = escapeHtmlAttribute;
    window.safeSetInnerHTML = safeSetInnerHTML;
    window.safeCreateElement = safeCreateElement;
    window.safeSetTextContent = safeSetTextContent;
    window.createSafeAlertElement = createSafeAlertElement;
    window.sanitizeUserInput = sanitizeUserInput;
    window.sanitizeHtml = sanitizeHtml;

    // Export UI helper functions
    window.safeUpdateButton = safeUpdateButton;
    window.createSafeLoadingOverlay = createSafeLoadingOverlay;
    window.safeSetStyles = safeSetStyles;
    window.showSafeAlert = showSafeAlert;

})();
