<?php

/**
 * CDN URL Rewrite Module
 *
 * Rewrites local static asset URLs (CSS, JS, images, fonts) to a configured
 * CDN hostname in the final HTML output. Operates on frontend only — skips
 * admin, login, REST, AJAX, feed, and sitemap contexts.
 *
 * @package ProRank\SEO\Modules\Performance
 * @since   1.0.11
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\Performance;

use ProRank\SEO\Modules\BaseModule;

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

class CdnRewriteModule extends BaseModule {

    protected string $slug = 'cdn_rewrite';

    protected string $name = 'CDN URL Rewrite';

    protected string $description = 'Rewrite static asset URLs to a CDN hostname for faster delivery.';

    protected string $feature_tier = 'free';

    protected ?string $parent_slug = 'support-account';

    /**
     * Default asset directories to rewrite.
     */
    private const DEFAULT_DIRECTORIES = 'wp-content,wp-includes';

    /**
     * Default file extensions to rewrite.
     */
    private const DEFAULT_EXTENSIONS = 'css,js,jpg,jpeg,png,gif,webp,avif,svg,ico,woff,woff2,ttf,eot,mp4,webm,pdf';

    /**
     * Get setting from cache settings option.
     */
    protected function get_setting(string $key, $default = null) {
        static $settings = null;
        if ($settings === null) {
            $settings = get_option('prorank_cache_settings', []);
        }
        return $settings[$key] ?? $default;
    }

    public function init_hooks(): void {
        // Always listen for setting changes so we can flush caches on toggle.
        add_action('update_option_prorank_cache_settings', [$this, 'on_settings_changed'], 10, 2);

        $cdn_url = $this->get_cdn_url();
        if ($cdn_url === '') {
            return;
        }

        if ($this->is_excluded_context()) {
            return;
        }

        // Rewrite enqueued style/script URLs.
        add_filter('style_loader_src', [$this, 'rewrite_asset_url'], 9999);
        add_filter('script_loader_src', [$this, 'rewrite_asset_url'], 9999);

        // Rewrite URLs in the_content (images, links to static files).
        add_filter('the_content', [$this, 'rewrite_content_urls'], 9999);

        // Rewrite srcset URLs.
        add_filter('wp_calculate_image_srcset', [$this, 'rewrite_srcset_urls'], 9999, 5);
    }

    /**
     * Rewrite a single asset URL to CDN.
     *
     * @param mixed $src Source URL from WP loader — may be non-string.
     * @return mixed
     */
    public function rewrite_asset_url($src) {
        if (!is_string($src) || $src === '') {
            return $src;
        }

        return $this->maybe_rewrite($src);
    }

    /**
     * Rewrite static asset URLs inside post content.
     */
    public function rewrite_content_urls(string $content): string {
        $cdn_url = $this->get_cdn_url();
        $site_url = $this->get_site_url();

        if ($cdn_url === '' || $site_url === '') {
            return $content;
        }

        $directories = $this->get_rewrite_directories();
        foreach ($directories as $dir) {
            $content = str_replace(
                $site_url . '/' . $dir . '/',
                $cdn_url . '/' . $dir . '/',
                $content
            );
        }

        return $content;
    }

    /**
     * Rewrite srcset URLs.
     *
     * @param array $sources Array of srcset source arrays.
     * @return array
     */
    public function rewrite_srcset_urls($sources, $size_array = [], $image_src = '', $image_meta = [], $attachment_id = 0): array {
        if (!is_array($sources)) {
            return $sources;
        }

        foreach ($sources as &$source) {
            if (isset($source['url']) && is_string($source['url'])) {
                $source['url'] = $this->maybe_rewrite($source['url']);
            }
        }

        return $sources;
    }

    /**
     * Conditionally rewrite a URL to the CDN hostname.
     */
    private function maybe_rewrite(string $url): string {
        $cdn_url = $this->get_cdn_url();
        $site_url = $this->get_site_url();

        if ($cdn_url === '' || $site_url === '') {
            return $url;
        }

        // Only rewrite URLs that belong to this site.
        if (strpos($url, $site_url) !== 0) {
            return $url;
        }

        // Only rewrite URLs in allowed directories.
        $relative = substr($url, strlen($site_url));
        $directories = $this->get_rewrite_directories();
        $in_allowed_dir = false;
        foreach ($directories as $dir) {
            if (strpos($relative, '/' . $dir . '/') === 0) {
                $in_allowed_dir = true;
                break;
            }
        }

        if (!$in_allowed_dir) {
            return $url;
        }

        // Check exclusions.
        $exclusions = $this->get_exclusions();
        foreach ($exclusions as $pattern) {
            if ($pattern !== '' && stripos($url, $pattern) !== false) {
                return $url;
            }
        }

        return str_replace($site_url, $cdn_url, $url);
    }

    /**
     * Get the configured CDN URL, normalised.
     */
    private function get_cdn_url(): string {
        static $cdn_url = null;
        if ($cdn_url === null) {
            $raw = (string) $this->get_setting('cdn_rewrite_url', '');
            $cdn_url = rtrim($raw, '/');
        }
        return $cdn_url;
    }

    /**
     * Get the site URL, normalised.
     */
    private function get_site_url(): string {
        static $site_url = null;
        if ($site_url === null) {
            $site_url = rtrim(site_url(), '/');
        }
        return $site_url;
    }

    /**
     * Get the list of directories to rewrite.
     */
    private function get_rewrite_directories(): array {
        $raw = (string) $this->get_setting('cdn_rewrite_directories', self::DEFAULT_DIRECTORIES);
        return array_filter(array_map('trim', explode(',', $raw)));
    }

    /**
     * Get URL exclusion patterns.
     */
    private function get_exclusions(): array {
        $raw = (string) $this->get_setting('cdn_rewrite_exclusions', '');
        return array_filter(array_map('trim', explode(',', $raw)));
    }

    /**
     * Flush all relevant caches when CDN rewrite settings change.
     *
     * Triggered by update_option_prorank_cache_settings. Clears ProRank HTML
     * cache, Elementor rendered cache, WP object cache, and WP Rocket cache
     * so stale CDN-rewritten markup is not served after enabling or disabling.
     *
     * @param mixed $old_value Previous option value.
     * @param mixed $new_value New option value.
     */
    public function on_settings_changed($old_value, $new_value): void {
        $old_cdn = is_array($old_value) ? ($old_value['cdn_rewrite_url'] ?? '') : '';
        $new_cdn = is_array($new_value) ? ($new_value['cdn_rewrite_url'] ?? '') : '';

        // Only flush when the CDN URL actually changed.
        if ($old_cdn === $new_cdn) {
            return;
        }

        // 1. ProRank HTML cache.
        do_action('prorank_clear_cache');

        // 2. Elementor rendered element cache.
        if (class_exists('\Elementor\Plugin')) {
            $elementor = \Elementor\Plugin::instance();
            if (isset($elementor->files_manager) && method_exists($elementor->files_manager, 'clear_cache')) {
                $elementor->files_manager->clear_cache();
            }
            // Also clear per-element rendered cache in postmeta.
            global $wpdb;
            $wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_elementor_element_cache'");
        }

        // 3. WP object cache.
        wp_cache_flush();

        // 4. WP Rocket (if active).
        if (function_exists('rocket_clean_domain')) {
            rocket_clean_domain();
        }
    }

    /**
     * Check if the current context should be excluded from CDN rewriting.
     */
    private function is_excluded_context(): bool {
        if (is_admin()) {
            return true;
        }

        if (defined('REST_REQUEST') && REST_REQUEST) {
            return true;
        }

        if (wp_doing_ajax()) {
            return true;
        }

        if (wp_doing_cron()) {
            return true;
        }

        if (defined('WP_CLI') && WP_CLI) {
            return true;
        }

        $uri = $_SERVER['REQUEST_URI'] ?? '';
        if (preg_match('/sitemap.*\.xml/i', $uri)) {
            return true;
        }

        if (preg_match('/\/feed\/?$/i', $uri)) {
            return true;
        }

        // Login page.
        if (in_array($GLOBALS['pagenow'] ?? '', ['wp-login.php', 'wp-register.php'], true)) {
            return true;
        }

        return false;
    }
}
