<?php

declare(strict_types=1);


/**
 * WP-CLI Commands for Image Optimization
 *
 * @package ProRank\SEO\CLI
 * @since 1.0.0
 */

namespace ProRank\SEO\CLI;

use ProRank\SEO\Modules\Performance\ImageOptimizationModule;
use WP_CLI;
use WP_CLI\Utils;

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


/**
 * Manage ProRank SEO image optimization.
 *
 * ## EXAMPLES
 *
 *     # Optimize all unoptimized images
 *     wp prorank image optimize --all
 *
 *     # Show optimization statistics
 *     wp prorank image stats
 *
 *     # Check server capabilities
 *     wp prorank image check-support
 */


class ImageOptimizationCommand {

    /**
     * MIME types eligible for optimization in this pipeline.
     *
     * @var array<int, string>
     */
    private const OPTIMIZABLE_MIME_TYPES = ['image/jpeg', 'image/png'];

    /**
     * Image optimization module instance.
     *
     * @var ImageOptimizationModule|null
     */
    private $module = null;

    /**
     * Get the image optimization module.
     *
     * @return ImageOptimizationModule|null
     */
    private function get_module(): ?ImageOptimizationModule {
        if ($this->module === null) {
            if (class_exists('\ProRank\SEO\Modules\Performance\ImageOptimizationModule')) {
                $this->module = new ImageOptimizationModule();
            }
        }
        return $this->module;
    }

    /**
     * Optimize images in the media library.
     *
     * ## OPTIONS
     *
     * [--all]
     * : Optimize all unoptimized images.
     *
     * [--id=<id>]
     * : Optimize a specific attachment by ID.
     *
     * [--format=<format>]
     * : Generate specific format only (webp, avif).
     *
     * [--batch=<size>]
     * : Process in batches of specified size. Default: 10.
     *
     * [--dry-run]
     * : Show what would be optimized without making changes.
     *
     * ## EXAMPLES
     *
     *     # Optimize all unoptimized images
     *     wp prorank image optimize --all
     *
     *     # Optimize specific image
     *     wp prorank image optimize --id=123
     *
     *     # Generate WebP for all images
     *     wp prorank image optimize --all --format=webp
     *
     *     # Dry run to see what would be optimized
     *     wp prorank image optimize --all --dry-run
     *
     * @when after_wp_load
     */
    public function optimize($args, $assoc_args) {
        $module = $this->get_module();
        if (!$module) {
            WP_CLI::error('Image optimization module not available.');
        }

        $dry_run = Utils\get_flag_value($assoc_args, 'dry-run', false);
        $batch_size = (int) Utils\get_flag_value($assoc_args, 'batch', 10);
        $format = Utils\get_flag_value($assoc_args, 'format', null);
        $specific_id = Utils\get_flag_value($assoc_args, 'id', null);
        $all = Utils\get_flag_value($assoc_args, 'all', false);

        if ($specific_id) {
            // Optimize specific attachment
            $attachment = get_post($specific_id);
            if (!$attachment || $attachment->post_type !== 'attachment') {
                WP_CLI::error("Attachment ID {$specific_id} not found.");
            }

            if (!wp_attachment_is_image($specific_id)) {
                WP_CLI::error("Attachment ID {$specific_id} is not an image.");
            }

            if ($dry_run) {
                WP_CLI::log("Would optimize: " . get_attached_file($specific_id));
                return;
            }

            WP_CLI::log("Optimizing attachment ID {$specific_id}...");
            $result = $this->optimize_single_image((int) $specific_id, $format);

            if ($result['success']) {
                WP_CLI::success(sprintf(
                    'Optimized %s (saved %s)',
                    basename(get_attached_file($specific_id)),
                    size_format($result['bytes_saved'])
                ));
            } else {
                WP_CLI::warning($result['message']);
            }
            return;
        }

        if (!$all) {
            WP_CLI::error('Please specify --all to optimize all images or --id=<id> for a specific image.');
        }

        // Get all unoptimized images
        $query_args = [
            'post_type' => 'attachment',
            'post_mime_type' => self::OPTIMIZABLE_MIME_TYPES,
            'post_status' => 'inherit',
            'posts_per_page' => -1,
            'fields' => 'ids',
        ];

        // Filter to unoptimized only
        $query_args['meta_query'] = [
            'relation' => 'OR',
            [
                'key' => '_prorank_optimized',
                'compare' => 'NOT EXISTS',
            ],
            [
                'key' => '_prorank_optimized',
                'value' => '0',
            ],
        ];

        $attachments = get_posts($query_args);
        $invalid_attachments = [];
        $attachments = array_values(array_filter($attachments, function ($id) use (&$invalid_attachments) {
            $file_path = get_attached_file((int) $id);
            if (!$file_path || !is_string($file_path) || !file_exists($file_path)) {
                $invalid_attachments[] = (int) $id;
                return false;
            }
            return true;
        }));
        $total = count($attachments);

        if ($total === 0) {
            if (!empty($invalid_attachments)) {
                WP_CLI::warning(sprintf(
                    'Skipped %d invalid attachments with missing files.',
                    count($invalid_attachments)
                ));
            }
            WP_CLI::success('All images are already optimized!');
            return;
        }

        if ($dry_run) {
            WP_CLI::log("Would optimize {$total} images.");
            foreach ($attachments as $id) {
                WP_CLI::log("  - " . basename(get_attached_file($id)));
            }
            return;
        }

        WP_CLI::log("Starting optimization of {$total} images...");

        $progress = Utils\make_progress_bar('Optimizing images', $total);
        $success_count = 0;
        $error_count = 0;
        $total_bytes_saved = 0;

        foreach ($attachments as $id) {
            $result = $this->optimize_single_image($id, $format);

            if ($result['success']) {
                $success_count++;
                $total_bytes_saved += $result['bytes_saved'];
            } else {
                $error_count++;
                if (WP_CLI::get_config('debug')) {
                    WP_CLI::warning("Failed: " . basename(get_attached_file($id)) . " - " . $result['message']);
                }
            }

            $progress->tick();
        }

        $progress->finish();

        WP_CLI::log('');
        WP_CLI::log('Optimization Complete:');
        WP_CLI::log('----------------------');
        WP_CLI::log(sprintf('  Successful: %d', $success_count));
        WP_CLI::log(sprintf('  Failed: %d', $error_count));
        WP_CLI::log(sprintf('  Space saved: %s', size_format($total_bytes_saved)));
        if (!empty($invalid_attachments)) {
            WP_CLI::log(sprintf('  Skipped invalid: %d', count($invalid_attachments)));
        }

        if ($error_count > 0) {
            WP_CLI::warning('Some images failed to optimize. Run with --debug for details.');
        } else {
            WP_CLI::success('All images optimized successfully!');
        }
    }

