<?php
/**
 * CSS Deferrer Service
 *
 * Defers non-critical CSS loading for improved page performance.
 * Uses the media="print" technique with onload swap for async loading.
 *
 * @package Mamba\Modules\CriticalCss\Services
 * @since   1.1.0
 */

namespace Mamba\Modules\CriticalCss\Services;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Class CssDeferrer
 *
 * Handles CSS deferring for non-critical stylesheets.
 * Premium feature that works in conjunction with Critical CSS inlining.
 *
 * @since 1.1.0
 */
final class CssDeferrer {
    
    /**
     * Stylesheets that should never be deferred
     */
    private const EXCLUDED_HANDLES = [
        'admin-bar',
        'dashicons',
        'mamba-critical-css',
        'wp-block-library', // May contain critical layout styles
    ];
    
    /**
     * Stylesheets that are safe to defer
     */
    private const DEFERRABLE_HANDLES = [
        'woocommerce-smallscreen',
        'woocommerce-layout',
        'woocommerce-general',
        'wc-block-style',
        'wc-block-vendors-style',
        'wp-block-library-theme',
        'classic-theme-styles',
        'global-styles',
    ];
    
    /**
     * Track deferred stylesheets for noscript fallback
     */
    private array $deferredStylesheets = [];
    
    /**
     * Defer a stylesheet by modifying its link tag
     *
     * @param string $tag    The link tag HTML
     * @param string $handle The stylesheet handle
     * @param string $href   The stylesheet URL
     * @param string $media  The media attribute
     * @return string Modified link tag
     */
    public function deferStylesheet(string $tag, string $handle, string $href, string $media): string {
        // Skip if not enabled
        if (!get_option('mamba_enable_css_defer', 0)) {
            return $tag;
        }
        
        // Skip excluded handles
        if (in_array($handle, self::EXCLUDED_HANDLES, true)) {
            return $tag;
        }
        
        // Check if this handle should be deferred
        if (!$this->shouldDefer($handle)) {
            return $tag;
        }
        
        // Track for noscript fallback
        $this->deferredStylesheets[] = [
            'handle' => $handle,
            'href' => $href,
            'media' => $media,
        ];
        
        // Apply the media="print" onload technique
        // This loads CSS asynchronously without blocking render
        $deferredTag = $this->createDeferredTag($tag, $handle, $href, $media);
        
        return $deferredTag;
    }
    
    /**
     * Check if a stylesheet should be deferred
     */
    private function shouldDefer(string $handle): bool {
        // Check user-defined deferrable handles (Premium)
        $userDeferrable = $this->getUserDeferrableHandles();
        if (in_array($handle, $userDeferrable, true)) {
            return true;
        }
        
        // Check default deferrable handles
        if (in_array($handle, self::DEFERRABLE_HANDLES, true)) {
            return true;
        }
        
        // Check if defer all is enabled (Premium)
        if (get_option('mamba_css_defer_all', 0)) {
            // Defer all except excluded
            return !in_array($handle, self::EXCLUDED_HANDLES, true);
        }
        
        return false;
    }
    
    /**
     * Get user-defined deferrable handles
     */
    private function getUserDeferrableHandles(): array {
        $handles = get_option('mamba_css_defer_handles', '');
        if (empty($handles)) {
            return [];
        }
        
        return array_filter(array_map('trim', explode("\n", $handles)));
    }
    
    /**
     * Create a deferred link tag
     */
    private function createDeferredTag(string $tag, string $handle, string $href, string $media): string {
        // Escape for use in JavaScript
        $escapedMedia = esc_attr($media ?: 'all');
        
        // Build the deferred tag using media="print" technique
        // When the stylesheet loads, we swap media to the original value
        $deferredTag = sprintf(
            '<link rel="stylesheet" id="%s-css" href="%s" media="print" onload="this.media=\'%s\'" data-mamba-deferred="1">',
            esc_attr($handle),
            esc_url($href),
            $escapedMedia
        );
        
        return $deferredTag . "\n";
    }
    
    /**
     * Add noscript fallback for deferred stylesheets
     * Called in wp_footer to ensure all deferred styles have a fallback
     */
    public function addNoscriptFallback(): void {
        if (empty($this->deferredStylesheets)) {
            return;
        }
        
        echo "\n<noscript id=\"mamba-deferred-css-fallback\">\n";
        
        foreach ($this->deferredStylesheets as $stylesheet) {
            printf(
                '<link rel="stylesheet" href="%s" media="%s">',
                esc_url($stylesheet['href']),
                esc_attr($stylesheet['media'] ?: 'all')
            );
            echo "\n";
        }
        
        echo "</noscript>\n";
    }
    
    /**
     * Get list of deferred stylesheets
     */
    public function getDeferredStylesheets(): array {
        return $this->deferredStylesheets;
    }
    
    /**
     * Get excluded handles
     */
    public function getExcludedHandles(): array {
        return self::EXCLUDED_HANDLES;
    }
    
    /**
     * Get default deferrable handles
     */
    public function getDeferrableHandles(): array {
        return self::DEFERRABLE_HANDLES;
    }
    
    /**
     * Save user-defined deferrable handles (Premium)
     */
    public function saveUserDeferrableHandles(array $handles): bool {
        $handles = array_map('sanitize_text_field', $handles);
        return update_option('mamba_css_defer_handles', implode("\n", $handles));
    }
    
    /**
     * Add a handle to the exclusion list (Premium)
     */
    public function addExcludedHandle(string $handle): bool {
        $excluded = get_option('mamba_css_defer_excluded', []);
        if (!is_array($excluded)) {
            $excluded = [];
        }
        
        if (!in_array($handle, $excluded, true)) {
            $excluded[] = sanitize_text_field($handle);
            return update_option('mamba_css_defer_excluded', $excluded);
        }
        
        return true;
    }
    
    /**
     * Remove a handle from the exclusion list (Premium)
     */
    public function removeExcludedHandle(string $handle): bool {
        $excluded = get_option('mamba_css_defer_excluded', []);
        if (!is_array($excluded)) {
            return true;
        }
        
        $excluded = array_filter($excluded, function($h) use ($handle) {
            return $h !== $handle;
        });
        
        return update_option('mamba_css_defer_excluded', array_values($excluded));
    }
}
