<?php
/**
 * JavaScript Delayer Service
 *
 * Delays JavaScript execution until user interaction to improve Core Web Vitals.
 * Scripts are not loaded until the user interacts with the page (mouse move, scroll, 
 * keydown, touchstart), significantly improving INP, TBT, and FCP metrics.
 *
 * @package Mamba\Modules\Bloat\Services
 * @since   1.0.0
 */

namespace Mamba\Modules\Bloat\Services;

defined( 'ABSPATH' ) || exit;

/**
 * Class JsDelayer
 *
 * Handles JavaScript delay functionality with WooCommerce-aware defaults.
 * Premium feature that delays non-critical scripts until user interaction.
 *
 * @since 1.0.0
 */
final class JsDelayer {
    
    /**
     * Scripts that are safe to delay by default on WooCommerce stores
     */
    private const DEFAULT_DELAY_KEYWORDS = [
        'gtag',
        'google-analytics',
        'analytics',
        'ga.js',
        'gtm.js',
        'googletagmanager',
        'fbevents',
        'facebook',
        'pixel',
        'hotjar',
        'clarity',
        'tidio',
        'intercom',
        'crisp',
        'drift',
        'tawk',
        'livechat',
        'zendesk',
        'hubspot',
        'mailchimp',
        'klaviyo',
        'omnisend',
        'recaptcha',
        'hcaptcha',
    ];
    
    /**
     * Scripts that should NEVER be delayed (critical for functionality)
     */
    private const NEVER_DELAY = [
        'jquery-core',
        'jquery',
        'jquery-migrate',
        'woocommerce',
        'wc-checkout',
        'wc-add-to-cart-variation',
        'wc-single-product',
        'wp-hooks',
        'wp-i18n',
        'wp-polyfill',
        'react',
        'react-dom',
        'wp-element',
        'wp-components',
        'wp-blocks',
        'wp-data',
        'wp-editor',
        'mamba-',
    ];
    
    /**
     * User interaction events that trigger script loading
     */
    private const INTERACTION_EVENTS = [
        'mousemove',
        'mousedown',
        'mouseup',
        'mouseover',
        'mouseout',
        'touchstart',
        'touchend',
        'touchmove',
        'keydown',
        'keyup',
        'wheel',
        'scroll',
        'click',
    ];
    
    /**
     * Default timeout in milliseconds (fallback if no interaction)
     */
    private const DEFAULT_TIMEOUT = 5000;
    
    /**
     * Whether the feature is enabled
     */
    private bool $enabled = false;
    
    /**
     * Delay timeout in milliseconds
     */
    private int $timeout;
    
    /**
     * Keywords to match for delaying scripts
     */
    private array $delayKeywords = [];
    
    /**
     * Keywords to exclude from delaying
     */
    private array $exclusions = [];
    
    /**
     * Collected scripts to delay
     */
    private array $delayedScripts = [];
    
    /**
     * Constructor
     */
    public function __construct() {
        $this->enabled = (bool) get_option('mamba_enable_js_delay', 0);
        $this->timeout = (int) get_option('mamba_js_delay_timeout', self::DEFAULT_TIMEOUT);
        
        // Get user-configured delay keywords, fall back to defaults
        $userKeywords = get_option('mamba_js_delay_scripts', '');
        if (!empty($userKeywords)) {
            $this->delayKeywords = array_filter(array_map('trim', explode("\n", $userKeywords)));
        } else {
            $this->delayKeywords = self::DEFAULT_DELAY_KEYWORDS;
        }
        
        // Get user exclusions
        $userExclusions = get_option('mamba_js_delay_exclusions', '');
        if (!empty($userExclusions)) {
            $this->exclusions = array_filter(array_map('trim', explode("\n", $userExclusions)));
        }
    }
    
    /**
     * Check if JS delay is enabled
     */
    public function isEnabled(): bool {
        return $this->enabled;
    }
    
    /**
     * Filter script tags to delay matching scripts
     * 
     * @param string $tag    The script tag HTML
     * @param string $handle The script handle
     * @param string $src    The script source URL
     * @return string Modified script tag
     */
    public function maybeDelayScript(string $tag, string $handle, string $src): string {
        // Skip if not enabled
        if (!$this->enabled) {
            return $tag;
        }
        
        // Skip admin pages
        if (is_admin()) {
            return $tag;
        }
        
        // Skip if already processed
        if (strpos($tag, 'data-mamba-delay') !== false) {
            return $tag;
        }
        
        // Check if this script should never be delayed
        if ($this->isNeverDelay($handle, $src)) {
            return $tag;
        }
        
        // Check if this script is in user exclusions
        if ($this->isExcluded($handle, $src)) {
            return $tag;
        }
        
        // Check if this script matches delay keywords
        if (!$this->shouldDelay($handle, $src)) {
            return $tag;
        }
        
        // Transform the script tag to delay it
        return $this->transformScriptTag($tag, $handle, $src);
    }
    
