/**
 * RSL Popover & Tooltip System
 *
 * Provides flexible, accessible popovers and tooltips with intelligent positioning,
 * collision detection, and multiple trigger modes. Automatically calculates optimal
 * placement and adjusts position to stay within viewport bounds.
 *
 * @fileoverview Smart popover and tooltip system with collision detection
 * @version 2.0.0
 * @license MIT
 *
 * Key Features:
 * - Intelligent auto-positioning with collision detection
 * - Multiple trigger modes: click, hover, focus, manual
 * - Four placement options: top, bottom, left, right, auto
 * - Viewport boundary detection and adjustment
 * - Optional arrow indicators with smart positioning
 * - Close on outside click or Escape key
 * - Smooth animations (fade, scale)
 * - Hover intent for tooltips (prevents accidental triggers)
 * - Event callbacks and custom events
 * - ARIA accessibility attributes
 * - Multiple active popovers support
 * - Automatic position updates on scroll/resize
 *
 * Trigger Modes:
 * - click: Toggle on click (ideal for popovers with actions)
 * - hover: Show on mouseenter (ideal for tooltips)
 * - focus: Show when element receives focus (keyboard accessible)
 * - manual: Controlled via JavaScript API only
 *
 * Usage:
 * // HTML markup:
 * <button data-toggle="popover"
 *         data-popover="myPopover"
 *         aria-describedby="myPopover">
 *   Show Popover
 * </button>
 * <div id="myPopover" class="popover" style="display: none;">
 *   <div class="arrow"></div>
 *   <div class="popover-header">Title</div>
 *   <div class="popover-body">Content here</div>
 * </div>
 *
 * // With data attributes:
 * <button data-toggle="tooltip"
 *         data-popover="myTooltip"
 *         data-trigger="hover"
 *         data-placement="top"
 *         data-animation="fade">
 *   Hover me
 * </button>
 *
 * // JavaScript API:
 * RSL.Popover.show(triggerElement);
 * RSL.Popover.hide(triggerElement);
 * RSL.Popover.toggle(triggerElement);
 * RSL.Popover.hideAll();
 *
 * Public API:
 * - RSL.Popover.show(trigger, options) - Show a popover
 * - RSL.Popover.hide(trigger, immediate) - Hide a popover
 * - RSL.Popover.toggle(trigger, options) - Toggle popover visibility
 * - RSL.Popover.hideAll() - Hide all active popovers
 * - RSL.Popover.updatePosition(trigger) - Update popover position
 * - RSL.Popover.updateAll() - Update all active popover positions
 * - RSL.Popover.init() - Initialize/reinitialize popovers
 *
 * Events:
 * - popover:show - Fired when popover is shown
 * - popover:hide - Fired when popover is hidden
 */

