<?php
/**
 * Font Search for Maple Local Fonts.
 *
 * @package Maple_Local_Fonts
 */

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

/**
 * Class MLF_Font_Search
 *
 * Handles searching Google Fonts metadata.
 */
class MLF_Font_Search {

    /**
     * Cache key for fonts metadata.
     *
     * @var string
     */
    const CACHE_KEY = 'mlf_google_fonts_metadata';

    /**
     * Cache duration in seconds (24 hours).
     *
     * @var int
     */
    const CACHE_DURATION = DAY_IN_SECONDS;

    /**
     * Google Fonts metadata URL.
     *
     * @var string
     */
    const METADATA_URL = 'https://fonts.google.com/metadata/fonts';

    /**
     * Maximum metadata response size (10MB).
     *
     * Google Fonts metadata for 1500+ fonts can be 5-8MB.
     *
     * @var int
     */
    const MAX_METADATA_SIZE = 10 * 1024 * 1024;

    /**
     * Rate limiter instance.
     *
     * @var MLF_Rate_Limiter
     */
    private $rate_limiter;

    /**
     * Constructor.
     */
    public function __construct() {
        add_action('wp_ajax_mlf_search_fonts', [$this, 'handle_search']);
        // No nopriv - admin only

        // Initialize rate limiter: 30 requests per minute (search can be rapid while typing)
        $this->rate_limiter = new MLF_Rate_Limiter(30, 60);
    }

    /**
     * Handle font search AJAX request.
     */
    public function handle_search() {
        // 1. NONCE CHECK
        if (!check_ajax_referer('mlf_search_fonts', 'nonce', false)) {
            wp_send_json_error(['message' => __('Security check failed.', 'maple-local-fonts')], 403);
        }

        // 2. CAPABILITY CHECK
        $capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
        if (!current_user_can($capability)) {
            wp_send_json_error(['message' => __('Unauthorized.', 'maple-local-fonts')], 403);
        }

        // 3. RATE LIMIT CHECK
        if (!$this->rate_limiter->check_and_record('search')) {
            wp_send_json_error([
                'message' => __('Too many requests. Please wait a moment and try again.', 'maple-local-fonts'),
            ], 429);
        }

        // 4. INPUT VALIDATION
        $query = isset($_POST['query']) ? sanitize_text_field(wp_unslash($_POST['query'])) : '';

        if (strlen($query) < 2) {
            wp_send_json_success(['fonts' => []]);
        }

        // Limit query length
        if (strlen($query) > 100) {
            wp_send_json_error(['message' => __('Search query too long.', 'maple-local-fonts')]);
        }

        // 5. PROCESS REQUEST
        $fonts = $this->get_fonts_metadata();

        if (is_wp_error($fonts)) {
            wp_send_json_error(['message' => $fonts->get_error_message()]);
        }

        // Search fonts
        $results = $this->search_fonts($fonts, $query);

        wp_send_json_success(['fonts' => $results]);
    }