    /**
     * Optimize a single image.
     *
     * @param int $attachment_id Attachment ID.
     * @param string|null $format Specific format to generate.
     * @return array Result with success status and bytes saved.
     */
    private function optimize_single_image(int $attachment_id, ?string $format = null): array {
        $file_path = get_attached_file($attachment_id);
        if (!$file_path || !file_exists($file_path)) {
            update_post_meta($attachment_id, '_prorank_optimized', '0');
            update_post_meta($attachment_id, '_prorank_last_error', 'File not found');
            return ['success' => false, 'message' => 'File not found', 'bytes_saved' => 0];
        }

        $mime_type = (string) get_post_mime_type($attachment_id);
        if (!in_array($mime_type, self::OPTIMIZABLE_MIME_TYPES, true)) {
            update_post_meta($attachment_id, '_prorank_last_error', 'Unsupported image format');
            return [
                'success' => false,
                'message' => sprintf('Unsupported image format: %s', $mime_type ?: 'unknown'),
                'bytes_saved' => 0,
            ];
        }

        $original_size = filesize($file_path);
        $module = $this->get_module();

        try {
            $metadata = wp_get_attachment_metadata($attachment_id);
            if (!$metadata) {
                update_post_meta($attachment_id, '_prorank_optimized', '0');
                update_post_meta($attachment_id, '_prorank_last_error', 'No metadata');
                return ['success' => false, 'message' => 'No metadata', 'bytes_saved' => 0];
            }

            // Trigger optimization through the module
            $metadata = $module->generate_optimized_images($metadata, $attachment_id);

            // Mark as optimized
            update_post_meta($attachment_id, '_prorank_optimized', '1');
            update_post_meta($attachment_id, '_prorank_optimized_date', current_time('mysql'));
            delete_post_meta($attachment_id, '_prorank_last_error');

            // Calculate savings
            clearstatcache(true, $file_path);
            $new_size = file_exists($file_path) ? filesize($file_path) : $original_size;
            $bytes_saved = max(0, $original_size - $new_size);

            return ['success' => true, 'message' => 'Optimized', 'bytes_saved' => $bytes_saved];
        } catch (\Exception $e) {
            update_post_meta($attachment_id, '_prorank_optimized', '0');
            update_post_meta($attachment_id, '_prorank_last_error', $e->getMessage());
            return ['success' => false, 'message' => $e->getMessage(), 'bytes_saved' => 0];
        }
    }

