<?php
/**
 * Plugin Name: Maple Local Fonts
 * Plugin URI: https://mapleopentech.org/plugins/maple-local-fonts
 * Description: Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography.
 * Version: 1.0.0
 * Requires at least: 6.5
 * Requires PHP: 7.4
 * Author: Maple Open Technologies
 * Author URI: https://mapleopentech.org
 * License: GPL-2.0-or-later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: maple-local-fonts
 * Domain Path: /languages
 */

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

// Plugin constants
define('MLF_VERSION', '1.0.0');
define('MLF_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('MLF_PLUGIN_URL', plugin_dir_url(__FILE__));
define('MLF_PLUGIN_BASENAME', plugin_basename(__FILE__));

// Limits
define('MLF_MAX_FONTS_PER_REQUEST', 10);
define('MLF_MAX_WEIGHTS_PER_FONT', 9);
define('MLF_REQUEST_TIMEOUT', 30);
define('MLF_MAX_CSS_SIZE', 512 * 1024);      // 512KB max CSS response
define('MLF_MAX_FONT_FILE_SIZE', 5 * 1024 * 1024); // 5MB max font file
define('MLF_MAX_FONT_FACES', 20);            // Max font faces per import (9 weights × 2 styles + buffer)

/**
 * Ensure WOFF2 MIME type is registered (fixes Safari font loading).
 *
 * @param array $mimes Existing MIME types.
 * @return array Modified MIME types.
 */
function mlf_add_woff2_mime_type($mimes) {
    $mimes['woff2'] = 'font/woff2';
    $mimes['woff']  = 'font/woff';
    return $mimes;
}
add_filter('mime_types', 'mlf_add_woff2_mime_type');


/**
 * Check WordPress version on activation.
 */
function mlf_activate() {
    if (version_compare(get_bloginfo('version'), '6.5', '<')) {
        deactivate_plugins(plugin_basename(__FILE__));
        wp_die(
            esc_html__('Maple Local Fonts requires WordPress 6.5 or higher for Font Library support.', 'maple-local-fonts'),
            esc_html__('Plugin Activation Error', 'maple-local-fonts'),
            ['back_link' => true]
        );
    }

    // Ensure fonts directory exists
    $font_dir = wp_get_font_dir();
    if (!file_exists($font_dir['path'])) {
        wp_mkdir_p($font_dir['path']);
    }

    // Create .htaccess for proper MIME types and CORS (Apache servers)
    $htaccess_path = trailingslashit($font_dir['path']) . '.htaccess';
    if (!file_exists($htaccess_path)) {
        $htaccess_content = "# Maple Local Fonts - Font MIME types and CORS\n";
        $htaccess_content .= "<IfModule mod_mime.c>\n";
        $htaccess_content .= "    AddType font/woff2 .woff2\n";
        $htaccess_content .= "    AddType font/woff .woff\n";
        $htaccess_content .= "</IfModule>\n\n";
        $htaccess_content .= "<IfModule mod_headers.c>\n";
        $htaccess_content .= "    <FilesMatch \"\\.(woff2?|ttf|otf|eot)$\">\n";
        $htaccess_content .= "        Header set Access-Control-Allow-Origin \"*\"\n";
        $htaccess_content .= "    </FilesMatch>\n";
        $htaccess_content .= "</IfModule>\n";

        global $wp_filesystem;
        if (empty($wp_filesystem)) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }

        if ($wp_filesystem) {
            $wp_filesystem->put_contents($htaccess_path, $htaccess_content, FS_CHMOD_FILE);
        }
    }

    // Flush rewrite rules for fonts CSS endpoint
    mlf_add_rewrite_rules();
    flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'mlf_activate');

/**
 * Flush rewrite rules on deactivation.
 */
function mlf_deactivate() {
    flush_rewrite_rules();
}
register_deactivation_hook(__FILE__, 'mlf_deactivate');

/**
 * Check WordPress version on admin init (in case WP was downgraded).
 */
function mlf_check_version() {
    if (version_compare(get_bloginfo('version'), '6.5', '<')) {
        deactivate_plugins(plugin_basename(__FILE__));
        add_action('admin_notices', 'mlf_version_notice');
    }
}
add_action('admin_init', 'mlf_check_version');

