<?php
/**
 * Effects Manager - Manages locally bundled seasonal effects.
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

class Seazonify_Effects {

    /**
     * Get the app token from plugin settings.
     */
    private static function get_app_token() {
        $settings = get_option('seazonify_settings', array());
        return isset($settings['app_token']) ? $settings['app_token'] : '';
    }

    /**
     * Fetch effects from local JSON files.
     */
    private static function fetch_locally($type, $page = 1) {
        // Normalize types: visual/visuals, audio/audios, combo/combos
        $type = rtrim(strtolower($type), 's');
        $type = ($type === 'combo') ? 'combo' : $type; // Redundant but clear
        
        $page_file = SEAZONIFY_PLUGIN_DIR . "assets/effects/{$type}/{$type}-{$page}.json";

        if (!file_exists($page_file)) {
            return new WP_Error('file_not_found', __('Effects page not found locally.', 'seazonify'));
        }

        $content = file_get_contents($page_file);
        $data = json_decode($content, true);

        if (empty($data) || !isset($data['effects'])) {
            return new WP_Error('invalid_json', __('Invalid local effects data.', 'seazonify'));
        }

        // Sanitize all paths to be full local URLs
        $data['effects'] = array_map(function($effect) use ($type) {
            return self::sanitize_effect_data($effect, $type);
        }, $data['effects']);

        return $data;
    }

    /**
     * Sanitize effect data by ensuring local paths are relative to the plugin root.
     * This allows the JS controller's Self-Aware resolver to handle the final URL.
     */
    public static function sanitize_effect_data($effect, $type) {
        $fields = array('thumbnail', 'visualJsUrl', 'audioJsUrl', 'jsUrl', 'audioUrl', 'url', 'file', 'js_url');

        foreach ($fields as $field) {
            if (!empty($effect[$field]) && strpos($effect[$field], 'http') === false) {
                // Remove any existing redundant prefixes to find the "clean" relative path
                $path = ltrim($effect[$field], '/');
                $path = str_replace('assets/effects/', '', $path);
                
                // Add the type prefix (e.g. 'visual/') if missing and needed for JS/Audio files
                if ($field !== 'thumbnail' && strpos($path, $type . '/') === false) {
                    $path = $type . '/' . $path;
                }
                
                // FORCE ABSOLUTE: Always prepend the absolute SEAZONIFY_EFFECTS_URL
                // This ensures that even if saved as relative in DB/JSON, it's served as absolute.
                $effect[$field] = SEAZONIFY_EFFECTS_URL . ltrim($path, '/');
            }
        }

        return $effect;
    }

    /**
     * Get the "Clean" content of an effect JS file.
     * Rewrites any internal CDN links to local plugin links on the fly.
     */
    public static function get_effect_content($effect_id, $type = 'visual') {
        $effect = self::get_effect($effect_id, $type);
        if (!$effect || empty($effect['jsUrl'])) {
            return '';
        }

        // Convert URL to absolute path
        $path = str_replace(SEAZONIFY_PLUGIN_URL, SEAZONIFY_PLUGIN_DIR, $effect['jsUrl']);
        
        if (!file_exists($path)) {
            return '';
        }

        $content = file_get_contents($path);
        $local_base = SEAZONIFY_EFFECTS_URL . $type . '/';
        
        // 1. Fix relative internal assets (e.g. "../assets/img/..." or "assets/effects/...")

        // This makes images and sounds called within the JS absolutely referenced
        $content = preg_replace_callback('/([\'"])(?:\.\.\/)?assets\/([^\'"]+)([\'"])/', function($m) {
            return $m[1] . SEAZONIFY_PLUGIN_URL . 'assets/' . $m[2] . $m[3];
        }, $content);
        
        return $content;
    }

    /**
     * Get initial effects (first page of each type) for admin panel / customizer load.
     */
    public static function get_initial_effects() {
        $cached = get_transient('seazonify_initial_effects');
        if ($cached !== false) {
            return $cached;
        }

        $effects = array('visual' => array(), 'audio' => array(), 'combos' => array());
        
        // In the Lite/Bundled version, we always start with Local Effects
        foreach (array('visual', 'audio', 'combo') as $type) {
            $page_data = self::fetch_locally($type, 1);
            if (!is_wp_error($page_data) && isset($page_data['effects'])) {
                $key = $type === 'combo' ? 'combos' : $type;
                $effects[$key] = $page_data['effects'];
            }
        }

        set_transient('seazonify_initial_effects', $effects, 3600);
        return $effects;
    }

    /**
     * Load a specific page of effects for a type (used in AJAX pagination/search).
     */
    public static function load_effects_page($type, $page = 1, $search = '') {
        // Normalize input type (handling both singular and plural from UI)
        $type = rtrim(strtolower($type), 's');
        $type = ($type === 'combo') ? 'combo' : $type;

        $cache_key = "seazonify_{$type}_page_{$page}" . (!empty($search) ? "_" . md5($search) : "");
        $cached    = get_transient($cache_key);
        if ($cached !== false) {
            // Failsafe: Ensure all cached effects are sanitized to be absolute URLs
            if (isset($cached['effects']) && is_array($cached['effects'])) {
                foreach ($cached['effects'] as &$effect) {
                    $effect = self::sanitize_effect_data($effect, $type);
                }
            }
            return $cached;
        }

        // Use local storage for the core bundled experience
        $data = self::fetch_locally($type, $page);

        if (is_wp_error($data)) {
            return $data;
        }

        // Apply search filter locally if requested
        if (!empty($search)) {
            $data['effects'] = array_filter($data['effects'], function($e) use ($search) {
                return stripos($e['name'], $search) !== false || stripos($e['description'], $search) !== false;
            });
        }

        set_transient($cache_key, $data, 3600);
        return $data;
    }

    /**
     * Get a single effect by ID and type.
     */
    public static function get_effect($effect_id, $type = 'visual') {
        $type = ($type === 'combos') ? 'combo' : $type;

        // Load the first page to find the effect
        $page_data = self::load_effects_page($type, 1);
        if (is_wp_error($page_data)) return null;

        foreach ($page_data['effects'] as $effect) {
            if (isset($effect['id']) && (string) $effect['id'] === (string) $effect_id) {
                return $effect;
            }
        }

        // If not found in page 1, check other pages (simplified for local)
        if ($page_data['total_pages'] > 1) {
            for ($p = 2; $p <= $page_data['total_pages']; $p++) {
                $p_data = self::load_effects_page($type, $p);
                if (is_wp_error($p_data)) continue;
                foreach ($p_data['effects'] as $e) {
                    if (isset($e['id']) && (string) $e['id'] === (string) $effect_id) return $e;
                }
            }
        }

        return null;
    }

    /**
     * Get the JS URL for a specific effect.
     */
    public static function get_effect_url($effect_id, $type = 'visual') {
        $effect = self::get_effect($effect_id, $type);
        if (!$effect) return false;

        if ($type === 'visual' && isset($effect['visualJsUrl'])) return $effect['visualJsUrl'];
        if ($type === 'audio' && isset($effect['audioJsUrl'])) return $effect['audioJsUrl'];
        if ($type === 'combo' && isset($effect['visualJsUrl'])) return $effect['visualJsUrl'];

        return false;
    }

    /**
     * Get controller URL.
     */
    public static function get_controller_url() {
        return SEAZONIFY_CONTROLLER_URL;
    }

    /**
     * Get CDN base URL (now points to local assets).
     */
    public static function get_cdn_base_url() {
        return SEAZONIFY_CDN_BASE;
    }

    /**
     * Clear all effects cache (index + all pages).
     */
    public static function clear_cache() {
        global $wpdb;

        // Concatenate prefix and like pattern safely for prepare
        $pattern = '_transient_seazonify%';
        $timeout_pattern = '_transient_timeout_seazonify%';

        // We use a direct query here because WordPress does not provide a native 
        // function to delete transients by prefix. This is the most performant way.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
                $pattern,
                $timeout_pattern
            )
        );

        // Clear object cache if an external persistent cache (like Redis/Memcached) is used.
        if (function_exists('wp_cache_flush_runtime')) {
            wp_cache_flush_runtime();
        }
    }

    /**
     * Fetch effects index for a given type.
     */
    public static function get_type_index($type) {
        // Normalize type
        $type = rtrim(strtolower($type), 's');
        $type = ($type === 'combo') ? 'combo' : $type;

        $index_file = SEAZONIFY_PLUGIN_DIR . "assets/effects/{$type}/index.json";

        if (!file_exists($index_file)) {
            return array('files' => array());
        }

        $data = json_decode(file_get_contents($index_file), true);
        return $data ?: array('files' => array());
    }

    /**
     * Get all type indexes
     */
    public static function get_all_indexes() {
        return array(
            'visual' => self::get_type_index('visual'),
            'audio'  => self::get_type_index('audio'),
            'combo'  => self::get_type_index('combo'),
        );
    }
}