    /**
     * Show optimization statistics.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Output format (table, json, csv). Default: table.
     *
     * ## EXAMPLES
     *
     *     wp prorank image stats
     *     wp prorank image stats --format=json
     *
     * @when after_wp_load
     */
    public function stats($args, $assoc_args) {
        $module = $this->get_module();
        if (!$module) {
            WP_CLI::error('Image optimization module not available.');
        }

        $stats = $module->get_stats();
        $format = Utils\get_flag_value($assoc_args, 'format', 'table');
        $pending = isset($stats['pending'])
            ? (int) $stats['pending']
            : max(0, ((int) ($stats['total'] ?? 0)) - ((int) ($stats['optimized'] ?? 0)));

        $data = [
            [
                'Metric' => 'Total Images',
                'Value' => $stats['total'] ?? 0,
            ],
            [
                'Metric' => 'Optimized',
                'Value' => $stats['optimized'] ?? 0,
            ],
            [
                'Metric' => 'Pending',
                'Value' => $pending,
            ],
            [
                'Metric' => 'Space Saved',
                'Value' => size_format($stats['bytes_saved'] ?? 0),
            ],
            [
                'Metric' => 'WebP Generated',
                'Value' => $stats['webp_count'] ?? 0,
            ],
            [
                'Metric' => 'AVIF Generated',
                'Value' => $stats['avif_count'] ?? 0,
            ],
        ];

        if (!empty($stats['unsupported_images'])) {
            $data[] = [
                'Metric' => 'Unsupported (Skipped)',
                'Value' => (int) $stats['unsupported_images'],
            ];
        }

        if ($format === 'json') {
            WP_CLI::log(json_encode($data, JSON_PRETTY_PRINT));
        } else {
            WP_CLI\Utils\format_items($format, $data, ['Metric', 'Value']);
        }
    }

    /**
     * List images with their optimization status.
     *
     * ## OPTIONS
     *
     * [--status=<status>]
     * : Filter by status (optimized, pending, failed).
     *
     * [--limit=<limit>]
     * : Limit number of results. Default: 20.
     *
     * [--format=<format>]
     * : Output format (table, json, csv, ids). Default: table.
     *
     * ## EXAMPLES
     *
     *     wp prorank image list
     *     wp prorank image list --status=pending
     *     wp prorank image list --status=optimized --limit=50
     *
     * @when after_wp_load
     * @subcommand list
     */
    public function list_images($args, $assoc_args) {
        $status = Utils\get_flag_value($assoc_args, 'status', null);
        $limit = (int) Utils\get_flag_value($assoc_args, 'limit', 20);
        $format = Utils\get_flag_value($assoc_args, 'format', 'table');

        $query_args = [
            'post_type' => 'attachment',
            'post_mime_type' => self::OPTIMIZABLE_MIME_TYPES,
            'post_status' => 'inherit',
            'posts_per_page' => $status ? -1 : $limit,
        ];

        if ($status === 'optimized') {
            $query_args['meta_query'] = [
                [
                    'key' => '_prorank_optimized',
                    'value' => '1',
                ],
            ];
        } elseif ($status === 'pending') {
            $query_args['meta_query'] = [
                'relation' => 'OR',
                [
                    'key' => '_prorank_optimized',
                    'compare' => 'NOT EXISTS',
                ],
                [
                    'key' => '_prorank_optimized',
                    'value' => '0',
                ],
            ];
        } elseif ($status === 'failed') {
            $query_args['meta_query'] = [
                [
                    'key' => '_prorank_last_error',
                    'compare' => 'EXISTS',
                ],
            ];
        }

        $attachments = get_posts($query_args);

        if (empty($attachments)) {
            WP_CLI::log('No images found.');
            return;
        }

        if ($format === 'ids') {
            $ids = wp_list_pluck($attachments, 'ID');
            WP_CLI::log(implode(' ', $ids));
            return;
        }

        $data = [];
        foreach ($attachments as $attachment) {
            $file_path = get_attached_file($attachment->ID);
            $optimized = get_post_meta($attachment->ID, '_prorank_optimized', true);
            $optimized_date = get_post_meta($attachment->ID, '_prorank_optimized_date', true);
            $last_error = get_post_meta($attachment->ID, '_prorank_last_error', true);
            $has_valid_file = $file_path && file_exists($file_path);

            if (!$has_valid_file && empty($last_error)) {
                $last_error = 'File not found';
            }

            $current_status = 'Pending';
            if ($optimized === '1' && $has_valid_file) {
                $current_status = 'Optimized';
            } elseif (!$has_valid_file || !empty($last_error)) {
                $current_status = 'Failed';
            }

            if ($status && strtolower($current_status) !== strtolower($status)) {
                continue;
            }

            $data[] = [
                'ID' => $attachment->ID,
                'Filename' => basename($file_path),
                'Status' => $current_status,
                'Size' => $has_valid_file ? size_format(filesize($file_path)) : 'N/A',
                'Date' => $optimized_date ?: '-',
                'Error' => !empty($last_error) ? $last_error : '-',
            ];

            if (count($data) >= $limit) {
                break;
            }
        }

        if (empty($data)) {
            WP_CLI::log('No images found.');
            return;
        }

        WP_CLI\Utils\format_items($format, $data, ['ID', 'Filename', 'Status', 'Size', 'Date', 'Error']);
    }