/**
 * Ensure fonts directory htaccess exists (for MIME types and CORS).
 */
function mlf_ensure_htaccess() {
    $font_dir = wp_get_font_dir();
    $htaccess_path = trailingslashit($font_dir['path']) . '.htaccess';

    // Always update htaccess to ensure latest headers
    $htaccess_content = "# Maple Local Fonts - Font MIME types and CORS\n";
    $htaccess_content .= "# Required for cross-browser font loading including iOS Safari\n\n";
    $htaccess_content .= "<IfModule mod_mime.c>\n";
    $htaccess_content .= "    AddType font/woff2 .woff2\n";
    $htaccess_content .= "    AddType font/woff .woff\n";
    $htaccess_content .= "</IfModule>\n\n";
    $htaccess_content .= "<IfModule mod_headers.c>\n";
    $htaccess_content .= "    <FilesMatch \"\\.(woff2?|ttf|otf|eot)$\">\n";
    $htaccess_content .= "        Header set Access-Control-Allow-Origin \"*\"\n";
    $htaccess_content .= "        Header set Access-Control-Allow-Methods \"GET, OPTIONS\"\n";
    $htaccess_content .= "        Header set Access-Control-Allow-Headers \"Origin, Content-Type\"\n";
    $htaccess_content .= "        Header set Cross-Origin-Resource-Policy \"cross-origin\"\n";
    $htaccess_content .= "        Header set Timing-Allow-Origin \"*\"\n";
    $htaccess_content .= "    </FilesMatch>\n";
    $htaccess_content .= "</IfModule>\n";

    if (file_exists($font_dir['path'])) {
        global $wp_filesystem;
        if (empty($wp_filesystem)) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }

        if ($wp_filesystem) {
            $wp_filesystem->put_contents($htaccess_path, $htaccess_content, FS_CHMOD_FILE);
        }
    }
}
add_action('admin_init', 'mlf_ensure_htaccess');

/**
 * Display version notice.
 */
function mlf_version_notice() {
    echo '<div class="error"><p>';
    esc_html_e('Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher.', 'maple-local-fonts');
    echo '</p></div>';
}

/**
 * Declare WooCommerce HPOS compatibility.
 */
function mlf_declare_hpos_compatibility() {
    if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
            'custom_order_tables',
            __FILE__,
            true
        );
    }
}
add_action('before_woocommerce_init', 'mlf_declare_hpos_compatibility');

/**
 * Load plugin text domain.
 */
function mlf_load_textdomain() {
    load_plugin_textdomain('maple-local-fonts', false, dirname(MLF_PLUGIN_BASENAME) . '/languages');
}
add_action('plugins_loaded', 'mlf_load_textdomain');

/**
 * Autoload plugin classes.
 */
function mlf_autoload($class) {
    $prefix = 'MLF_';
    if (strpos($class, $prefix) !== 0) {
        return;
    }

    $class_name = str_replace($prefix, '', $class);
    $class_name = str_replace('_', '-', strtolower($class_name));
    $file = MLF_PLUGIN_DIR . 'includes/class-mlf-' . $class_name . '.php';

    if (file_exists($file)) {
        require_once $file;
    }
}
spl_autoload_register('mlf_autoload');

/**
 * Initialize the plugin.
 */
function mlf_init() {
    // Only load admin functionality
    if (is_admin()) {
        new MLF_Admin_Page();
        new MLF_Ajax_Handler();
        new MLF_Font_Search();
    }
}
add_action('plugins_loaded', 'mlf_init', 20);

/**
 * Register REST API routes.
 */
function mlf_register_rest_routes() {
    $controller = new MLF_Rest_Controller();
    $controller->register_routes();

    // Register font server routes for cross-browser compatibility
    $font_server = new MLF_Font_Server();
    $font_server->register_routes();
}
add_action('rest_api_init', 'mlf_register_rest_routes');

/**
 * Check if we should use REST API for font serving (compatibility mode).
 *
 * @return bool True if using REST API font serving.
 */
