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

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

/**
 * Class MLF_Font_Registry
 *
 * Handles registering fonts with WordPress Font Library API.
 */
class MLF_Font_Registry {

    /**
     * Register a font family with WordPress Font Library.
     *
     * @param string $font_name      Display name (e.g., "Open Sans").
     * @param string $font_slug      Slug (e.g., "open-sans").
     * @param array  $files          Array of downloaded file data.
     * @param string $font_version   Google Fonts version (e.g., "v35").
     * @param string $last_modified  Google Fonts last modified date.
     * @return int|WP_Error Font family post ID or error.
     */
    public function register_font($font_name, $font_slug, $files, $font_version = '', $last_modified = '') {
        // Check if font already exists
        $existing = get_posts([
            'post_type'      => 'wp_font_family',
            'name'           => $font_slug,
            'posts_per_page' => 1,
            'post_status'    => 'any',
        ]);

        if (!empty($existing)) {
            return new WP_Error('font_exists', 'Font family already installed');
        }

        // Get font directory info
        $font_dir = wp_get_font_dir();

        // Build font face array for WordPress
        $font_faces = [];
        foreach ($files as $file) {
            $filename = basename($file['path']);

            // Determine if this is a variable font (weight is a range like "100 900")
            $is_variable = isset($file['is_variable']) && $file['is_variable'];
            $weight = $file['weight'];

            $face_data = [
                'fontFamily' => $font_name,
                'fontStyle'  => $file['style'],
                'fontWeight' => $weight,
                'src'        => 'file:./' . $filename,
            ];

            $font_faces[] = $face_data;
        }

        // Determine font category for fallback
        // Default to sans-serif, but could be enhanced to detect from Google Fonts metadata
        $fallback = 'sans-serif';

        // Build font family settings (this is what Gutenberg reads)
        $font_family_settings = [
            'name'       => $font_name,
            'slug'       => $font_slug,
            'fontFamily' => "'{$font_name}', {$fallback}",
            'fontFace'   => $font_faces,
        ];

        // Create font family post
        $family_id = wp_insert_post([
            'post_type'    => 'wp_font_family',
            'post_title'   => $font_name,
            'post_name'    => $font_slug,
            'post_status'  => 'publish',
            'post_content' => wp_json_encode($font_family_settings),
        ], true);

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

        // Mark as imported by our plugin (for identification)
        update_post_meta($family_id, '_mlf_imported', '1');
        update_post_meta($family_id, '_mlf_import_date', current_time('mysql'));

        // Store version info for update checking
        if (!empty($font_version)) {
            update_post_meta($family_id, '_mlf_font_version', $font_version);
        }
        if (!empty($last_modified)) {
            update_post_meta($family_id, '_mlf_font_last_modified', $last_modified);
        }

        // Create font face posts (children) - WordPress also reads these
        foreach ($files as $file) {
            $filename = basename($file['path']);
            $weight = $file['weight'];

            $face_settings = [
                'fontFamily' => $font_name,
                'fontWeight' => $weight,
                'fontStyle'  => $file['style'],
                'src'        => 'file:./' . $filename,
            ];

            wp_insert_post([
                'post_type'    => 'wp_font_face',
                'post_parent'  => $family_id,
                'post_title'   => sprintf('%s %s %s', $font_name, $weight, $file['style']),
                'post_name'    => sanitize_title(sprintf('%s-%s-%s', $font_slug, $weight, $file['style'])),
                'post_status'  => 'publish',
                'post_content' => wp_json_encode($face_settings),
            ]);
        }

        // Clear all font-related caches
        $this->clear_font_caches();

        return $family_id;
    }

    /**
     * Delete a font family and its files.
     *
     * @param int $family_id Font family post ID.
     * @return bool|WP_Error True on success, error on failure.
     */
    public function delete_font($family_id) {
        $family = get_post($family_id);

        if (!$family || $family->post_type !== 'wp_font_family') {
            return new WP_Error('not_found', 'Font family not found');
        }

        // Verify it's one we imported
        if (get_post_meta($family_id, '_mlf_imported', true) !== '1') {
            return new WP_Error('not_ours', 'Cannot delete fonts not imported by this plugin');
        }

        // Get font info before deletion for cleanup
        $settings = json_decode($family->post_content, true);
        $font_slug = $settings['slug'] ?? $family->post_name;

        // Get font faces (children)
        $faces = get_children([
            'post_parent' => $family_id,
            'post_type'   => 'wp_font_face',
        ]);

        $font_dir = wp_get_font_dir();

        // Delete font face files and posts
        foreach ($faces as $face) {
            $face_settings = json_decode($face->post_content, true);

            if (isset($face_settings['src'])) {
                // Convert file:. URL to path
                $src = $face_settings['src'];
                $src = str_replace('file:./', '', $src);
                $file_path = trailingslashit($font_dir['path']) . basename($src);

                // Validate path and extension before deletion
                if ($this->validate_font_path($file_path)
                    && pathinfo($file_path, PATHINFO_EXTENSION) === 'woff2'
                    && file_exists($file_path)) {
                    wp_delete_file($file_path);
                }
            }

            wp_delete_post($face->ID, true);
        }

        // Delete family post
        wp_delete_post($family_id, true);

        // Clean up global styles references to this font
        $this->remove_font_from_global_styles($font_slug);

        // Clear all font-related caches
        $this->clear_font_caches();

        return true;
    }