    /**
     * Show details for a specific image.
     *
     * ## OPTIONS
     *
     * <id>
     * : The attachment ID.
     *
     * ## EXAMPLES
     *
     *     wp prorank image info 123
     *
     * @when after_wp_load
     */
    public function info($args, $assoc_args) {
        $id = (int) $args[0];

        $attachment = get_post($id);
        if (!$attachment || $attachment->post_type !== 'attachment') {
            WP_CLI::error("Attachment ID {$id} not found.");
        }

        if (!wp_attachment_is_image($id)) {
            WP_CLI::error("Attachment ID {$id} is not an image.");
        }

        $file_path = get_attached_file($id);
        $metadata = wp_get_attachment_metadata($id);
        $optimized = get_post_meta($id, '_prorank_optimized', true);
        $optimized_date = get_post_meta($id, '_prorank_optimized_date', true);

        WP_CLI::log('Image Details:');
        WP_CLI::log('--------------');
        WP_CLI::log(sprintf('ID: %d', $id));
        WP_CLI::log(sprintf('Filename: %s', basename($file_path)));
        WP_CLI::log(sprintf('Path: %s', $file_path));
        WP_CLI::log(sprintf('Size: %s', file_exists($file_path) ? size_format(filesize($file_path)) : 'N/A'));
        WP_CLI::log(sprintf('Dimensions: %dx%d', $metadata['width'] ?? 0, $metadata['height'] ?? 0));
        WP_CLI::log(sprintf('MIME Type: %s', get_post_mime_type($id)));
        WP_CLI::log(sprintf('Status: %s', $optimized === '1' ? 'Optimized' : 'Pending'));
        WP_CLI::log(sprintf('Optimized Date: %s', $optimized_date ?: '-'));

        // Check for WebP/AVIF versions
        $webp_path = preg_replace('/\.(jpe?g|png|gif)$/i', '.webp', $file_path);
        $avif_path = preg_replace('/\.(jpe?g|png|gif)$/i', '.avif', $file_path);

        WP_CLI::log('');
        WP_CLI::log('Generated Formats:');
        WP_CLI::log(sprintf('  WebP: %s', file_exists($webp_path) ? 'Yes (' . size_format(filesize($webp_path)) . ')' : 'No'));
        WP_CLI::log(sprintf('  AVIF: %s', file_exists($avif_path) ? 'Yes (' . size_format(filesize($avif_path)) . ')' : 'No'));

        // Show thumbnail sizes
        if (!empty($metadata['sizes'])) {
            WP_CLI::log('');
            WP_CLI::log('Thumbnail Sizes:');
            foreach ($metadata['sizes'] as $size => $info) {
                WP_CLI::log(sprintf('  %s: %dx%d', $size, $info['width'], $info['height']));
            }
        }
    }