function mlf_use_rest_font_serving() {
    return get_option('mlf_compatibility_mode', true); // Default to true for maximum compatibility
}

/**
 * Get the URL for a font file.
 *
 * Uses REST API endpoint for compatibility, or direct file URL for performance.
 *
 * @param string $filename The font filename.
 * @return string The font URL.
 */
function mlf_get_font_url($filename) {
    if (mlf_use_rest_font_serving()) {
        // Use REST API endpoint (guaranteed proper headers)
        return rest_url('mlf/v1/font/' . $filename);
    } else {
        // Use direct file URL (faster but depends on server config)
        $font_dir = wp_get_font_dir();
        return trailingslashit($font_dir['url']) . $filename;
    }
}

/**
 * Get the required capability for managing fonts.
 *
 * @return string The capability required to manage fonts.
 */
function mlf_get_capability() {
    /**
     * Filter the capability required to manage local fonts.
     *
     * @since 1.0.0
     * @param string $capability Default capability is 'edit_theme_options'.
     */
    return apply_filters('mlf_manage_fonts_capability', 'edit_theme_options');
}

/**
 * Add imported fonts to the theme.json typography settings.
 *
 * This makes fonts appear in the Site Editor typography dropdown
 * and generates the @font-face CSS for the frontend.
 *
 * @param WP_Theme_JSON_Data $theme_json The theme.json data.
 * @return WP_Theme_JSON_Data Modified theme.json data.
 */
function mlf_add_fonts_to_theme_json($theme_json) {
    // Wrap in try-catch to prevent breaking Site Editor if something goes wrong
    try {
        $registry = new MLF_Font_Registry();
        $fonts = $registry->get_imported_fonts_with_src();

        if (empty($fonts) || !is_array($fonts)) {
            return $theme_json;
        }

        // Build our font families
        $font_families = [];
        $font_dir = wp_get_font_dir();

        if (empty($font_dir['url'])) {
            return $theme_json;
        }

        $font_base_url = trailingslashit($font_dir['url']);

        foreach ($fonts as $font) {
            // Validate required font properties
            if (empty($font['name']) || empty($font['slug']) || empty($font['variants'])) {
                continue;
            }

            $font_faces = [];

            foreach ($font['variants'] as $variant) {
                // Validate variant data
                if (empty($variant['filename'])) {
                    continue;
                }

                $weight = !empty($variant['weight']) ? $variant['weight'] : '400';
                $style = !empty($variant['style']) ? $variant['style'] : 'normal';
                $filename = $variant['filename'];

                // Use direct file URL
                $font_url = $font_base_url . $filename;

                $font_faces[] = [
                    'fontFamily'  => $font['name'],
                    'fontWeight'  => $weight,
                    'fontStyle'   => $style,
                    'fontDisplay' => 'swap',
                    'src'         => [$font_url],
                ];
            }

            // Only add font if it has valid faces
            if (!empty($font_faces)) {
                $font_families[] = [
                    'name'       => $font['name'],
                    'slug'       => $font['slug'],
                    'fontFamily' => "'{$font['name']}', sans-serif",
                    'fontFace'   => $font_faces,
                ];
            }
        }

        // Only update if we have valid fonts
        if (empty($font_families)) {
            return $theme_json;
        }

        // Use update_with to merge - WordPress handles the merging logic
        $new_data = [
            'version'  => 2,
            'settings' => [
                'typography' => [
                    'fontFamilies' => $font_families,
                ],
            ],
        ];

        return $theme_json->update_with($new_data);
    } catch (Exception $e) {
        // Log error but don't break the Site Editor
        error_log('MLF theme.json filter error: ' . $e->getMessage());
        return $theme_json;
    }
}
add_filter('wp_theme_json_data_user', 'mlf_add_fonts_to_theme_json', 10);

/**
 * Generate @font-face CSS for imported fonts.
 *
 * @return string CSS content.
 */