    /**
     * Get Google Fonts metadata (cached).
     *
     * @return array|WP_Error Array of fonts or error.
     */
    public function get_fonts_metadata() {
        // Check cache first
        $cached = get_transient(self::CACHE_KEY);
        if ($cached !== false) {
            return $cached;
        }

        // Fetch from Google
        $response = wp_remote_get(self::METADATA_URL, [
            'timeout'   => 30,
            'sslverify' => true,
        ]);

        if (is_wp_error($response)) {
            return new WP_Error('fetch_failed', __('Could not fetch fonts list from Google.', 'maple-local-fonts'));
        }

        $status = wp_remote_retrieve_response_code($response);
        if ($status !== 200) {
            return new WP_Error('fetch_failed', __('Google Fonts returned an error.', 'maple-local-fonts'));
        }

        $body = wp_remote_retrieve_body($response);

        // Validate response size
        if (strlen($body) > self::MAX_METADATA_SIZE) {
            return new WP_Error('response_too_large', __('Font metadata response too large.', 'maple-local-fonts'));
        }

        // Validate response is not empty
        if (empty($body)) {
            return new WP_Error('empty_response', __('Empty response from Google Fonts.', 'maple-local-fonts'));
        }

        // Google's metadata has a )]}' prefix for security, remove it
        $body = preg_replace('/^\)\]\}\'\s*/', '', $body);

        $data = json_decode($body, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            return new WP_Error('parse_failed', __('Could not parse fonts data.', 'maple-local-fonts'));
        }

        if (!$data || !isset($data['familyMetadataList'])) {
            return new WP_Error('parse_failed', __('Could not parse fonts data.', 'maple-local-fonts'));
        }

        // Process and simplify the data
        $fonts = [];
        foreach ($data['familyMetadataList'] as $font) {
            $fonts[] = [
                'family'       => $font['family'],
                'category'     => $font['category'] ?? 'sans-serif',
                'variants'     => $font['fonts'] ?? [],
                'subsets'      => $font['subsets'] ?? ['latin'],
                'version'      => $font['version'] ?? '',
                'lastModified' => $font['lastModified'] ?? '',
            ];
        }

        // Cache for 24 hours
        set_transient(self::CACHE_KEY, $fonts, self::CACHE_DURATION);

        return $fonts;
    }

    /**
     * Search fonts by query.
     *
     * @param array  $fonts All fonts.
     * @param string $query Search query.
     * @return array Matching fonts (max 20).
     */
    private function search_fonts($fonts, $query) {
        $query_lower = strtolower($query);
        $exact_matches = [];
        $starts_with = [];
        $contains = [];

        foreach ($fonts as $font) {
            $family_lower = strtolower($font['family']);

            // Exact match
            if ($family_lower === $query_lower) {
                $exact_matches[] = $this->format_font_result($font);
            }
            // Starts with query
            elseif (strpos($family_lower, $query_lower) === 0) {
                $starts_with[] = $this->format_font_result($font);
            }
            // Contains query
            elseif (strpos($family_lower, $query_lower) !== false) {
                $contains[] = $this->format_font_result($font);
            }
        }

        // Combine results: exact matches first, then starts with, then contains
        $results = array_merge($exact_matches, $starts_with, $contains);

        // Limit to 20 results
        return array_slice($results, 0, 20);
    }

    /**
     * Format a font for the search results.
     *
     * @param array $font Font data.
     * @return array Formatted font.
     */
    private function format_font_result($font) {
        // Check if font has variable version
        $has_variable = false;
        $has_italic = false;
        $weights = [];

        if (!empty($font['variants'])) {
            foreach ($font['variants'] as $variant => $data) {
                // Variable fonts have ranges like "100..900"
                if (strpos($variant, '..') !== false) {
                    $has_variable = true;
                }
                if (strpos($variant, 'i') !== false || strpos($variant, 'italic') !== false) {
                    $has_italic = true;
                }
                // Extract weight
                $weight = preg_replace('/[^0-9]/', '', $variant);
                if ($weight && !in_array($weight, $weights, true)) {
                    $weights[] = $weight;
                }
            }
        }

        // Sort weights
        sort($weights, SORT_NUMERIC);

        return [
            'family'       => $font['family'],
            'category'     => $font['category'],
            'has_variable' => $has_variable,
            'has_italic'   => $has_italic,
            'weights'      => $weights,
            'version'      => $font['version'] ?? '',
            'lastModified' => $font['lastModified'] ?? '',
        ];
    }

    /**
     * Clear the fonts metadata cache.
     */
    public function clear_cache() {
        delete_transient(self::CACHE_KEY);
    }

    /**
     * Get version info for a specific font.
     *
     * @param string $font_family Font family name.
     * @return array|null Version info or null if not found.
     */
    public function get_font_version($font_family) {
        $fonts = $this->get_fonts_metadata();

        if (is_wp_error($fonts)) {
            return null;
        }

        $font_family_lower = strtolower($font_family);

        foreach ($fonts as $font) {
            if (strtolower($font['family']) === $font_family_lower) {
                return [
                    'version'      => $font['version'] ?? '',
                    'lastModified' => $font['lastModified'] ?? '',
                ];
            }
        }

        return null;
    }
}