(function() {
    'use strict';

    /**
     * Active popovers registry
     * @type {Map<string, {trigger: HTMLElement, popover: HTMLElement, config: Object}>}
     */
    const activePopovers = new Map();

    /** @type {boolean} Track if document click handler is attached */
    let documentClickHandlerAttached = false;

    /**
     * Default popover configuration
     * @const
     * @type {Object}
     */
    const defaults = {
        trigger: 'click',           // 'click', 'hover', 'focus', or 'manual'
        placement: 'auto',          // 'top', 'bottom', 'left', 'right', 'auto'
        offset: 10,                 // Distance from trigger element in pixels
        closeOnOutsideClick: true,  // Close when clicking outside
        closeOnEscape: true,        // Close on Escape key
        animation: 'fade',          // 'fade', 'scale', 'none'
        delay: { show: 0, hide: 0 }, // Delays in milliseconds
        arrow: true,                // Show arrow indicator
        boundary: null              // Viewport boundary for collision detection
    };

    /**
     * Calculate optimal position for popover
     *
     * Calculates all possible placement positions, evaluates them for
     * visibility and viewport fit, then returns the best position with
     * boundary adjustments to keep popover fully visible.
     *
     * @param {HTMLElement} trigger - The trigger element
     * @param {HTMLElement} popover - The popover element
     * @param {string} placement - Desired placement: 'top', 'bottom', 'left', 'right', 'auto'
     * @param {number} offset - Distance from trigger in pixels
     * @returns {{top: number, left: number, placement: string}} Position object with coordinates and final placement
     *
     * @example
     * const pos = calculatePosition(trigger, popover, 'auto', 10);
     * popover.style.top = `${pos.top}px`;
     * popover.style.left = `${pos.left}px`;
     */
    function calculatePosition(trigger, popover, placement, offset) {
        const triggerRect = trigger.getBoundingClientRect();
        const popoverRect = popover.getBoundingClientRect();
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;
        const scrollX = window.scrollX;
        const scrollY = window.scrollY;

        const positions = {};

        // Calculate all possible positions
        positions.top = {
            top: triggerRect.top + scrollY - popoverRect.height - offset,
            left: triggerRect.left + scrollX + (triggerRect.width / 2) - (popoverRect.width / 2),
            placement: 'top'
        };

        positions.bottom = {
            top: triggerRect.bottom + scrollY + offset,
            left: triggerRect.left + scrollX + (triggerRect.width / 2) - (popoverRect.width / 2),
            placement: 'bottom'
        };

        positions.left = {
            top: triggerRect.top + scrollY + (triggerRect.height / 2) - (popoverRect.height / 2),
            left: triggerRect.left + scrollX - popoverRect.width - offset,
            placement: 'left'
        };

        positions.right = {
            top: triggerRect.top + scrollY + (triggerRect.height / 2) - (popoverRect.height / 2),
            left: triggerRect.right + scrollX + offset,
            placement: 'right'
        };

        // Auto-placement: find best position with collision detection
        if (placement === 'auto') {
            const scores = {};

            Object.keys(positions).forEach(pos => {
                const position = positions[pos];
                let score = 0;

                // Check if fully visible in viewport
                const wouldBeVisible =
                    position.top >= 0 &&
                    position.left >= 0 &&
                    position.top + popoverRect.height <= viewportHeight &&
                    position.left + popoverRect.width <= viewportWidth;

                if (wouldBeVisible) score += 1000;

                // Prefer vertical placements slightly
                if (pos === 'bottom' || pos === 'top') score += 10;

                // Prefer bottom over top
                if (pos === 'bottom') score += 5;

                scores[pos] = score;
            });

            // Find best position
            const bestPlacement = Object.keys(scores).reduce((a, b) => scores[a] > scores[b] ? a : b);
            placement = bestPlacement;
        }

        const position = positions[placement];

        // Ensure popover stays within viewport (boundary adjustment)
        if (position.left < 0) {
            position.left = offset;
        } else if (position.left + popoverRect.width > viewportWidth) {
            position.left = viewportWidth - popoverRect.width - offset;
        }

        if (position.top < 0) {
            position.top = offset;
        } else if (position.top + popoverRect.height > viewportHeight + scrollY) {
            position.top = viewportHeight + scrollY - popoverRect.height - offset;
        }

        return position;
    }

    /**
     * Position arrow indicator
     *
     * Calculates and applies arrow position to point at the trigger element.
     * Arrow is centered on the trigger regardless of popover position adjustments.
     *
     * @param {HTMLElement} trigger - The trigger element
     * @param {HTMLElement} popover - The popover element
     * @param {HTMLElement} arrow - The arrow element
     * @param {string} placement - Current placement: 'top', 'bottom', 'left', 'right'
     */
    function positionArrow(trigger, popover, arrow, placement) {
        if (!arrow) return;

        const triggerRect = trigger.getBoundingClientRect();
        const popoverRect = popover.getBoundingClientRect();

        // Remove all placement classes
        arrow.classList.remove('top', 'bottom', 'left', 'right');
        arrow.classList.add(placement);

        // Calculate arrow position
        if (placement === 'top' || placement === 'bottom') {
            const triggerCenter = triggerRect.left + (triggerRect.width / 2);
            const popoverLeft = popoverRect.left;
            const arrowLeft = triggerCenter - popoverLeft;
            arrow.style.left = `${arrowLeft}px`;
            arrow.style.top = '';
        } else {
            const triggerMiddle = triggerRect.top + (triggerRect.height / 2);
            const popoverTop = popoverRect.top;
            const arrowTop = triggerMiddle - popoverTop;
            arrow.style.top = `${arrowTop}px`;
            arrow.style.left = '';
        }
    }

    /**
     * Show popover
     *
     * Displays popover with calculated position, applies animations,
     * sets up event handlers, and registers in active popovers map.
     *
     * @param {HTMLElement} trigger - The trigger element
     * @param {Object} [options={}] - Configuration options (merged with defaults)
     *
     * @example
     * const trigger = document.querySelector('#myButton');
     * showPopover(trigger, { placement: 'top', animation: 'scale' });
     */
    function showPopover(trigger, options = {}) {
        const config = { ...defaults, ...options };

        // Get popover element
        const popoverId = trigger.getAttribute('aria-describedby') || trigger.getAttribute('data-popover');
        if (!popoverId) {
            console.warn('No popover ID found for trigger', trigger);
            return;
        }

        const popover = document.getElementById(popoverId);
        if (!popover) {
            console.warn('Popover element not found:', popoverId);
            return;
        }

        // Check if already visible
        if (activePopovers.has(popoverId)) return;

        // Show popover (initially invisible for measurement)
        popover.style.visibility = 'hidden';
        popover.style.display = 'block';

        // Calculate position
        const position = calculatePosition(trigger, popover, config.placement, config.offset);

        // Apply position
        popover.style.top = `${position.top}px`;
        popover.style.left = `${position.left}px`;

        // Position arrow if exists
        const arrow = popover.querySelector('.arrow');
        if (arrow && config.arrow) {
            positionArrow(trigger, popover, arrow, position.placement);
        }

        // Apply animation class
        if (config.animation !== 'none') {
            popover.classList.add(`popover-${config.animation}`);
        }

        // Make visible
        popover.style.visibility = 'visible';
        popover.classList.add('popover-visible');

        // Update ARIA
        trigger.setAttribute('aria-expanded', 'true');
        popover.setAttribute('data-placement', position.placement);

        // Register active popover
        activePopovers.set(popoverId, {
            trigger,
            popover,
            config
        });

        // Attach document click handler if needed (with delay to avoid immediate closure)
        if (config.closeOnOutsideClick && !documentClickHandlerAttached) {
            setTimeout(() => {
                attachDocumentClickHandler();
            }, 0);
        }

        // Call onShow callback
        if (config.onShow && typeof config.onShow === 'function') {
            config.onShow(popover, trigger);
        }

        // Emit custom event
        const event = new CustomEvent('popover:show', { detail: { popover, trigger } });
        document.dispatchEvent(event);
    }

    /**
     * Hide popover
     *
     * Hides popover with optional delay, removes from active registry,
     * updates ARIA, and fires callbacks/events.
     *
     * @param {HTMLElement} trigger - The trigger element
     * @param {boolean} [immediate=false] - Skip hide delay if true
     *
     * @example
     * hidePopover(trigger); // Uses configured delay
     * hidePopover(trigger, true); // Immediate hide
     */
    function hidePopover(trigger, immediate = false) {
        const popoverId = trigger.getAttribute('aria-describedby') || trigger.getAttribute('data-popover');
        if (!popoverId) return;

        const popover = document.getElementById(popoverId);
        if (!popover) return;

        const activePopover = activePopovers.get(popoverId);
        if (!activePopover) return;

        // Hide function
        const performHide = () => {
            popover.style.display = 'none';
            popover.classList.remove('popover-visible');
            trigger.setAttribute('aria-expanded', 'false');

            // Remove from active registry
            activePopovers.delete(popoverId);

            // Call onHide callback
            if (activePopover.config.onHide && typeof activePopover.config.onHide === 'function') {
                activePopover.config.onHide(popover, trigger);
            }

            // Emit custom event
            const event = new CustomEvent('popover:hide', { detail: { popover, trigger } });
            document.dispatchEvent(event);
        };

        if (immediate || activePopover.config.delay.hide === 0) {
            performHide();
        } else {
            setTimeout(performHide, activePopover.config.delay.hide);
        }
    }

    /**
     * Toggle popover visibility
     *
     * Shows popover if hidden, hides if visible.
     *
     * @param {HTMLElement} trigger - The trigger element
     * @param {Object} [options={}] - Configuration options for showing
     *
     * @example
     * const trigger = document.querySelector('#myButton');
     * togglePopover(trigger);
     */
    function togglePopover(trigger, options = {}) {
        const popoverId = trigger.getAttribute('aria-describedby') || trigger.getAttribute('data-popover');
        if (activePopovers.has(popoverId)) {
            hidePopover(trigger);
        } else {
            showPopover(trigger, options);
        }
    }

    /**
     * Hide all active popovers
     *
     * Immediately closes all currently visible popovers.
     * Useful for cleanup or when opening modal dialogs.
     *
     * @example
     * hideAllPopovers();
     */
    function hideAllPopovers() {
        activePopovers.forEach((data, popoverId) => {
            hidePopover(data.trigger, true);
        });
    }

    /**
     * Document click handler for closing popovers
     *
     * Attached once when first popover with closeOnOutsideClick is shown.
     * Checks if click is outside both trigger and popover, then closes.
     */
    function attachDocumentClickHandler() {
        if (documentClickHandlerAttached) return;

        document.addEventListener('click', function(e) {
            activePopovers.forEach((data, popoverId) => {
                const { trigger, popover, config } = data;

                if (!config.closeOnOutsideClick) return;

                // Check if click is outside both trigger and popover
                if (!trigger.contains(e.target) && !popover.contains(e.target)) {
                    hidePopover(trigger, true);
                }
            });
        });

        documentClickHandlerAttached = true;
    }

    /**
     * Escape key handler
     *
     * Closes all popovers when Escape key is pressed.
     *
     * @param {KeyboardEvent} e - Keyboard event
     */
    function handleEscapeKey(e) {
        if (e.key === 'Escape' || e.keyCode === 27) {
            hideAllPopovers();
        }
    }

    /**
     * Initialize popovers from data attributes
     *
     * Finds all elements with data-toggle="popover" or data-toggle="tooltip",
     * parses their configuration from data attributes, and attaches appropriate
     * event listeners based on trigger mode.
     *
     * @example
     * // Called automatically on DOM ready
     * // Can be called manually after dynamic content:
     * initializePopovers();
     */
    function initializePopovers() {
        const triggers = document.querySelectorAll('[data-toggle="popover"], [data-toggle="tooltip"]');

        triggers.forEach(trigger => {
            const isTooltip = trigger.getAttribute('data-toggle') === 'tooltip';

            // Parse options from data attributes
            const options = {
                trigger: trigger.getAttribute('data-trigger') || (isTooltip ? 'hover' : 'click'),
                placement: trigger.getAttribute('data-placement') || 'auto',
                offset: parseInt(trigger.getAttribute('data-offset')) || 10,
                animation: trigger.getAttribute('data-animation') || 'fade',
                arrow: trigger.getAttribute('data-arrow') !== 'false'
            };

            // Attach event listeners based on trigger type
            if (options.trigger === 'click') {
                trigger.addEventListener('click', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    togglePopover(trigger, options);
                });
            } else if (options.trigger === 'hover') {
                setupHoverTrigger(trigger, options);
            } else if (options.trigger === 'focus') {
                trigger.addEventListener('focus', function() {
                    showPopover(trigger, options);
                });

                trigger.addEventListener('blur', function() {
                    hidePopover(trigger);
                });
            }

            // Set initial ARIA attributes
            trigger.setAttribute('aria-expanded', 'false');
        });
    }

    /**
     * Setup hover trigger with intent detection
     *
     * Handles mouseenter/mouseleave for both trigger and popover.
     * Keeps popover open when hovering over it.
     *
     * @param {HTMLElement} trigger - The trigger element
     * @param {Object} options - Configuration options
     */
    function setupHoverTrigger(trigger, options) {
        let hoverTimeout;

        trigger.addEventListener('mouseenter', function() {
            clearTimeout(hoverTimeout);
            hoverTimeout = setTimeout(() => {
                showPopover(trigger, options);
            }, options.delay?.show || 0);
        });

        trigger.addEventListener('mouseleave', function() {
            clearTimeout(hoverTimeout);
            hoverTimeout = setTimeout(() => {
                hidePopover(trigger);
            }, options.delay?.hide || 200);
        });

        // Keep popover open when hovering over it
        const popoverId = trigger.getAttribute('aria-describedby') || trigger.getAttribute('data-popover');
        const popover = document.getElementById(popoverId);

        if (popover) {
            popover.addEventListener('mouseenter', function() {
                clearTimeout(hoverTimeout);
            });

            popover.addEventListener('mouseleave', function() {
                hoverTimeout = setTimeout(() => {
                    hidePopover(trigger);
                }, options.delay?.hide || 200);
            });
        }
    }

    /**
     * Update popover position
     *
     * Recalculates and applies position for a currently visible popover.
     * Useful when trigger element moves or viewport changes.
     *
     * @param {HTMLElement} trigger - The trigger element
     *
     * @example
     * // Update after layout changes
     * updatePopoverPosition(trigger);
     */
    function updatePopoverPosition(trigger) {
        const popoverId = trigger.getAttribute('aria-describedby') || trigger.getAttribute('data-popover');
        const activePopover = activePopovers.get(popoverId);

        if (!activePopover) return;

        const { popover, config } = activePopover;
        const position = calculatePosition(trigger, popover, config.placement, config.offset);

        popover.style.top = `${position.top}px`;
        popover.style.left = `${position.left}px`;

        const arrow = popover.querySelector('.arrow');
        if (arrow && config.arrow) {
            positionArrow(trigger, popover, arrow, position.placement);
        }
    }

    /**
     * Update all active popover positions
     *
     * Recalculates positions for all currently visible popovers.
     * Called automatically on scroll and resize events.
     *
     * @example
     * updateAllPopoverPositions();
     */
    function updateAllPopoverPositions() {
        activePopovers.forEach((data, popoverId) => {
            updatePopoverPosition(data.trigger);
        });
    }

    /**
     * Handle scroll events
     *
     * Updates all popover positions when page scrolls
     */
    function handleScroll() {
        updateAllPopoverPositions();
    }

    /**
     * Handle resize events with debouncing
     *
     * Updates all popover positions when window resizes
     */
    let resizeTimeout;
    function handleResize() {
        clearTimeout(resizeTimeout);
        resizeTimeout = setTimeout(updateAllPopoverPositions, 100);
    }

    /**
     * Initialize event listeners
     */
    document.addEventListener('keydown', handleEscapeKey);
    window.addEventListener('scroll', handleScroll);
    window.addEventListener('resize', handleResize);

    /**
     * Initialize on DOM ready or immediately if already loaded
     */
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializePopovers);
    } else {
        initializePopovers();
    }

    /**
     * Public API
     *
     * Exposed methods for popover management
     *
     * @namespace RSL.Popover
     * @property {Function} show - Show a popover
     * @property {Function} hide - Hide a popover
     * @property {Function} toggle - Toggle popover visibility
     * @property {Function} hideAll - Hide all popovers
     * @property {Function} updatePosition - Update single popover position
     * @property {Function} updateAll - Update all popover positions
     * @property {Function} init - Initialize/reinitialize popovers
     */
    window.RSL = window.RSL || {};
    window.RSL.Popover = {
        show: showPopover,
        hide: hidePopover,
        toggle: togglePopover,
        hideAll: hideAllPopovers,
        updatePosition: updatePopoverPosition,
        updateAll: updateAllPopoverPositions,
        init: initializePopovers
    };

})();