    /**
     * Restore original images from backups.
     *
     * ## OPTIONS
     *
     * [--id=<id>]
     * : Restore a specific attachment by ID.
     *
     * [--all]
     * : Restore all images from backups.
     *
     * [--yes]
     * : Skip confirmation prompt.
     *
     * ## EXAMPLES
     *
     *     wp prorank image restore --id=123
     *     wp prorank image restore --all --yes
     *
     * @when after_wp_load
     */
    public function restore($args, $assoc_args) {
        $specific_id = Utils\get_flag_value($assoc_args, 'id', null);
        $all = Utils\get_flag_value($assoc_args, 'all', false);

        if (!$specific_id && !$all) {
            WP_CLI::error('Please specify --id=<id> or --all.');
        }

        $module = $this->get_module();
        if (!$module) {
            WP_CLI::error('Image optimization module not available.');
        }

        if ($specific_id) {
            $result = $module->restore_from_backup((int) $specific_id);
            if ($result) {
                WP_CLI::success("Restored original for attachment ID {$specific_id}.");
            } else {
                WP_CLI::error("No backup found for attachment ID {$specific_id}.");
            }
            return;
        }

        // Restore all
        if (!isset($assoc_args['yes'])) {
            WP_CLI::confirm('Are you sure you want to restore all images from backups?');
        }

        $backup_dir = $this->get_backup_directory();
        if (!is_dir($backup_dir)) {
            WP_CLI::error('No backup directory found.');
        }

        $attachment_ids = get_posts([
            'post_type'      => 'attachment',
            'post_status'    => 'inherit',
            'fields'         => 'ids',
            'posts_per_page' => -1,
            'meta_query'     => [
                [
                    'key'     => '_prorank_backup_path',
                    'compare' => 'EXISTS',
                ],
            ],
        ]);
        $total = count($attachment_ids);

        if ($total === 0) {
            WP_CLI::log('No backups found.');
            return;
        }

        WP_CLI::log("Restoring {$total} images from backups...");
        $progress = Utils\make_progress_bar('Restoring', $total);
        $restored = 0;

        foreach ($attachment_ids as $attachment_id) {
            $result = $module->restore_from_backup((int) $attachment_id);
            if ($result) {
                $restored++;
            }
            $progress->tick();
        }

        $progress->finish();
        WP_CLI::success("Restored {$restored} of {$total} images.");
    }

    /**
     * Clear all backup files.
     *
     * ## OPTIONS
     *
     * [--yes]
     * : Skip confirmation prompt.
     *
     * ## EXAMPLES
     *
     *     wp prorank image clear-backups --yes
     *
     * @when after_wp_load
     * @subcommand clear-backups
     */
    public function clear_backups($args, $assoc_args) {
        if (!isset($assoc_args['yes'])) {
            WP_CLI::confirm('Are you sure you want to delete all backup files? This cannot be undone.');
        }

        $backup_dir = $this->get_backup_directory();
        if (!is_dir($backup_dir)) {
            WP_CLI::log('No backup directory found.');
            return;
        }

        $files = $this->get_backup_files($backup_dir);
        $total = count($files);
        $total_size = 0;

        foreach ($files as $file) {
            $total_size += filesize($file);
            \ProRank\SEO\Core\FilesystemHelper::delete_file($file);
        }

        WP_CLI::success(sprintf('Deleted %d backup files, freed %s.', $total, size_format($total_size)));
    }

    /**
     * Resolve image backup directory used by the module.
     *
     * @return string
     */
    private function get_backup_directory(): string {
        $site_root        = wp_normalize_path(untrailingslashit(ABSPATH));
        $site_parent_root = wp_normalize_path(dirname($site_root));
        $site_fingerprint = md5((string) home_url('/'));

        return trailingslashit($site_parent_root) . 'prorank-image-backups-' . $site_fingerprint;
    }

    /**
     * Recursively collect backup files from backup directory.
     *
     * @param string $backup_dir
     * @return array
     */
    private function get_backup_files(string $backup_dir): array {
        $files = [];

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($backup_dir, \FilesystemIterator::SKIP_DOTS)
        );

        foreach ($iterator as $item) {
            if (!$item->isFile()) {
                continue;
            }

            if (basename($item->getPathname()) === '.htaccess') {
                continue;
            }

            $files[] = $item->getPathname();
        }