    /**
     * Remove references to a deleted font from global styles.
     *
     * This prevents block errors when a font is deleted but still referenced.
     *
     * @param string $font_slug The font slug to remove.
     */
    private function remove_font_from_global_styles($font_slug) {
        // Get the global styles post for the current theme
        $global_styles = get_posts([
            'post_type'      => 'wp_global_styles',
            'posts_per_page' => 1,
            'post_status'    => 'publish',
            'tax_query'      => [
                [
                    'taxonomy' => 'wp_theme',
                    'field'    => 'name',
                    'terms'    => get_stylesheet(),
                ],
            ],
        ]);

        if (empty($global_styles)) {
            return;
        }

        $global_styles_post = $global_styles[0];
        $content = json_decode($global_styles_post->post_content, true);

        if (empty($content)) {
            return;
        }

        $modified = false;

        // Helper function to recursively remove font references
        $remove_font_refs = function (&$data) use ($font_slug, &$modified, &$remove_font_refs) {
            if (!is_array($data)) {
                return;
            }

            foreach ($data as $key => &$value) {
                // Check for fontFamily references using the slug pattern
                if ($key === 'fontFamily' && is_string($value)) {
                    // WordPress uses format like: var(--wp--preset--font-family--font-slug)
                    if (strpos($value, '--font-family--' . $font_slug) !== false) {
                        unset($data[$key]);
                        $modified = true;
                    }
                }

                // Check for typography.fontFamily in element/block styles
                if (is_array($value)) {
                    $remove_font_refs($value);
                }
            }
        };

        $remove_font_refs($content);

        if ($modified) {
            wp_update_post([
                'ID'           => $global_styles_post->ID,
                'post_content' => wp_json_encode($content),
            ]);
        }
    }

    /**
     * Clear all font-related caches.
     */
    private function clear_font_caches() {
        // Clear WordPress Font Library cache
        delete_transient('wp_font_library_fonts');

        // Clear our plugin's cache
        delete_transient('mlf_imported_fonts_list');

        // Clear global settings cache (used by Gutenberg)
        wp_cache_delete('wp_get_global_settings', 'theme_json');
        wp_cache_delete('wp_get_global_stylesheet', 'theme_json');

        // Clear theme.json related caches
        delete_transient('global_styles');
        delete_transient('global_styles_' . get_stylesheet());

        // Clear WP_Theme_JSON caches
        if (class_exists('WP_Theme_JSON_Resolver')) {
            WP_Theme_JSON_Resolver::clean_cached_data();
        }

        // Clear object cache for post queries
        wp_cache_flush_group('posts');

        // Clear any theme mods cache
        delete_transient('theme_mods_' . get_stylesheet());

        // Trigger action for other plugins/themes that might cache fonts
        do_action('mlf_fonts_cache_cleared');
    }