    /**
     * Check if script should never be delayed
     */
    private function isNeverDelay(string $handle, string $src): bool {
        foreach (self::NEVER_DELAY as $keyword) {
            if (stripos($handle, $keyword) !== false || stripos($src, $keyword) !== false) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Check if script is in user exclusions
     */
    private function isExcluded(string $handle, string $src): bool {
        foreach ($this->exclusions as $exclusion) {
            if (empty($exclusion)) continue;
            if (stripos($handle, $exclusion) !== false || stripos($src, $exclusion) !== false) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Check if script should be delayed based on keywords
     */
    private function shouldDelay(string $handle, string $src): bool {
        foreach ($this->delayKeywords as $keyword) {
            if (empty($keyword)) continue;
            if (stripos($handle, $keyword) !== false || stripos($src, $keyword) !== false) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Transform a script tag to be delayed
     */
    private function transformScriptTag(string $tag, string $handle, string $src): string {
        // Store original src and replace with data attribute
        $delayedTag = str_replace(
            ' src=',
            ' data-mamba-delay="true" data-mamba-src=',
            $tag
        );
        
        // Change type to prevent execution
        if (strpos($delayedTag, 'type=') !== false) {
            $delayedTag = preg_replace('/type=["\'][^"\']*["\']/', 'type="mamba/delayed"', $delayedTag);
        } else {
            $delayedTag = str_replace('<script ', '<script type="mamba/delayed" ', $delayedTag);
        }
        
        // Track delayed script for logging
        $this->delayedScripts[] = $handle;
        
        return $delayedTag;
    }
    
    /**
     * Output the delay loader script in the footer
     * This script listens for user interaction and loads delayed scripts
     */
    public function outputLoaderScript(): void {
        if (!$this->enabled || is_admin()) {
            return;
        }
        
        $events = implode("','", self::INTERACTION_EVENTS);
        $timeout = (int) $this->timeout;
        
        $loader_js = "(function(){
    var loaded = false;
    var events = ['" . $events . "'];
    var timeout = " . $timeout . ";
    function loadDelayedScripts() {
        if (loaded) return;
        loaded = true;
        events.forEach(function(e) {
            document.removeEventListener(e, loadDelayedScripts, {passive: true});
        });
        var scripts = document.querySelectorAll('script[data-mamba-delay=\"true\"]');
        scripts.forEach(function(oldScript) {
            var newScript = document.createElement('script');
            Array.from(oldScript.attributes).forEach(function(attr) {
                if (attr.name === 'data-mamba-delay') return;
                if (attr.name === 'data-mamba-src') {
                    newScript.src = attr.value;
                    return;
                }
                if (attr.name === 'type' && attr.value === 'mamba/delayed') {
                    newScript.type = 'text/javascript';
                    return;
                }
                newScript.setAttribute(attr.name, attr.value);
            });
            if (oldScript.innerHTML) {
                newScript.innerHTML = oldScript.innerHTML;
            }
            oldScript.parentNode.replaceChild(newScript, oldScript);
        });
    }
    events.forEach(function(e) {
        document.addEventListener(e, loadDelayedScripts, {passive: true, once: true});
    });
    setTimeout(loadDelayedScripts, timeout);
})();";
        wp_register_script('mamba-js-delay-loader', false, [], false, true);
        wp_enqueue_script('mamba-js-delay-loader');
        wp_add_inline_script('mamba-js-delay-loader', $loader_js);
    }
    
    /**
     * Get the list of delayed scripts (for debugging/logging)
     */
    public function getDelayedScripts(): array {
        return $this->delayedScripts;
    }
    
    /**
     * Get default delay keywords
     */
    public static function getDefaultKeywords(): array {
        return self::DEFAULT_DELAY_KEYWORDS;
    }
    
    /**
     * Get never-delay keywords
     */
    public static function getNeverDelayKeywords(): array {
        return self::NEVER_DELAY;
    }
}