function mlf_get_font_face_css() {
    $registry = new MLF_Font_Registry();
    $fonts = $registry->get_imported_fonts();

    if (empty($fonts)) {
        return '';
    }

    $css = '';

    foreach ($fonts as $font) {
        $font_slug = sanitize_title($font['name']);
        // Escape font name for CSS (handle quotes and special chars)
        $css_font_name = addcslashes($font['name'], "'\\");

        foreach ($font['variants'] as $variant) {
            $weight = $variant['weight'];
            $style = $variant['style'];

            // Build filename based on our naming convention
            if (strpos($weight, ' ') !== false) {
                // Variable font
                $filename = sprintf('%s_%s_variable.woff2', $font_slug, $style);
            } else {
                $filename = sprintf('%s_%s_%s.woff2', $font_slug, $style, $weight);
            }

            // Get font URL (uses REST API or direct based on setting)
            $font_url = mlf_get_font_url($filename);

            // Ensure HTTPS if site uses HTTPS (important for reverse proxy setups)
            if (is_ssl() && strpos($font_url, 'http://') === 0) {
                $font_url = str_replace('http://', 'https://', $font_url);
            }

            $css .= "@font-face {";
            $css .= "font-family: '{$css_font_name}';";
            $css .= "font-style: {$style};";
            $css .= "font-weight: {$weight};";
            $css .= "font-display: swap;";
            $css .= "src: url('" . esc_url($font_url) . "') format('woff2');";
            $css .= "}\n";
        }
    }

    return $css;
}

/**
 * Enqueue font face CSS on frontend.
 *
 * Uses external CSS file just like Google Fonts does.
 */
function mlf_enqueue_font_faces() {
    $registry = new MLF_Font_Registry();
    $fonts = $registry->get_imported_fonts();

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

    // Load CSS from custom endpoint (like Google Fonts serves CSS)
    wp_enqueue_style(
        'mlf-local-fonts',
        home_url('/mlf-fonts.css'),
        [],
        null // No version string, like Google Fonts
    );
}
add_action('wp_enqueue_scripts', 'mlf_enqueue_font_faces');

/**
 * Enqueue font face CSS in block editor.
 */
function mlf_enqueue_editor_font_faces() {
    $registry = new MLF_Font_Registry();
    $fonts = $registry->get_imported_fonts();

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

    // Load CSS from custom endpoint (like Google Fonts serves CSS)
    wp_enqueue_style(
        'mlf-local-fonts-editor',
        home_url('/mlf-fonts.css'),
        [],
        null
    );
}
add_action('enqueue_block_editor_assets', 'mlf_enqueue_editor_font_faces');

/**
 * Add rewrite rule for fonts CSS endpoint.
 */
function mlf_add_rewrite_rules() {
    add_rewrite_rule('^mlf-fonts\.css$', 'index.php?mlf_fonts_css=1', 'top');
}
add_action('init', 'mlf_add_rewrite_rules');

/**
 * Add query var for fonts CSS.
 */
function mlf_add_query_vars($vars) {
    $vars[] = 'mlf_fonts_css';
    return $vars;
}
add_filter('query_vars', 'mlf_add_query_vars');

/**
 * Handle fonts CSS request.
 */
function mlf_handle_fonts_css_request() {
    if (!get_query_var('mlf_fonts_css')) {
        return;
    }

    // Send CSS headers
    header('Content-Type: text/css; charset=UTF-8');
    header('Access-Control-Allow-Origin: *');
    header('Cache-Control: public, max-age=86400');
    header('X-Content-Type-Options: nosniff');

    $registry = new MLF_Font_Registry();
    $fonts = $registry->get_imported_fonts_with_src();

    if (empty($fonts)) {
        echo "/* No fonts installed */\n";
        exit;
    }

    echo "/* Maple Local Fonts */\n\n";

    // Use direct font file URLs
    $font_dir = wp_get_font_dir();
    $font_base_url = trailingslashit($font_dir['url']);

    foreach ($fonts as $font) {
        foreach ($font['variants'] as $variant) {
            $weight = $variant['weight'];
            $style = $variant['style'];

            // Use the actual filename from database (stored in src)
            $filename = $variant['filename'];
            $file_url = $font_base_url . $filename;

            echo "@font-face {\n";
            echo "  font-family: '{$font['name']}';\n";
            echo "  font-style: {$style};\n";
            echo "  font-weight: {$weight};\n";
            echo "  font-display: swap;\n";
            echo "  src: url({$file_url}) format('woff2');\n";
            echo "}\n\n";
        }
    }

    exit;
}
add_action('template_redirect', 'mlf_handle_fonts_css_request');