    /**
     * Get all fonts imported by this plugin.
     *
     * Uses optimized queries to avoid N+1 pattern.
     *
     * @return array Array of font data.
     */
    public function get_imported_fonts() {
        // Check transient cache first
        $cached = get_transient('mlf_imported_fonts_list');
        if ($cached !== false) {
            return $cached;
        }

        $fonts = get_posts([
            'post_type'      => 'wp_font_family',
            'posts_per_page' => 100,
            'post_status'    => 'publish',
            'meta_key'       => '_mlf_imported',
            'meta_value'     => '1',
        ]);

        if (empty($fonts)) {
            set_transient('mlf_imported_fonts_list', [], 5 * MINUTE_IN_SECONDS);
            return [];
        }

        // Collect all font IDs for batch query
        $font_ids = wp_list_pluck($fonts, 'ID');

        // Single query to get ALL font faces for ALL fonts (fixes N+1)
        $all_faces = get_posts([
            'post_type'       => 'wp_font_face',
            'posts_per_page'  => 1000,
            'post_status'     => 'publish',
            'post_parent__in' => $font_ids,
        ]);

        // Group faces by parent font ID
        $faces_by_font = [];
        foreach ($all_faces as $face) {
            $parent_id = $face->post_parent;
            if (!isset($faces_by_font[$parent_id])) {
                $faces_by_font[$parent_id] = [];
            }
            $faces_by_font[$parent_id][] = $face;
        }

        // Batch get all metadata
        $import_dates = [];
        $font_versions = [];
        $font_last_modified = [];
        foreach ($font_ids as $font_id) {
            $import_dates[$font_id] = get_post_meta($font_id, '_mlf_import_date', true);
            $font_versions[$font_id] = get_post_meta($font_id, '_mlf_font_version', true);
            $font_last_modified[$font_id] = get_post_meta($font_id, '_mlf_font_last_modified', true);
        }

        $result = [];

        foreach ($fonts as $font) {
            $settings = json_decode($font->post_content, true);

            // Get variants from pre-fetched data
            $faces = $faces_by_font[$font->ID] ?? [];

            $variants = [];
            foreach ($faces as $face) {
                $face_settings = json_decode($face->post_content, true);
                $variants[] = [
                    'weight' => $face_settings['fontWeight'] ?? '400',
                    'style'  => $face_settings['fontStyle'] ?? 'normal',
                ];
            }

            // Sort variants by weight then style
            usort($variants, function($a, $b) {
                // Handle weight ranges (variable fonts)
                $a_weight = is_numeric($a['weight']) ? intval($a['weight']) : intval(explode(' ', $a['weight'])[0]);
                $b_weight = is_numeric($b['weight']) ? intval($b['weight']) : intval(explode(' ', $b['weight'])[0]);

                $weight_cmp = $a_weight - $b_weight;
                if ($weight_cmp !== 0) {
                    return $weight_cmp;
                }
                return strcmp($a['style'], $b['style']);
            });

            $result[] = [
                'id'            => $font->ID,
                'name'          => $settings['name'] ?? $font->post_title,
                'slug'          => $settings['slug'] ?? $font->post_name,
                'variants'      => $variants,
                'import_date'   => $import_dates[$font->ID] ?? '',
                'version'       => $font_versions[$font->ID] ?? '',
                'last_modified' => $font_last_modified[$font->ID] ?? '',
            ];
        }

        // Cache for 5 minutes
        set_transient('mlf_imported_fonts_list', $result, 5 * MINUTE_IN_SECONDS);

        return $result;
    }

    /**
     * Get all fonts imported by this plugin, including actual filenames.
     *
     * This method includes the actual filename stored in the database,
     * which is needed to correctly reference variable vs static font files.
     *
     * @return array Array of font data with filenames.
     */
    public function get_imported_fonts_with_src() {
        $fonts = get_posts([
            'post_type'      => 'wp_font_family',
            'posts_per_page' => 100,
            'post_status'    => 'publish',
            'meta_key'       => '_mlf_imported',
            'meta_value'     => '1',
        ]);

        if (empty($fonts)) {
            return [];
        }

        // Collect all font IDs for batch query
        $font_ids = wp_list_pluck($fonts, 'ID');

        // Single query to get ALL font faces for ALL fonts
        $all_faces = get_posts([
            'post_type'       => 'wp_font_face',
            'posts_per_page'  => 1000,
            'post_status'     => 'publish',
            'post_parent__in' => $font_ids,
        ]);

        // Group faces by parent font ID
        $faces_by_font = [];
        foreach ($all_faces as $face) {
            $parent_id = $face->post_parent;
            if (!isset($faces_by_font[$parent_id])) {
                $faces_by_font[$parent_id] = [];
            }
            $faces_by_font[$parent_id][] = $face;
        }

        $result = [];

        foreach ($fonts as $font) {
            $settings = json_decode($font->post_content, true);

            // Get variants from pre-fetched data
            $faces = $faces_by_font[$font->ID] ?? [];

            $variants = [];
            foreach ($faces as $face) {
                $face_settings = json_decode($face->post_content, true);

                // Extract filename from src (format: "file:./filename.woff2")
                $src = $face_settings['src'] ?? '';
                $filename = str_replace('file:./', '', $src);

                $variants[] = [
                    'weight'   => $face_settings['fontWeight'] ?? '400',
                    'style'    => $face_settings['fontStyle'] ?? 'normal',
                    'filename' => $filename,
                ];
            }

            $result[] = [
                'id'       => $font->ID,
                'name'     => $settings['name'] ?? $font->post_title,
                'slug'     => $settings['slug'] ?? $font->post_name,
                'variants' => $variants,
            ];
        }

        return $result;
    }

    /**
     * Validate that a path is within the WordPress fonts directory.
     *
     * @param string $path Full path to validate.
     * @return bool True if path is safe, false otherwise.
     */
    private function validate_font_path($path) {
        $font_dir = wp_get_font_dir();
        $fonts_path = wp_normalize_path(trailingslashit($font_dir['path']));

        // Resolve to real path (handles ../ etc)
        $real_path = realpath($path);

        // If realpath fails, file doesn't exist yet - validate the directory
        if ($real_path === false) {
            $dir = dirname($path);
            $real_dir = realpath($dir);
            if ($real_dir === false) {
                return false;
            }
            $real_path = wp_normalize_path($real_dir . '/' . basename($path));
        } else {
            $real_path = wp_normalize_path($real_path);
        }

        // Must be within fonts directory
        return strpos($real_path, $fonts_path) === 0;
    }
}