        return $files;
    }

    /**
     * Regenerate all formats for images.
     *
     * ## OPTIONS
     *
     * [--id=<id>]
     * : Regenerate for a specific attachment.
     *
     * [--format=<format>]
     * : Regenerate specific format (webp, avif).
     *
     * ## EXAMPLES
     *
     *     wp prorank image regenerate --id=123
     *     wp prorank image regenerate --id=123 --format=webp
     *
     * @when after_wp_load
     */
    public function regenerate($args, $assoc_args) {
        $id = Utils\get_flag_value($assoc_args, 'id', null);
        $format = Utils\get_flag_value($assoc_args, 'format', null);

        if (!$id) {
            WP_CLI::error('Please specify --id=<id>.');
        }

        if ($format !== null) {
            $format = strtolower((string) $format);
            if (!in_array($format, ['webp', 'avif'], true)) {
                WP_CLI::error('Invalid format. Allowed values: webp, avif.');
            }
        }

        $id = (int) $id;
        if ($id <= 0) {
            WP_CLI::error('Invalid attachment ID.');
        }

        $module = $this->get_module();
        if (!$module) {
            WP_CLI::error('Image optimization module not available.');
        }

        // Reset optimization status
        delete_post_meta($id, '_prorank_optimized');
        delete_post_meta($id, '_prorank_optimized_date');

        // Backup existing generated files before force-regeneration.
        // If regeneration fails (or produces no file), we restore the previous sidecar.
        $file_path = get_attached_file($id);
        $sidecar_backups = $this->prepare_sidecar_backups($file_path, $format);
        $this->remove_existing_sidecars($sidecar_backups);

        // Re-optimize
        $result = $this->optimize_single_image($id, $format);
        $restored_formats = $this->restore_missing_sidecars($sidecar_backups);
        $this->clear_performance_cache_after_regenerate();

        if ($result['success']) {
            if (!empty($restored_formats)) {
                WP_CLI::warning(
                    sprintf(
                        'Regeneration did not produce new %s file(s); restored previous sidecar(s).',
                        implode(', ', $restored_formats)
                    )
                );
            }
            WP_CLI::success(sprintf('Regenerated formats for attachment ID %d.', $id));
        } else {
            WP_CLI::error('Failed to regenerate: ' . $result['message']);
        }
    }

    /**
     * Build sidecar paths/backups for regenerate workflow.
     *
     * @param string|false $file_path Original attachment path.
     * @param string|null  $format Specific format filter (webp|avif|null).
     * @return array<string, array{target:string,backup:string|null}>
     */
    private function prepare_sidecar_backups($file_path, ?string $format): array {
        if (!$file_path || !is_string($file_path) || $file_path === '') {
            return [];
        }

        $formats = $format ? [$format] : ['webp', 'avif'];
        $map = [];

        foreach ($formats as $current_format) {
            $target = (string) preg_replace('/\.(jpe?g|png|gif)$/i', '.' . $current_format, $file_path);
            if ($target === '') {
                continue;
            }

            $backup = null;
            if (file_exists($target)) {
                $backup = $target . '.prorank-bak-' . gmdate('YmdHis') . '-' . wp_rand(1000, 9999);
            }

            $map[$current_format] = [
                'target' => $target,
                'backup' => $backup,
            ];
        }

        return $map;
    }

    /**
     * Move current sidecars to backup paths before regeneration.
     *
     * @param array<string, array{target:string,backup:string|null}> $sidecar_backups
     * @return void
     */
    private function remove_existing_sidecars(array &$sidecar_backups): void {
        foreach ($sidecar_backups as $format => &$paths) {
            $target = (string) ($paths['target'] ?? '');
            $backup = $paths['backup'] ?? null;

            if ($target === '' || !file_exists($target)) {
                continue;
            }

            if (!$backup || !\ProRank\SEO\Core\FilesystemHelper::move($target, $backup, true)) {
                WP_CLI::warning(sprintf('Could not backup existing %s sidecar before regenerate.', $format));
                $paths['backup'] = null;
                continue;
            }
        }
        unset($paths);
    }

    /**
     * Restore previous sidecars when regeneration does not produce them.
     *
     * @param array<string, array{target:string,backup:string|null}> $sidecar_backups
     * @return array<int, string> Formats restored from backup.
     */
    private function restore_missing_sidecars(array $sidecar_backups): array {
        $restored = [];

        foreach ($sidecar_backups as $format => $paths) {
            $target = (string) ($paths['target'] ?? '');
            $backup = $paths['backup'] ?? null;

            if ($target === '' || !$backup || !file_exists($backup)) {
                continue;
            }

            // New file exists, remove backup.
            if (file_exists($target)) {
                @\ProRank\SEO\Core\FilesystemHelper::delete_file($backup);
                continue;
            }

            if (\ProRank\SEO\Core\FilesystemHelper::move($backup, $target, true)) {
                $restored[] = (string) $format;
            }
        }

        return $restored;
    }

    /**
     * Clear performance cache after regeneration to avoid stale picture/srcset HTML.
     *
     * @return void
     */
    private function clear_performance_cache_after_regenerate(): void {
        try {
            $plugin = \ProRank\SEO\Plugin::get_instance();
            if ($plugin && method_exists($plugin, 'clear_performance_runtime_cache')) {
                $plugin->clear_performance_runtime_cache();
                return;
            }
        } catch (\Throwable $e) {
            // Fall through to action-based clear.
        }

        do_action('prorank_clear_cache');
    }

    /**
     * Check server support for image optimization.
     *
     * ## EXAMPLES
     *
     *     wp prorank image check-support
     *
     * @when after_wp_load
     * @subcommand check-support
     */
    public function check_support($args, $assoc_args) {
        WP_CLI::log('Server Capabilities:');
        WP_CLI::log('--------------------');

        // Check GD
        $gd_info = function_exists('gd_info') ? gd_info() : [];
        WP_CLI::log(sprintf('GD Library: %s', !empty($gd_info) ? 'Yes (v' . ($gd_info['GD Version'] ?? 'unknown') . ')' : 'No'));

        if (!empty($gd_info)) {
            WP_CLI::log(sprintf('  - WebP Support: %s', !empty($gd_info['WebP Support']) ? 'Yes' : 'No'));
            WP_CLI::log(sprintf('  - AVIF Support: %s', !empty($gd_info['AVIF Support']) ? 'Yes' : 'No'));
        }

        // Check ImageMagick
        $imagick_available = extension_loaded('imagick') && class_exists('Imagick');
        WP_CLI::log(sprintf('ImageMagick: %s', $imagick_available ? 'Yes' : 'No'));

        if ($imagick_available) {
            $imagick = new \Imagick();
            $formats = $imagick->queryFormats();
            WP_CLI::log(sprintf('  - WebP Support: %s', in_array('WEBP', $formats) ? 'Yes' : 'No'));
            WP_CLI::log(sprintf('  - AVIF Support: %s', in_array('AVIF', $formats) ? 'Yes' : 'No'));
        }

        // Check command-line tools
        WP_CLI::log('');
        WP_CLI::log('Command-Line Tools:');

        $binaries = [
            'cwebp' => 'WebP encoder',
            'avifenc' => 'AVIF encoder',
            'jpegoptim' => 'JPEG optimizer',
            'optipng' => 'PNG optimizer',
            'pngquant' => 'PNG quantizer',
            'gifsicle' => 'GIF optimizer',
        ];

        foreach ($binaries as $binary => $description) {
            $available = false;
            $paths = array_filter(explode(PATH_SEPARATOR, (string) getenv('PATH')));
            foreach ($paths as $path) {
                $candidate = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $binary;
                if (is_file($candidate) && is_executable($candidate)) {
                    $available = true;
                    break;
                }
            }
            WP_CLI::log(sprintf('  %s (%s): %s', $binary, $description, $available ? 'Available' : 'Not found'));
        }

        // Check PHP settings
        WP_CLI::log('');
        WP_CLI::log('PHP Settings:');
        WP_CLI::log(sprintf('  PHP Version: %s', PHP_VERSION));
        WP_CLI::log(sprintf('  Memory Limit: %s', ini_get('memory_limit')));
        WP_CLI::log(sprintf('  Max Execution Time: %ss', ini_get('max_execution_time')));
        WP_CLI::log(sprintf('  Upload Max Filesize: %s', ini_get('upload_max_filesize')));
    }
}

// Register the command with WP-CLI
if (defined('WP_CLI') && WP_CLI) {
    WP_CLI::add_command('prorank image', 'ProRank\SEO\CLI\ImageOptimizationCommand');
}