/**
 * Register admin menu.
 */
function mlf_register_menu() {
    add_submenu_page(
        'options-general.php',
        __('Maple Fonts', 'maple-local-fonts'),
        __('Maple Fonts', 'maple-local-fonts'),
        mlf_get_capability(),
        'maple-local-fonts',
        'mlf_render_admin_page'
    );
}
add_action('admin_menu', 'mlf_register_menu');

/**
 * Add settings link to plugin action links.
 *
 * @param array $links Existing plugin action links.
 * @return array Modified plugin action links.
 */
function mlf_plugin_action_links($links) {
    $settings_link = sprintf(
        '<a href="%s">%s</a>',
        esc_url(admin_url('options-general.php?page=maple-local-fonts')),
        esc_html__('Settings', 'maple-local-fonts')
    );
    array_unshift($links, $settings_link);
    return $links;
}
add_filter('plugin_action_links_' . MLF_PLUGIN_BASENAME, 'mlf_plugin_action_links');

/**
 * Render admin page (delegates to MLF_Admin_Page).
 */
function mlf_render_admin_page() {
    $admin_page = new MLF_Admin_Page();
    $admin_page->render();
}

/**
 * Enqueue admin assets.
 *
 * @param string $hook The current admin page hook.
 */
function mlf_enqueue_admin_assets($hook) {
    if ($hook !== 'settings_page_maple-local-fonts') {
        return;
    }

    // Use filemtime for cache-busting during development
    $css_version = MLF_VERSION . '.' . filemtime(MLF_PLUGIN_DIR . 'assets/admin.css');
    $js_version = MLF_VERSION . '.' . filemtime(MLF_PLUGIN_DIR . 'assets/admin.js');

    wp_enqueue_style(
        'mlf-admin',
        MLF_PLUGIN_URL . 'assets/admin.css',
        [],
        $css_version
    );

    wp_enqueue_script(
        'mlf-admin',
        MLF_PLUGIN_URL . 'assets/admin.js',
        ['jquery'],
        $js_version,
        true
    );

    wp_localize_script('mlf-admin', 'mapleLocalFontsData', [
        'ajaxUrl'          => admin_url('admin-ajax.php'),
        'downloadNonce'    => wp_create_nonce('mlf_download_font'),
        'deleteNonce'      => wp_create_nonce('mlf_delete_font'),
        'searchNonce'      => wp_create_nonce('mlf_search_fonts'),
        'checkUpdatesNonce' => wp_create_nonce('mlf_check_updates'),
        'updateFontNonce'  => wp_create_nonce('mlf_update_font'),
        'strings'          => [
            'downloading'     => __('Downloading...', 'maple-local-fonts'),
            'deleting'        => __('Deleting...', 'maple-local-fonts'),
            'updating'        => __('Updating...', 'maple-local-fonts'),
            'checking'        => __('Checking...', 'maple-local-fonts'),
            'confirmDelete'   => __('Are you sure you want to delete this font?', 'maple-local-fonts'),
            'error'           => __('An error occurred. Please try again.', 'maple-local-fonts'),
            'searching'       => __('Searching...', 'maple-local-fonts'),
            'noResults'       => __('No fonts found. Try a different search term.', 'maple-local-fonts'),
            'selectFont'      => __('Please select a font first.', 'maple-local-fonts'),
            'previewText'     => __('Maple Fonts Preview', 'maple-local-fonts'),
            'minChars'        => __('Please enter at least 2 characters.', 'maple-local-fonts'),
            'noUpdates'       => __('All fonts are up to date.', 'maple-local-fonts'),
            'updatesFound'    => __('Updates available for %d font(s).', 'maple-local-fonts'),
        ],
    ]);
}
add_action('admin_enqueue_scripts', 'mlf_enqueue_admin_assets');
