<?php
/**
 * File State Renderer - UI Components
 * 
 * Contains all UI rendering methods for the File State Scanner.
 * 
 * @package StaticQ
 */

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

class SQMedia_File_State_Renderer_Components {
    
    /**
     * Reference to main renderer (for status constants)
     */
    private SQMedia_File_State_Renderer $renderer;
    
    /**
     * Database instance
     */
    private SQMedia_Database $db;
    
    /**
     * Constructor
     */
    public function __construct(SQMedia_File_State_Renderer $renderer, SQMedia_Database $db) {
        $this->renderer = $renderer;
        $this->db = $db;
    }
    
    /**
     * Render unified scanner card (single card replacing both phase cards)
     */
    public function render_unified_card(array $state): void {
        $status = $state['status'];
        $subtitle = $this->get_unified_subtitle($state);
        $badge = $this->get_status_badge_data($status);
        $is_legacy = $state['is_legacy'] ?? false;

        $show_rescan = !in_array($status, [
            SQMedia_File_State_Renderer::STATUS_NOT_SCANNED,
            SQMedia_File_State_Renderer::STATUS_SCANNING,
            SQMedia_File_State_Renderer::STATUS_BUILDING_INDEX,
            SQMedia_File_State_Renderer::STATUS_FIXING,
        ], true);

        ?>
        <div class="sqmedia-phase-card"
             id="sqmedia-scanner-card"
             data-status="<?php echo esc_attr($status); ?>"
             data-scan-id="<?php echo esc_attr($state['scan_id'] ?? ''); ?>">

            <div class="sqmedia-phase-header">
                <div class="sqmedia-phase-header-left">
                    <div class="sqmedia-phase-number <?php echo esc_attr($this->get_phase_number_class($status)); ?>">
                        <?php echo esc_html($status === SQMedia_File_State_Renderer::STATUS_CLEAN ? '✓' : '⚙'); ?>
                    </div>
                    <div>
                        <div class="sqmedia-phase-title"><?php esc_html_e('Media Library Scanner', 'staticq-media'); ?></div>
                        <div class="sqmedia-phase-subtitle" id="sqmedia-scanner-subtitle">
                            <?php echo esc_html($subtitle); ?>
                        </div>
                    </div>
                </div>
                <div class="sqmedia-phase-header-right">
                    <span class="sqmedia-status-badge sqmedia-status-<?php echo esc_attr($badge['class']); ?>"><?php
                        if (!empty($badge['icon'])) {
                            echo esc_html($badge['icon']) . ' ';
                        }
                        echo esc_html($badge['label']);
                    ?></span>
                    <?php if ($show_rescan): ?>
                        <button type="button" class="button button-secondary sqmedia-start-scan-btn">
                            🔄 <?php esc_html_e('Rescan', 'staticq-media'); ?>
                        </button>
                    <?php endif; ?>
                </div>
            </div>

            <div class="sqmedia-phase-body">
                <p class="sqmedia-phase-description"><?php esc_html_e('Scans your entire media library in one pass: checks metadata alignment with current settings, then verifies files exist in the correct storage locations. Issues are fixed atomically per attachment — metadata first, then files.', 'staticq-media'); ?></p>

                <?php if ($is_legacy && in_array($status, [SQMedia_File_State_Renderer::STATUS_CLEAN, SQMedia_File_State_Renderer::STATUS_ISSUES, SQMedia_File_State_Renderer::STATUS_CRITICAL], true)): ?>
                    <div class="sqmedia-alert sqmedia-alert-info" style="margin-bottom: 15px;">
                        <span class="sqmedia-alert-icon">💡</span>
                        <div><?php esc_html_e('Previous scan results are from a legacy phase-specific scan. Run a new scan for the unified experience.', 'staticq-media'); ?></div>
                    </div>
                <?php endif; ?>

                <?php
                switch ($status) {
                    case SQMedia_File_State_Renderer::STATUS_NOT_SCANNED:
                        $this->render_unified_not_scanned();
                        break;

                    case SQMedia_File_State_Renderer::STATUS_SCANNING:
                    case SQMedia_File_State_Renderer::STATUS_BUILDING_INDEX:
                        $this->render_scanning_body(0, $state);
                        break;

                    case SQMedia_File_State_Renderer::STATUS_PAUSED:
                        $this->render_paused_body(0, $state);
                        break;

                    case SQMedia_File_State_Renderer::STATUS_FIXING:
                        $this->render_fixing_body(0, $state);
                        break;

                    case SQMedia_File_State_Renderer::STATUS_CLEAN:
                        $this->render_unified_clean_body($state);
                        break;

                    case SQMedia_File_State_Renderer::STATUS_ISSUES:
                    case SQMedia_File_State_Renderer::STATUS_CRITICAL:
                        $this->render_unified_issues_body($state);
                        break;
                }

                $this->render_backup_section('mls');
                ?>
            </div>
        </div>
        <?php
    }

    /**
     * Get unified subtitle based on state
     */
    private function get_unified_subtitle(array $state): string {
        switch ($state['status']) {
            case SQMedia_File_State_Renderer::STATUS_SCANNING:
                return __('Scanning...', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_BUILDING_INDEX:
                return __('Building file index...', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_PAUSED:
                return __('Paused', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_FIXING:
                return __('Fixing issues...', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_CLEAN:
                return __('All clean — metadata and files aligned', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_ISSUES:
            case SQMedia_File_State_Renderer::STATUS_CRITICAL:
                $total_issues = $state['total_issues'] ?? 0;
                $attachments = $state['attachments_with_issues'] ?? 0;
                return sprintf(
                    // translators: %1$d: number of issues, %2$d: number of attachments
                    _n('%1$d issue across %2$d attachments', '%1$d issues across %2$d attachments', $total_issues, 'staticq-media'),
                    $total_issues,
                    $attachments
                );
            default:
                return __('Check metadata and file alignment', 'staticq-media');
        }
    }

    /**
     * Render not-scanned state for unified card
     */
    private function render_unified_not_scanned(): void {
        ?>
        <div class="sqmedia-overwrite-option" style="margin-bottom: 15px;">
            <label>
                <input type="checkbox" id="sqmedia-overwrite-existing" value="1">
                <?php esc_html_e('Regenerate all files (overwrites existing)', 'staticq-media'); ?>
            </label>
            <p class="sqmedia-hint" style="margin: 4px 0 0 24px; color: #646970; font-size: 12px;">
                <?php esc_html_e('When checked, all sized files will be flagged for regeneration regardless of whether they already exist. Use this after changing compression settings.', 'staticq-media'); ?>
            </p>
        </div>

        <button type="button" class="button button-primary sqmedia-start-scan-btn">
            🔍 <?php esc_html_e('Scan Media Library', 'staticq-media'); ?>
        </button>
        <?php
    }

    /**
     * Render clean body for unified card
     */
    private function render_unified_clean_body(array $state): void {
        $scanned_at = $state['scanned_at'] ?? '';
        $total = $state['total_scanned'] ?? 0;
        $duration = $state['duration'] ?? '';

        ?>
        <p class="sqmedia-last-scan">
            <?php
            echo esc_html(sprintf(
                // translators: %1$s: time ago, %2$s: number of media items, %3$s: duration or empty
                __('Last scan: %1$s • %2$s WP media verified%3$s', 'staticq-media'),
                human_time_diff(strtotime($scanned_at), strtotime(current_time('mysql'))) . ' ' . __('ago', 'staticq-media'),
                number_format_i18n($total),
                $duration ? ' ' . sprintf(
                    // translators: %s: scan duration
                    __('in %s', 'staticq-media'),
                    $duration
                ) : ''
            ));
            ?>
        </p>

        <?php $this->render_unified_cascade($state, true); ?>

        <div class="sqmedia-button-group">
            <button type="button" class="button button-secondary sqmedia-view-details-btn">
                👁 <?php esc_html_e('View Details', 'staticq-media'); ?>
            </button>
            <button type="button" class="button button-secondary sqmedia-start-scan-btn">
                🔍 <?php esc_html_e('Re-scan', 'staticq-media'); ?>
            </button>
        </div>

        <div class="sqmedia-overwrite-option" style="margin-top: 10px;">
            <label>
                <input type="checkbox" id="sqmedia-overwrite-existing" value="1">
                <?php esc_html_e('Regenerate all files (overwrites existing)', 'staticq-media'); ?>
            </label>
        </div>

        <div class="sqmedia-details-panel" id="sqmedia-scanner-details" style="display: none;">
            <?php $this->render_unified_details_panel($state); ?>
        </div>
        <?php
    }

    /**
     * Render issues body for unified card
     */
    private function render_unified_issues_body(array $state): void {
        $scanned_at = $state['scanned_at'] ?? '';
        $total = $state['total_scanned'] ?? 0;
        $fixable_issues = $state['fixable_issues'] ?? $state['total_issues'] ?? 0;
        $duration = $state['duration'] ?? '';
        $scan_id = $state['scan_id'] ?? '';

        ?>
        <p class="sqmedia-last-scan">
            <?php
            echo esc_html(sprintf(
                // translators: %1$s: time ago, %2$s: number of media items, %3$s: duration or empty
                __('Last scan: %1$s • Scanned %2$s WP media%3$s', 'staticq-media'),
                human_time_diff(strtotime($scanned_at), strtotime(current_time('mysql'))) . ' ' . __('ago', 'staticq-media'),
                number_format_i18n($total),
                $duration ? ' ' . sprintf(
                    // translators: %s: scan duration
                    __('in %s', 'staticq-media'),
                    $duration
                ) : ''
            ));
            ?>
        </p>

        <?php $this->render_unified_cascade($state, false); ?>

        <div class="sqmedia-button-group">
            <button type="button" class="button button-secondary sqmedia-view-details-btn">
                👁 <?php esc_html_e('View Details', 'staticq-media'); ?>
            </button>
            <?php if ($fixable_issues > 0): ?>
                <button type="button" class="button button-primary sqmedia-fix-all-btn" data-scan-id="<?php echo esc_attr($scan_id); ?>">
                    🔧 <?php esc_html_e('Fix All', 'staticq-media'); ?>
                </button>
            <?php endif; ?>
            <button type="button" class="button button-secondary sqmedia-start-scan-btn">
                🔍 <?php esc_html_e('Re-scan', 'staticq-media'); ?>
            </button>
        </div>

        <?php if ($fixable_issues > 0): ?>
        <div class="sqmedia-overwrite-option" style="margin-top: 10px;">
            <label>
                <input type="checkbox" id="sqmedia-overwrite-existing" value="1">
                <?php esc_html_e('Regenerate all files (overwrites existing)', 'staticq-media'); ?>
            </label>
        </div>
        <?php endif; ?>

        <div class="sqmedia-details-panel" id="sqmedia-scanner-details" style="display: none;">
            <?php $this->render_unified_details_panel($state); ?>
        </div>
        <?php
    }

    /**
     * Render table backup section
     *
     * @param string $scanner 'mls' or 'pcs'
     */
    private function render_backup_section(string $scanner): void {
        $backup = SQMedia_Database::get_backup_status($scanner);
        $ajax_action = ($scanner === 'mls') ? 'sqmedia_backup_mls_tables' : 'sqmedia_backup_pcs_tables';
        $tables_label = ($scanner === 'mls')
            ? 'wp_postmeta, wp_posts'
            : 'wp_posts';
        ?>
        <div class="sqmedia-backup-section" data-scanner="<?php echo esc_attr($scanner); ?>" style="margin-top: 14px; padding: 12px 16px; background: #f8f9fa; border: 1px solid #e2e4e7; border-radius: 4px; font-size: 13px;">
            <p style="margin: 0 0 8px; color: #50575e;">
                <?php esc_html_e('Scanning does not modify your data. The repair process may update', 'staticq-media'); ?>
                <code style="font-size: 12px;"><?php echo esc_html($tables_label); ?></code>.
                <?php echo esc_html($scanner === 'mls'
                    ? __('As an extra safety measure, you can back up these tables before applying fixes.', 'staticq-media')
                    : __('As an extra safety measure, you can back up this table before applying fixes.', 'staticq-media')
                ); ?>
            </p>
            <div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
                <button type="button" class="button button-secondary sqmedia-backup-tables-btn" data-action="<?php echo esc_attr($ajax_action); ?>">
                    <?php echo esc_html($scanner === 'mls'
                        ? __('Backup Tables', 'staticq-media')
                        : __('Backup Table', 'staticq-media')
                    ); ?>
                </button>
                <span class="sqmedia-backup-status" style="color: #646970; font-size: 12px;">
                    <?php if ($backup['exists']): ?>
                        <?php echo esc_html(sprintf(
                            /* translators: %s: date and time of backup */
                            __('Backup exists from %s', 'staticq-media'),
                            wp_date(get_option('date_format') . ' ' . get_option('time_format'), strtotime($backup['date']))
                        )); ?>
                    <?php else: ?>
                        <?php esc_html_e('No backup yet', 'staticq-media'); ?>
                    <?php endif; ?>
                </span>
            </div>
        </div>
        <?php
    }

    /**
     * Render unified cascade report (merged Phase 1 + Phase 2 sections)
     */
    private function render_unified_cascade(array $state, bool $is_clean): void {
        $summary = $state['results']['summary'] ?? [];
        $issue_counts = $state['issue_counts'] ?? [];
        $total = $summary['total'] ?? ($state['total_scanned'] ?? 0);
        $type_counts = $summary['type_counts'] ?? [];

        // File index counts
        $local_count = $state['results']['local_file_count'] ?? 0;
        $bucket_count = $state['results']['bucket_file_count'] ?? 0;

        $storage_mode = SQMedia_Settings::get_storage_mode();
        $includes_local = ($storage_mode === 'local_only' || $storage_mode === 'cloud_and_local');
        $includes_bucket = ($storage_mode === 'cloud_only' || $storage_mode === 'cloud_and_local');

        // Metadata issue counts
        $corrupted = $issue_counts[SQMedia_File_State_Scanner::CAT_CORRUPTED] ?? 0;
        $wrong_dimensions = $issue_counts[SQMedia_File_State_Scanner::CAT_WRONG_DIMENSIONS] ?? 0;
        $missing_sizes = $issue_counts[SQMedia_File_State_Scanner::CAT_MISSING_SIZES] ?? 0;
        $deprecated_sizes = $issue_counts[SQMedia_File_State_Scanner::CAT_DEPRECATED_SIZES] ?? 0;

        // File issue counts
        $upload_to_bucket = $issue_counts[SQMedia_File_State_Scanner::CAT_UPLOAD_TO_BUCKET] ?? 0;
        $download_to_local = $issue_counts[SQMedia_File_State_Scanner::CAT_DOWNLOAD_TO_LOCAL] ?? 0;
        $generate_sizes = $issue_counts[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES] ?? 0;
        $critical_missing = $issue_counts[SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING] ?? 0;
        $original_misplaced = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISPLACED] ?? 0;
        $original_policy = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_POLICY] ?? 0;
        $original_missing = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISSING] ?? 0;

        $files_checked = $summary['files_checked'] ?? 0;
        $with_issues = $state['attachments_with_issues'] ?? ($summary['with_issues'] ?? 0);
        $clean_count = max(0, $total - $with_issues);

        ?>
        <div class="sqmedia-cascade-report">
            <!-- Building file index -->
            <?php if ($local_count > 0 || $bucket_count > 0): ?>
            <div class="sqmedia-cascade-section">
                <div class="sqmedia-cascade-header"><?php esc_html_e('Building file index...', 'staticq-media'); ?></div>
                <div class="sqmedia-cascade-items">
                    <?php if ($includes_local && $local_count > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-success">
                        <span class="sqmedia-cascade-icon">✓</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of local files
                                _n('%s local file indexed', '%s local files indexed', $local_count, 'staticq-media'),
                                number_format_i18n($local_count)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($includes_bucket && $bucket_count > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-success">
                        <span class="sqmedia-cascade-icon">✓</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of bucket files
                                _n('%s bucket file indexed', '%s bucket files indexed', $bucket_count, 'staticq-media'),
                                number_format_i18n($bucket_count)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                </div>
            </div>
            <?php endif; ?>

            <!-- Scanning media library -->
            <div class="sqmedia-cascade-section">
                <div class="sqmedia-cascade-header"><?php esc_html_e('Scanning media library...', 'staticq-media'); ?></div>
                <div class="sqmedia-cascade-items">
                    <div class="sqmedia-cascade-item sqmedia-cascade-success">
                        <span class="sqmedia-cascade-icon">✓</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of media items
                                _n('%s WP media found', '%s WP media found', $total, 'staticq-media'),
                                number_format_i18n($total)
                            ));
                        ?></span>
                    </div>
                </div>
            </div>

            <!-- Checking metadata integrity -->
            <div class="sqmedia-cascade-section">
                <div class="sqmedia-cascade-header"><?php esc_html_e('Checking metadata integrity...', 'staticq-media'); ?></div>
                <div class="sqmedia-cascade-items">
                    <?php if ($corrupted > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                        <span class="sqmedia-cascade-icon">⚠</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of attachments
                                _n('%s attachment has corrupted metadata', '%s attachments have corrupted metadata', $corrupted, 'staticq-media'),
                                number_format_i18n($corrupted)
                            ));
                        ?></span>
                    </div>
                    <?php else: ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-success">
                        <span class="sqmedia-cascade-icon">✓</span>
                        <span class="sqmedia-cascade-text"><?php esc_html_e('All metadata is valid', 'staticq-media'); ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($missing_sizes > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                        <span class="sqmedia-cascade-icon">⚠</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of attachments
                                _n('%s attachment missing sizes', '%s attachments missing sizes', $missing_sizes, 'staticq-media'),
                                number_format_i18n($missing_sizes)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($wrong_dimensions > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                        <span class="sqmedia-cascade-icon">⚠</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of attachments
                                _n('%s attachment has wrong dimensions', '%s attachments have wrong dimensions', $wrong_dimensions, 'staticq-media'),
                                number_format_i18n($wrong_dimensions)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($deprecated_sizes > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-info">
                        <span class="sqmedia-cascade-icon">○</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of attachments
                                _n('%s attachment has deprecated sizes', '%s attachments have deprecated sizes', $deprecated_sizes, 'staticq-media'),
                                number_format_i18n($deprecated_sizes)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                </div>
            </div>

            <!-- Verifying file locations -->
            <div class="sqmedia-cascade-section">
                <div class="sqmedia-cascade-header"><?php esc_html_e('Verifying file locations...', 'staticq-media'); ?></div>
                <div class="sqmedia-cascade-items">
                    <?php if ($files_checked > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-info">
                        <span class="sqmedia-cascade-icon">→</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of files
                                _n('%s file verified', '%s files verified', $files_checked, 'staticq-media'),
                                number_format_i18n($files_checked)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($clean_count > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-success">
                        <span class="sqmedia-cascade-icon">✓</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of attachments
                                _n('%s attachment fully aligned', '%s attachments fully aligned', $clean_count, 'staticq-media'),
                                number_format_i18n($clean_count)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($upload_to_bucket > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                        <span class="sqmedia-cascade-icon">⚠</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of files
                                _n('%s file needs upload to bucket', '%s files need upload to bucket', $upload_to_bucket, 'staticq-media'),
                                number_format_i18n($upload_to_bucket)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($download_to_local > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                        <span class="sqmedia-cascade-icon">⚠</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of files
                                _n('%s file needs download to local', '%s files need download to local', $download_to_local, 'staticq-media'),
                                number_format_i18n($download_to_local)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($generate_sizes > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                        <span class="sqmedia-cascade-icon">⚠</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of files
                                _n('%s file needs to be generated', '%s files need to be generated', $generate_sizes, 'staticq-media'),
                                number_format_i18n($generate_sizes)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($critical_missing > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-error">
                        <span class="sqmedia-cascade-icon">✗</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of files
                                _n('%s master file missing (unrecoverable)', '%s master files missing (unrecoverable)', $critical_missing, 'staticq-media'),
                                number_format_i18n($critical_missing)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($original_misplaced > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                        <span class="sqmedia-cascade-icon">⚠</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of images
                                _n('%s original image in wrong location', '%s original images in wrong location', $original_misplaced, 'staticq-media'),
                                number_format_i18n($original_misplaced)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($original_policy > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                        <span class="sqmedia-cascade-icon">⚠</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of images
                                _n('%s image needs resizing or cleanup', '%s images need resizing or cleanup', $original_policy, 'staticq-media'),
                                number_format_i18n($original_policy)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                    <?php if ($original_missing > 0): ?>
                    <div class="sqmedia-cascade-item sqmedia-cascade-info">
                        <span class="sqmedia-cascade-icon">ℹ</span>
                        <span class="sqmedia-cascade-text"><?php
                            echo esc_html(sprintf(
                                // translators: %s: number of images
                                _n('%s original image missing (info only)', '%s original images missing (info only)', $original_missing, 'staticq-media'),
                                number_format_i18n($original_missing)
                            ));
                        ?></span>
                    </div>
                    <?php endif; ?>
                </div>
            </div>

            <!-- Media types -->
            <?php if (!empty($type_counts)): ?>
            <div class="sqmedia-cascade-section sqmedia-cascade-types">
                <div class="sqmedia-cascade-header"><?php esc_html_e('Media types scanned', 'staticq-media'); ?></div>
                <div class="sqmedia-cascade-types-list">
                    <?php
                    arsort($type_counts);
                    $type_icons = $this->get_extension_icons();
                    foreach ($type_counts as $ext => $count):
                        $icon = $type_icons[$ext] ?? '📄';
                    ?>
                        <span class="sqmedia-cascade-type"><?php echo esc_html($icon); ?> <?php echo esc_html(number_format_i18n($count)); ?> <?php echo esc_html(strtoupper($ext)); ?></span>
                    <?php endforeach; ?>
                </div>
            </div>
            <?php endif; ?>
        </div>
        <?php
    }

    /**
     * Render unified details panel with all issue types in one tab bar
     */
    private function render_unified_details_panel(array $state): void {
        $scan_id = $state['scan_id'];
        $issue_counts = $state['issue_counts'] ?? [];
        $total_issues = array_sum($issue_counts);
        $fixable_issues = $total_issues - ($issue_counts['critical_missing'] ?? 0) - ($issue_counts['original_missing'] ?? 0);
        $total_scanned = $state['total_scanned'] ?? 0;
        $has_critical = !empty($issue_counts[SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING]);

        // "All" tab counts distinct affected attachments (not the sum of per-category
        // counts — one attachment can match several categories). Falls back to the
        // category sum if the scan state predates the attachments_with_issues field.
        $all_tab_count = (int) ($state['attachments_with_issues'] ?? $total_issues);

        $settings_url = admin_url('admin.php?page=sqmedia-settings');
        $storage_settings_url = $settings_url . '#sqmedia_storage_mode';

        // Combined tabs — all issue categories in one tab bar
        $tabs = [
            '' => [
                'label' => __('All', 'staticq-media'),
                'count' => $all_tab_count,
                'description' => __('All issues found during the scan.', 'staticq-media'),
                'success_message' => $has_critical
                    ? __('No fixable issues found. See Critical tab for unrecoverable attachments.', 'staticq-media')
                    : sprintf(
                        // translators: %s: number of media items
                        __('All %s WP media passed every check.', 'staticq-media'),
                        number_format_i18n($total_scanned)
                    ),
            ],
            SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING => [
                'label' => __('Critical', 'staticq-media'),
                'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING] ?? 0,
                'description' => __('Master files missing from ALL storage locations — cannot be recovered automatically.', 'staticq-media'),
                'success_message' => __('All master files are present and accounted for.', 'staticq-media'),
                'is_critical' => true,
            ],
            SQMedia_File_State_Scanner::CAT_CORRUPTED => [
                'label' => __('Corrupted', 'staticq-media'),
                'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_CORRUPTED] ?? 0,
                'description' => __('Attachments with invalid metadata: missing file path, dimensions, or filesize.', 'staticq-media'),
                'success_message' => __('All metadata is valid and complete.', 'staticq-media'),
            ],
            SQMedia_File_State_Scanner::CAT_MISSING_SIZES => [
                'label' => __('Missing Sizes', 'staticq-media'),
                'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_MISSING_SIZES] ?? 0,
                'description' => __('Attachments missing size entries that should exist based on current settings.', 'staticq-media'),
                'success_message' => __('All attachments have entries for all required sizes.', 'staticq-media'),
            ],
            SQMedia_File_State_Scanner::CAT_WRONG_DIMENSIONS => [
                'label' => __('Wrong Dimensions', 'staticq-media'),
                'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_WRONG_DIMENSIONS] ?? 0,
                'description' => __('Size entries with dimensions that don\'t match current settings.', 'staticq-media'),
                'success_message' => __('All size entries have correct dimensions.', 'staticq-media'),
            ],
            SQMedia_File_State_Scanner::CAT_DEPRECATED_SIZES => [
                'label' => __('Deprecated', 'staticq-media'),
                'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_DEPRECATED_SIZES] ?? 0,
                'description' => __('Sizes in metadata that no longer exist in current settings. Fixing removes the metadata entries.', 'staticq-media'),
                'success_message' => __('No deprecated size entries found.', 'staticq-media'),
            ],
            SQMedia_File_State_Scanner::CAT_POST_CONTENT_REF => [
                'label' => __('Post References', 'staticq-media'),
                'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_POST_CONTENT_REF] ?? 0,
                'description' => __('Image files referenced in post content that will change or be removed by other fixes. Repairing updates the post content to use the correct replacement file — the same size with new dimensions, the closest larger size, or the master image.', 'staticq-media'),
                'success_message' => __('No post content references are affected by the current issues.', 'staticq-media'),
            ],
        ];

        // Cloud-related tabs: strictly gated by storage mode
        $storage_mode = SQMedia_Settings::get_storage_mode();
        $includes_bucket = ($storage_mode === 'cloud_only' || $storage_mode === 'cloud_and_local');

        if ($includes_bucket) {
            $tabs[SQMedia_File_State_Scanner::CAT_UPLOAD_TO_BUCKET] = [
                'label' => __('Upload', 'staticq-media'),
                'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_UPLOAD_TO_BUCKET] ?? 0,
                'description' => sprintf(
                    // translators: %1$s: opening link tag, %2$s: closing link tag
                    __('Files existing locally but missing from bucket. %1$sReview storage settings%2$s.', 'staticq-media'),
                    '<a href="' . esc_url($storage_settings_url) . '">', '</a>'
                ),
                'success_message' => __('All required files are present in the cloud bucket.', 'staticq-media'),
                'allow_html' => true,
            ];
        }
        if ($storage_mode === 'cloud_and_local') {
            $tabs[SQMedia_File_State_Scanner::CAT_DOWNLOAD_TO_LOCAL] = [
                'label' => __('Download', 'staticq-media'),
                'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_DOWNLOAD_TO_LOCAL] ?? 0,
                'description' => sprintf(
                    // translators: %1$s: opening link tag, %2$s: closing link tag
                    __('Files in bucket but missing locally. %1$sReview storage settings%2$s.', 'staticq-media'),
                    '<a href="' . esc_url($storage_settings_url) . '">', '</a>'
                ),
                'success_message' => __('All required files are present locally.', 'staticq-media'),
                'allow_html' => true,
            ];
        }

        $tabs[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES] = [
            'label' => __('Generate', 'staticq-media'),
            'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES] ?? 0,
            'description' => __('Files missing from all locations but recoverable from the master image.', 'staticq-media'),
            'success_message' => __('All image sizes exist in expected locations.', 'staticq-media'),
        ];

        // Original image tabs (only show when issues exist)
        $original_misplaced_count = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISPLACED] ?? 0;
        $original_policy_count = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_POLICY] ?? 0;

        if ($original_misplaced_count > 0) {
            $tabs[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISPLACED] = [
                'label' => __('Original Location', 'staticq-media'),
                'count' => $original_misplaced_count,
                'description' => __('Original image files found in wrong location per current settings. Fixing moves them to the correct path.', 'staticq-media'),
                'success_message' => __('All original images are in their expected locations.', 'staticq-media'),
            ];
        }

        if ($original_policy_count > 0) {
            $tabs[SQMedia_File_State_Scanner::CAT_ORIGINAL_POLICY] = [
                'label' => __('Original & Scaling', 'staticq-media'),
                'count' => $original_policy_count,
                'description' => __('Images that need scaling down to match your size threshold, or originals pending removal.', 'staticq-media'),
                'success_message' => __('All images match your current size and original file settings.', 'staticq-media'),
            ];
        }

        $original_missing_count = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISSING] ?? 0;
        if ($original_missing_count > 0) {
            $tabs[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISSING] = [
                'label' => __('Original Missing', 'staticq-media'),
                'count' => $original_missing_count,
                'description' => __('WordPress metadata references an original (pre-scaled) image, but the file was not found locally or in the bucket. This is informational — the scaled master file is unaffected. If this is unexpected, check your storage for the missing files.', 'staticq-media'),
                'success_message' => __('All referenced original images are present.', 'staticq-media'),
                'is_info' => true,
            ];
        }

        // Build tab data for JS
        $tab_data = [];
        foreach ($tabs as $type => $tab) {
            $tab_data[$type] = [
                'description' => $tab['description'],
                'success_message' => $tab['success_message'],
                'count' => $tab['count'],
                'allow_html' => $tab['allow_html'] ?? false,
                'is_critical' => $tab['is_critical'] ?? false,
            ];
        }

        ?>
        <div class="sqmedia-details-header">
            <h3><?php esc_html_e('Scan Results', 'staticq-media'); ?></h3>
        </div>

        <div class="sqmedia-tabs-nav">
            <?php foreach ($tabs as $type => $tab): ?>
                <button type="button"
                        class="sqmedia-tab-btn <?php echo esc_attr($type === '' ? 'active' : ''); ?> <?php echo esc_attr($tab['count'] === 0 && $type !== '' ? 'sqmedia-tab-success' : ''); ?>"
                        data-type="<?php echo esc_attr($type); ?>">
                    <?php echo esc_html($tab['label']); ?>
                    <?php if ($tab['count'] > 0): ?>
                        <span class="sqmedia-tab-count"><?php echo esc_html($tab['count']); ?></span>
                    <?php else: ?>
                        <span class="sqmedia-tab-check">✓</span>
                    <?php endif; ?>
                </button>
            <?php endforeach; ?>
        </div>

        <div class="sqmedia-tab-description-area" id="sqmedia-scanner-tab-area">
            <div class="sqmedia-tab-description" id="sqmedia-scanner-tab-desc"></div>
            <div class="sqmedia-tab-success-message" id="sqmedia-scanner-success-msg" style="display: none;">
                <span class="sqmedia-success-icon">✓</span>
                <span class="sqmedia-success-text"></span>
            </div>
        </div>

        <div class="sqmedia-search-bar" id="sqmedia-scanner-search" style="<?php echo esc_attr($total_issues === 0 ? 'display:none;' : ''); ?>">
            <input type="search" class="sqmedia-filter-search"
                   placeholder="<?php esc_attr_e('Search by filename or ID...', 'staticq-media'); ?>">
        </div>

        <script type="application/json" id="sqmedia-tab-data">
            <?php
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON_HEX_* flags neutralize <, >, &, ', " so the encoded value is safe inside a <script type="application/json"> data island.
            echo wp_json_encode($tab_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
            ?>
        </script>

        <input type="hidden" id="sqmedia-total-scanned" value="<?php echo esc_attr($total_scanned); ?>">

        <div class="sqmedia-details-table-container" id="sqmedia-scanner-table">
            <?php if ($total_issues > 0): ?>
                <?php $this->render_issues_table(0, $scan_id); ?>
            <?php else: ?>
                <div class="sqmedia-all-clear">
                    <span class="sqmedia-all-clear-icon">✓</span>
                    <p><?php
                        echo esc_html(sprintf(
                            // translators: %s: number of media items
                            __('All %s WP media passed every check.', 'staticq-media'),
                            number_format_i18n($total_scanned)
                        ));
                    ?></p>
                </div>
            <?php endif; ?>
        </div>

        <?php if ($fixable_issues > 0): ?>
        <div class="sqmedia-button-group">
            <button type="button" class="button button-primary sqmedia-fix-all-btn" data-scan-id="<?php echo esc_attr($scan_id); ?>">
                🔧 <?php echo esc_html(sprintf(
                    // translators: %d: number of attachments
                    __('Fix All %d Attachments', 'staticq-media'),
                    $state['attachments_with_issues'] ?? $state['total_issues']
                )); ?>
            </button>
        </div>
        <?php endif; ?>
        <?php
    }

    /**
     * Get phase subtitle based on state
     */
    public function get_phase_subtitle(array $state, string $default): string {
        switch ($state['status']) {
            case SQMedia_File_State_Renderer::STATUS_SCANNING:
                return __('Scanning...', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_BUILDING_INDEX:
                return __('Building file index...', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_PAUSED:
                return __('Paused', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_FIXING:
                return __('Fixing issues...', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_CLEAN:
                return __('All clean', 'staticq-media');
            case SQMedia_File_State_Renderer::STATUS_ISSUES:
            case SQMedia_File_State_Renderer::STATUS_CRITICAL:
                $file_issues = $state['total_issue_files'] ?? 0;
                $attachments = $state['attachments_with_issues'] ?? ($state['results']['summary']['with_issues'] ?? 0);
                
                // Use file count if available (phase 2), otherwise use attachment count (phase 1)
                if ($file_issues > 0) {
                    return sprintf(
                        // translators: %1$d: number of file issues, %2$d: number of attachments
                        _n('%1$d file issue across %2$d attachments', '%1$d file issues across %2$d attachments', $file_issues, 'staticq-media'),
                        $file_issues,
                        $attachments
                    );
                }

                $attachment_issues = $state['total_issues'] ?? 0;
                return sprintf(
                    // translators: %1$d: number of issues, %2$d: number of attachments
                    _n('%1$d issue across %2$d attachments', '%1$d issues across %2$d attachments', $attachment_issues, 'staticq-media'),
                    $attachment_issues,
                    $attachments
                );
            default:
                return $default;
        }
    }
    
    /**
     * Get status badge data (class, icon, label) for late escaping at echo site.
     */
    public function get_status_badge_data(string $status): array {
        $badges = [
            SQMedia_File_State_Renderer::STATUS_NOT_SCANNED => ['class' => 'not-scanned', 'icon' => '', 'label' => __('Not Scanned', 'staticq-media')],
            SQMedia_File_State_Renderer::STATUS_SCANNING => ['class' => 'scanning', 'icon' => '⏳', 'label' => __('In Progress', 'staticq-media')],
            SQMedia_File_State_Renderer::STATUS_BUILDING_INDEX => ['class' => 'scanning', 'icon' => '⏳', 'label' => __('Building Index', 'staticq-media')],
            SQMedia_File_State_Renderer::STATUS_PAUSED => ['class' => 'paused', 'icon' => '⏸️', 'label' => __('Paused', 'staticq-media')],
            SQMedia_File_State_Renderer::STATUS_FIXING => ['class' => 'fixing', 'icon' => '🔧', 'label' => __('Fixing...', 'staticq-media')],
            SQMedia_File_State_Renderer::STATUS_CLEAN => ['class' => 'clean', 'icon' => '✓', 'label' => __('Clean', 'staticq-media')],
            SQMedia_File_State_Renderer::STATUS_ISSUES => ['class' => 'issues', 'icon' => '⚠️', 'label' => __('Issues Found', 'staticq-media')],
            SQMedia_File_State_Renderer::STATUS_CRITICAL => ['class' => 'critical', 'icon' => '🔴', 'label' => __('Critical Issues', 'staticq-media')],
        ];

        return $badges[$status] ?? $badges[SQMedia_File_State_Renderer::STATUS_NOT_SCANNED];
    }
    
    /**
     * Get phase number CSS class
     */
    public function get_phase_number_class(string $status): string {
        switch ($status) {
            case SQMedia_File_State_Renderer::STATUS_CLEAN:
                return 'complete';
            case SQMedia_File_State_Renderer::STATUS_ISSUES:
                return 'warning';
            case SQMedia_File_State_Renderer::STATUS_CRITICAL:
                return 'critical';
            default:
                return '';
        }
    }
    
    /**
     * Render body for not-scanned state
     */
    public function render_not_scanned_body(int $phase, array $config, bool $disabled, array $other_phase_state): void {
        ?>
        <?php if ($phase === 2 && $other_phase_state['status'] === SQMedia_File_State_Renderer::STATUS_ISSUES): ?>
            <div class="sqmedia-alert sqmedia-alert-warning" style="margin-bottom: 15px;">
                <span class="sqmedia-alert-icon">⚠️</span>
                <div>
                    <strong><?php
                        echo esc_html(sprintf(
                            // translators: %d: number of issues
                            __('%d issues pending in Step 1', 'staticq-media'),
                            $other_phase_state['total_issues'] ?? 0
                        ));
                    ?></strong><br>
                    <?php esc_html_e('Fixing them will impact file requirements. We recommend fixing Step 1 first, then scanning here.', 'staticq-media'); ?>
                </div>
            </div>
        <?php endif; ?>

        <button type="button"
                class="button <?php echo esc_attr($disabled ? 'button-secondary' : 'button-primary'); ?> sqmedia-start-scan-btn"
                data-phase="<?php echo esc_attr($phase); ?>"
                <?php echo esc_attr($disabled ? 'disabled' : ''); ?>>
            <?php echo esc_html($config['scan_button']); ?>
        </button>
        
        <?php if ($phase === 2 && $disabled): ?>
            <p class="sqmedia-hint">💡 <?php esc_html_e('Run Step 1 first for best results', 'staticq-media'); ?></p>
        <?php endif; ?>
        <?php
    }
    
    /**
     * Render body for scanning state
     */
    public function render_scanning_body(int $phase, array $state): void {
        $prefix = $phase === 0 ? 'sqmedia-scanner' : 'sqmedia-phase-' . $phase;
        $processed = $state['processed'] ?? 0;
        $total = $state['total'] ?? 1;

        $is_building_index = $state['status'] === SQMedia_File_State_Renderer::STATUS_BUILDING_INDEX;
        $results = $state['results'] ?? [];
        $index_status = $results['index_status'] ?? 'building_local';
        $index_file_count = $results['index_file_count'] ?? 0;
        $index_current_folder = $results['index_current_folder'] ?? '';
        
        // Check if we're in a waiting state (pending or triggered)
        $is_waiting = in_array($index_status, ['pending', 'triggered', 'starting']);
        ?>
        <div class="sqmedia-progress-container" id="<?php echo esc_attr($prefix); ?>-progress">
            <div class="sqmedia-progress-text">
                <span class="sqmedia-spinner"></span>
                <span id="<?php echo esc_attr($prefix); ?>-progress-text">
                    <?php if ($is_building_index): ?>
                        <?php if ($is_waiting): ?>
                            <?php esc_html_e('Starting file index...', 'staticq-media'); ?>
                        <?php elseif ($index_status === 'building_bucket'): ?>
                            <?php
                            echo esc_html(sprintf(
                                // translators: %s: number of files found so far
                                __('Indexing bucket: %s files found...', 'staticq-media'),
                                number_format_i18n($index_file_count)
                            ));
                            ?>
                        <?php else: ?>
                            <?php
                            echo esc_html(sprintf(
                                // translators: %s: number of local files found so far
                                __('Indexing local files: %s files found...', 'staticq-media'),
                                number_format_i18n($index_file_count)
                            ));
                            ?>
                        <?php endif; ?>
                        <?php if ($index_current_folder && !$is_waiting): ?>
                            <br><small style="color:#666;"><?php
                            echo esc_html(sprintf(
                                // translators: %s: folder path being scanned
                                __('Scanning: %s', 'staticq-media'),
                                $index_current_folder
                            ));
                            ?></small>
                        <?php endif; ?>
                    <?php else: ?>
                        <?php
                        echo esc_html(sprintf(
                            // translators: %1$s: current attachment number, %2$s: total attachments
                            __('Processing attachment %1$s of %2$s', 'staticq-media'),
                            number_format_i18n($processed),
                            number_format_i18n($total)
                        ));
                        ?>
                    <?php endif; ?>
                </span>
                <span id="<?php echo esc_attr($prefix); ?>-elapsed"></span>
            </div>
        </div>

        <div class="sqmedia-scan-controls">
            <button type="button" class="button button-secondary sqmedia-pause-scan-btn">
                ⏸️ <?php esc_html_e('Pause', 'staticq-media'); ?>
            </button>
            <button type="button" class="button button-link sqmedia-cancel-scan-btn">
                ✔ <?php esc_html_e('Cancel', 'staticq-media'); ?>
            </button>
        </div>
        <?php
    }
    
    /**
     * Render body for paused state
     */
    public function render_paused_body(int $phase, array $state): void {
        $results = $state['results'] ?? [];
        
        // Check if this was a paused fix (has fix_total) or paused scan
        $is_fix = isset($results['fix_total']);
        
        if ($is_fix) {
            $processed = $results['fix_processed'] ?? 0;
            $total = $results['fix_total'] ?? 1;
            $fixed = $results['fix_success'] ?? 0;
            $errors = count($results['fix_errors'] ?? []);
            // translators: %1$s: current progress count, %2$s: total count, %3$s: percentage
            $label = __('Fixing paused at %1$s of %2$s attachments (%3$s%%)', 'staticq-media');
        } else {
            $processed = $state['processed'] ?? 0;
            $total = $state['total'] ?? 1;
            // translators: %1$s: current scan count, %2$s: total count, %3$s: percentage
            $label = __('Scan paused at %1$s of %2$s attachments (%3$s%%)', 'staticq-media');
        }

        $progress = $total > 0 ? round(($processed / $total) * 100) : 0;
        ?>
        <div class="sqmedia-progress-container">
            <div class="sqmedia-progress-bar">
                <div class="sqmedia-progress-fill sqmedia-progress-paused" style="width: <?php echo esc_attr($progress); ?>%;">
                    <?php echo esc_html($progress); ?>%
                </div>
            </div>
            <div class="sqmedia-progress-text">
                <span>
                    <?php echo esc_html(sprintf(
                        $label,
                        number_format_i18n($processed),
                        number_format_i18n($total),
                        $progress
                    )); ?>
                </span>
            </div>
        </div>

        <?php if ($is_fix): ?>
        <div class="sqmedia-fix-stats" style="margin: 10px 0; font-size: 13px; color: #646970;">
            <span class="sqmedia-fix-success">✓ <?php echo esc_html(number_format_i18n($fixed)); ?> <?php esc_html_e('fixed', 'staticq-media'); ?></span>
            &nbsp;
            <span class="sqmedia-fix-errors" <?php if ($errors > 0) echo 'style="color: #d63638;"'; ?>>✔ <?php echo esc_html(number_format_i18n($errors)); ?> <?php esc_html_e('errors', 'staticq-media'); ?></span>
        </div>
        <?php endif; ?>
        
        <div class="sqmedia-paused-actions">
            <?php if ($is_fix): ?>
            <button type="button" class="button button-primary sqmedia-resume-fix-btn" data-scan-id="<?php echo esc_attr($state['scan_id']); ?>">
                ▶️ <?php esc_html_e('Resume Fix', 'staticq-media'); ?>
            </button>
            <?php else: ?>
            <button type="button" class="button button-primary sqmedia-resume-scan-btn" data-scan-id="<?php echo esc_attr($state['scan_id']); ?>">
                ▶️ <?php esc_html_e('Resume Scan', 'staticq-media'); ?>
            </button>
            <?php endif; ?>
            <button type="button" class="button button-link sqmedia-cancel-scan-btn">
                ✔ <?php esc_html_e('Cancel & Discard', 'staticq-media'); ?>
            </button>
        </div>
        <?php
    }
    
    /**
     * Render body for fixing state
     */
    public function render_fixing_body(int $phase, array $state): void {
        $prefix = $phase === 0 ? 'sqmedia-scanner' : 'sqmedia-phase-' . $phase;
        $results = $state['results'] ?? [];
        $processed = $results['fix_processed'] ?? 0;
        $total = $results['fix_total'] ?? 1;
        $fixed = $results['fix_success'] ?? 0;
        $errors = count($results['fix_errors'] ?? []);
        $progress = $total > 0 ? round(($processed / $total) * 100) : 0;
        ?>
        <div class="sqmedia-fix-progress">
            <div class="sqmedia-progress-container" id="<?php echo esc_attr($prefix); ?>-fix-progress">
                <div class="sqmedia-progress-bar">
                    <div class="sqmedia-progress-fill" style="width: <?php echo esc_attr($progress); ?>%;">
                        <?php if ($progress > 5) echo esc_html($progress) . '%'; ?>
                    </div>
                </div>
                <div class="sqmedia-progress-text">
                    <span class="sqmedia-spinner"></span>
                    <span id="<?php echo esc_attr($prefix); ?>-fix-text"><?php
                        echo esc_html(sprintf(
                            // translators: %1$s: current attachment number, %2$s: total attachments
                            __('Fixing attachment %1$s of %2$s', 'staticq-media'),
                            number_format_i18n($processed),
                            number_format_i18n($total)
                        ));
                    ?></span>
                    <span id="<?php echo esc_attr($prefix); ?>-fix-elapsed"></span>
                </div>
            </div>

            <div class="sqmedia-fix-stats" id="<?php echo esc_attr($prefix); ?>-fix-stats" style="margin-top: 10px; font-size: 13px; color: #646970;">
                <span class="sqmedia-fix-success">✓ <?php echo esc_html(number_format_i18n($fixed)); ?> <?php esc_html_e('fixed', 'staticq-media'); ?></span>
                &nbsp;
                <span class="sqmedia-fix-errors" <?php if ($errors > 0) echo 'style="color: #d63638;"'; ?>>✔ <?php echo esc_html(number_format_i18n($errors)); ?> <?php esc_html_e('errors', 'staticq-media'); ?></span>
            </div>
            
            <div class="sqmedia-scan-controls" style="margin-top: 15px;">
                <button type="button" class="button button-secondary sqmedia-pause-fix-btn" data-scan-id="<?php echo esc_attr($state['scan_id']); ?>">
                    ⏸️ <?php esc_html_e('Pause', 'staticq-media'); ?>
                </button>
                <button type="button" class="button button-link sqmedia-stop-fix-btn" data-scan-id="<?php echo esc_attr($state['scan_id']); ?>">
                    ⏹️ <?php esc_html_e('Stop', 'staticq-media'); ?>
                </button>
            </div>
        </div>
        <?php
    }
    
    /**
     * Render body for clean state
     * Shows the same detailed report as issues state, but with all tabs showing success
     */
    public function render_clean_body(int $phase, array $state): void {
        $scanned_at = $state['scanned_at'] ?? '';
        $total = $state['total_scanned'] ?? 0;
        $duration = $state['duration'] ?? '';
        
        ?>
        <p class="sqmedia-last-scan">
            <?php
            echo esc_html(sprintf(
                // translators: %1$s: time ago, %2$s: number of media items, %3$s: duration or empty
                __('Last scan: %1$s • %2$s WP media verified%3$s', 'staticq-media'),
                human_time_diff(strtotime($scanned_at), strtotime(current_time('mysql'))) . ' ' . __('ago', 'staticq-media'),
                number_format_i18n($total),
                $duration ? ' ' . sprintf(
                    // translators: %s: scan duration
                    __('in %s', 'staticq-media'),
                    $duration
                ) : ''
            ));
            ?>
        </p>

        <?php $this->render_clean_summary($phase, $state); ?>

        <div class="sqmedia-button-group">
            <button type="button" class="button button-secondary sqmedia-view-details-btn" data-phase="<?php echo esc_attr($phase); ?>">
                👁 <?php esc_html_e('View Details', 'staticq-media'); ?>
            </button>
        </div>

        <!-- Details panel (hidden by default) - same as issues state but all clean -->
        <div class="sqmedia-details-panel" id="sqmedia-phase-<?php echo esc_attr($phase); ?>-details" style="display: none;">
            <?php $this->render_details_panel($phase, $state); ?>
        </div>
        <?php
    }
    
    /**
     * Render clean summary grid
     * Shows meaningful stats based on what was actually checked
     */
    public function render_clean_summary(int $phase, array $state): void {
        $this->render_scan_summary($phase, $state, true);
    }
    
    /**
     * Render comprehensive scan summary as cascading checks (used by both clean and issues states)
     * 
     * @param int $phase Phase number (1 or 2)
     * @param array $state Scan state data
     * @param bool $is_clean Whether this is a clean scan (no issues)
     */
    public function render_scan_summary(int $phase, array $state, bool $is_clean = false): void {
        $summary = $state['results']['summary'] ?? [];
        $issue_counts = $state['issue_counts'] ?? [];
        $total = $summary['total'] ?? ($state['total_scanned'] ?? 0);
        $type_counts = $summary['type_counts'] ?? [];
        
        // Get current settings
        $storage_mode = SQMedia_Settings::get_storage_mode();
        $webp_enabled = SQMedia_Settings::is_webp_enabled();
        $enabled_sizes = SQMedia_Settings::get_enabled_sizes();
        $size_count = count($enabled_sizes);
        
        // Phase-specific metrics
        if ($phase === 1) {
            $sizes_expected = $summary['sizes_expected'] ?? 0;
            $sizes_found = $summary['sizes_found'] ?? 0;
            $with_issues = $state['attachments_with_issues'] ?? ($summary['with_issues'] ?? 0);
            $clean_count = max(0, $total - $with_issues);
        } else {
            $files_checked = $summary['files_checked'] ?? 0;
            $files_ok = $state['files_ok'] ?? ($summary['files_ok'] ?? 0);
            $with_issues = $state['attachments_with_issues'] ?? ($summary['with_issues'] ?? 0);
            $clean_count = max(0, $total - $with_issues);
        }
        
        ?>
        <div class="sqmedia-cascade-report">
            <?php if ($phase === 1): ?>
                <?php $this->render_phase1_cascade($state, $total, $size_count, $webp_enabled, $issue_counts, $is_clean); ?>
            <?php else: ?>
                <?php $this->render_phase2_cascade($state, $total, $storage_mode, $issue_counts, $is_clean); ?>
            <?php endif; ?>
            
            <!-- Media Types Scanned -->
            <?php if (!empty($type_counts)): ?>
            <div class="sqmedia-cascade-section sqmedia-cascade-types">
                <div class="sqmedia-cascade-header"><?php esc_html_e('Media types scanned', 'staticq-media'); ?></div>
                <div class="sqmedia-cascade-types-list">
                    <?php 
                    arsort($type_counts);
                    $type_icons = $this->get_extension_icons();
                    foreach ($type_counts as $ext => $count): 
                        $icon = $type_icons[$ext] ?? '📄';
                    ?>
                        <span class="sqmedia-cascade-type"><?php echo esc_html($icon); ?> <?php echo esc_html(number_format_i18n($count)); ?> <?php echo esc_html(strtoupper($ext)); ?></span>
                    <?php endforeach; ?>
                </div>
            </div>
            <?php endif; ?>
        </div>
        <?php
    }
    
    /**
     * Render Phase 1 cascade checks
     */
    private function render_phase1_cascade(array $state, int $total, int $size_count, bool $webp_enabled, array $issue_counts, bool $is_clean): void {
        $summary = $state['results']['summary'] ?? [];
        $with_issues = $state['attachments_with_issues'] ?? ($summary['with_issues'] ?? 0);
        $clean_count = max(0, $total - $with_issues);
        
        // Get counts for each issue type
        $corrupted = $issue_counts[SQMedia_File_State_Scanner::CAT_CORRUPTED] ?? 0;
        $wrong_dimensions = $issue_counts[SQMedia_File_State_Scanner::CAT_WRONG_DIMENSIONS] ?? 0;
        $missing_sizes = $issue_counts[SQMedia_File_State_Scanner::CAT_MISSING_SIZES] ?? 0;
        $deprecated_sizes = $issue_counts[SQMedia_File_State_Scanner::CAT_DEPRECATED_SIZES] ?? 0;
        $missing_webp = $issue_counts[SQMedia_File_State_Scanner::CAT_MISSING_WEBP] ?? 0;
        
        ?>
        <!-- Step 1: Scanning -->
        <div class="sqmedia-cascade-section">
            <div class="sqmedia-cascade-header"><?php esc_html_e('Scanning media library...', 'staticq-media'); ?></div>
            <div class="sqmedia-cascade-items">
                <div class="sqmedia-cascade-item sqmedia-cascade-success">
                    <span class="sqmedia-cascade-icon">✓</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of media items
                            _n('%s WP media found', '%s WP media found', $total, 'staticq-media'),
                            number_format_i18n($total)
                        ));
                        ?>
                    </span>
                </div>
            </div>
        </div>

        <!-- Step 2: Metadata -->
        <div class="sqmedia-cascade-section">
            <div class="sqmedia-cascade-header"><?php esc_html_e('Checking metadata integrity...', 'staticq-media'); ?></div>
            <div class="sqmedia-cascade-items">
                <div class="sqmedia-cascade-item sqmedia-cascade-info">
                    <span class="sqmedia-cascade-icon">→</span>
                    <span class="sqmedia-cascade-text"><?php esc_html_e('Verifying file paths, dimensions, and filesize', 'staticq-media'); ?></span>
                </div>
                <?php if ($corrupted > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                    <span class="sqmedia-cascade-icon">⚠</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of media items
                            _n('%s WP media has corrupted metadata', '%s WP media have corrupted metadata', $corrupted, 'staticq-media'),
                            number_format_i18n($corrupted)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Details below', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php else: ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-success">
                    <span class="sqmedia-cascade-icon">✓</span>
                    <span class="sqmedia-cascade-text"><?php esc_html_e('All metadata is valid', 'staticq-media'); ?></span>
                </div>
                <?php endif; ?>
            </div>
        </div>

        <!-- Step 3: Thumbnail Sizes -->
        <div class="sqmedia-cascade-section">
            <div class="sqmedia-cascade-header"><?php esc_html_e('Validating thumbnail sizes...', 'staticq-media'); ?></div>
            <div class="sqmedia-cascade-items">
                <div class="sqmedia-cascade-item sqmedia-cascade-info">
                    <span class="sqmedia-cascade-icon">→</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of registered sizes
                            _n('Looking for %s registered size per image', 'Looking for %s registered sizes per image', $size_count, 'staticq-media'),
                            number_format_i18n($size_count)
                        ));
                        ?>
                    </span>
                </div>
                <?php
                $sizes_ok = $total - $wrong_dimensions - $missing_sizes;
                if ($sizes_ok > 0):
                ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-success">
                    <span class="sqmedia-cascade-icon">✓</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of images
                            _n('%s image has all sizes correct', '%s images have all sizes correct', $sizes_ok, 'staticq-media'),
                            number_format_i18n($sizes_ok)
                        ));
                        ?>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($wrong_dimensions > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                    <span class="sqmedia-cascade-icon">⚠</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of images
                            _n('%s image has wrong dimensions', '%s images have wrong dimensions', $wrong_dimensions, 'staticq-media'),
                            number_format_i18n($wrong_dimensions)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Details below', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($missing_sizes > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                    <span class="sqmedia-cascade-icon">⚠</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of images
                            _n('%s image is missing sizes', '%s images are missing sizes', $missing_sizes, 'staticq-media'),
                            number_format_i18n($missing_sizes)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Details below', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($deprecated_sizes > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-info">
                    <span class="sqmedia-cascade-icon">○</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of images
                            _n('%s image has deprecated sizes (optional cleanup)', '%s images have deprecated sizes (optional cleanup)', $deprecated_sizes, 'staticq-media'),
                            number_format_i18n($deprecated_sizes)
                        ));
                        ?>
                    </span>
                </div>
                <?php endif; ?>
            </div>
        </div>

        <?php if ($webp_enabled): ?>
        <!-- Step 4: WebP Variants -->
        <div class="sqmedia-cascade-section">
            <div class="sqmedia-cascade-header"><?php esc_html_e('Checking WebP variants...', 'staticq-media'); ?></div>
            <div class="sqmedia-cascade-items">
                <div class="sqmedia-cascade-item sqmedia-cascade-success">
                    <span class="sqmedia-cascade-icon">✓</span>
                    <span class="sqmedia-cascade-text"><?php esc_html_e('WebP generation is enabled', 'staticq-media'); ?></span>
                </div>
                <?php
                $webp_ok = $total - $missing_webp;
                if ($webp_ok > 0):
                ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-success">
                    <span class="sqmedia-cascade-icon">✓</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of images
                            _n('%s image has WebP variants', '%s images have WebP variants', $webp_ok, 'staticq-media'),
                            number_format_i18n($webp_ok)
                        ));
                        ?>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($missing_webp > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                    <span class="sqmedia-cascade-icon">⚠</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of images
                            _n('%s image needs WebP entries', '%s images need WebP entries', $missing_webp, 'staticq-media'),
                            number_format_i18n($missing_webp)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Details below', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
            </div>
        </div>
        <?php endif; ?>
        <?php
    }
    
    /**
     * Render Phase 2 cascade checks
     */
    private function render_phase2_cascade(array $state, int $total, string $storage_mode, array $issue_counts, bool $is_clean): void {
        $summary = $state['results']['summary'] ?? [];
        $files_checked = $summary['files_checked'] ?? 0;
        $with_issues = $state['attachments_with_issues'] ?? ($summary['with_issues'] ?? 0);
        $clean_count = max(0, $total - $with_issues);
        
        // File index counts
        $local_count = $state['results']['local_file_count'] ?? 0;
        $bucket_count = $state['results']['bucket_file_count'] ?? 0;
        
        // Get counts for each issue type
        $upload_to_bucket = $issue_counts[SQMedia_File_State_Scanner::CAT_UPLOAD_TO_BUCKET] ?? 0;
        $download_to_local = $issue_counts[SQMedia_File_State_Scanner::CAT_DOWNLOAD_TO_LOCAL] ?? 0;
        $generate_sizes = $issue_counts[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES] ?? 0;
        $critical_missing = $issue_counts[SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING] ?? 0;
        $original_misplaced = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISPLACED] ?? 0;
        $original_policy = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_POLICY] ?? 0;
        $original_missing = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISSING] ?? 0;

        $includes_local = ($storage_mode === 'local_only' || $storage_mode === 'cloud_and_local');
        $includes_bucket = ($storage_mode === 'cloud_only' || $storage_mode === 'cloud_and_local');
        
        ?>
        <!-- Step 1: Building Index -->
        <div class="sqmedia-cascade-section">
            <div class="sqmedia-cascade-header"><?php esc_html_e('Building file index...', 'staticq-media'); ?></div>
            <div class="sqmedia-cascade-items">
                <?php if ($includes_local && $local_count > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-success">
                    <span class="sqmedia-cascade-icon">✓</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of local files
                            _n('%s local file indexed', '%s local files indexed', $local_count, 'staticq-media'),
                            number_format_i18n($local_count)
                        ));
                        ?>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($includes_bucket && $bucket_count > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-success">
                    <span class="sqmedia-cascade-icon">✓</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of bucket files
                            _n('%s bucket file indexed', '%s bucket files indexed', $bucket_count, 'staticq-media'),
                            number_format_i18n($bucket_count)
                        ));
                        ?>
                    </span>
                </div>
                <?php endif; ?>
            </div>
        </div>

        <!-- Step 2: Scanning WP Media -->
        <div class="sqmedia-cascade-section">
            <div class="sqmedia-cascade-header"><?php esc_html_e('Scanning WP media...', 'staticq-media'); ?></div>
            <div class="sqmedia-cascade-items">
                <div class="sqmedia-cascade-item sqmedia-cascade-success">
                    <span class="sqmedia-cascade-icon">✓</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of media items
                            _n('%s WP media scanned', '%s WP media scanned', $total, 'staticq-media'),
                            number_format_i18n($total)
                        ));
                        ?>
                    </span>
                </div>
                <div class="sqmedia-cascade-item sqmedia-cascade-info">
                    <span class="sqmedia-cascade-icon">→</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of files
                            _n('%s file verified', '%s files verified', $files_checked, 'staticq-media'),
                            number_format_i18n($files_checked)
                        ));
                        ?>
                    </span>
                </div>
            </div>
        </div>

        <!-- Step 3: Verifying Storage -->
        <div class="sqmedia-cascade-section">
            <div class="sqmedia-cascade-header"><?php esc_html_e('Verifying file locations...', 'staticq-media'); ?></div>
            <div class="sqmedia-cascade-items">
                <?php if ($clean_count > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-success">
                    <span class="sqmedia-cascade-icon">✓</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of media items
                            _n('%s WP media fully synced', '%s WP media fully synced', $clean_count, 'staticq-media'),
                            number_format_i18n($clean_count)
                        ));
                        ?>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($upload_to_bucket > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                    <span class="sqmedia-cascade-icon">⚠</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of files
                            _n('%s file needs upload to bucket', '%s files need upload to bucket', $upload_to_bucket, 'staticq-media'),
                            number_format_i18n($upload_to_bucket)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Details below', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($download_to_local > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                    <span class="sqmedia-cascade-icon">⚠</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of files
                            _n('%s file needs download to local', '%s files need download to local', $download_to_local, 'staticq-media'),
                            number_format_i18n($download_to_local)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Details below', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($generate_sizes > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                    <span class="sqmedia-cascade-icon">⚠</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of files
                            _n('%s file needs to be generated', '%s files need to be generated', $generate_sizes, 'staticq-media'),
                            number_format_i18n($generate_sizes)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Details below', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($critical_missing > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-error">
                    <span class="sqmedia-cascade-icon">✗</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of files
                            _n('%s master file missing everywhere (unrecoverable)', '%s master files missing everywhere (unrecoverable)', $critical_missing, 'staticq-media'),
                            number_format_i18n($critical_missing)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Critical tab', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($original_misplaced > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                    <span class="sqmedia-cascade-icon">⚠</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of images
                            _n('%s original image in wrong location', '%s original images in wrong location', $original_misplaced, 'staticq-media'),
                            number_format_i18n($original_misplaced)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Original Location tab', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($original_policy > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-warning">
                    <span class="sqmedia-cascade-icon">⚠</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of images
                            _n('%s image needs resizing or cleanup', '%s images need resizing or cleanup', $original_policy, 'staticq-media'),
                            number_format_i18n($original_policy)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Original & Scaling tab', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
                <?php if ($original_missing > 0): ?>
                <div class="sqmedia-cascade-item sqmedia-cascade-info">
                    <span class="sqmedia-cascade-icon">ℹ</span>
                    <span class="sqmedia-cascade-text">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %s: number of images
                            _n('%s original image missing (info only)', '%s original images missing (info only)', $original_missing, 'staticq-media'),
                            number_format_i18n($original_missing)
                        ));
                        ?>
                        <span class="sqmedia-cascade-action"><?php esc_html_e('— see Original Missing tab', 'staticq-media'); ?></span>
                    </span>
                </div>
                <?php endif; ?>
            </div>
        </div>
        <?php
    }

    /**
     * Get icons for file extensions
     */
    private function get_extension_icons(): array {
        return [
            // Images
            'jpg' => '🖼️',
            'jpeg' => '🖼️',
            'png' => '🖼️',
            'gif' => '🖼️',
            'webp' => '🖼️',
            'svg' => '🎨',
            'bmp' => '🖼️',
            'ico' => '🖼️',
            'tiff' => '🖼️',
            'avif' => '🖼️',
            
            // Documents
            'pdf' => '📕',
            'doc' => '📘',
            'docx' => '📘',
            'xls' => '📗',
            'xlsx' => '📗',
            'ppt' => '📙',
            'pptx' => '📙',
            'txt' => '📄',
            'csv' => '📊',
            
            // Audio
            'mp3' => '🎵',
            'wav' => '🎵',
            'ogg' => '🎵',
            'flac' => '🎵',
            'aac' => '🎵',
            'm4a' => '🎵',
            
            // Video
            'mp4' => '🎬',
            'webm' => '🎬',
            'ogv' => '🎬',
            'mov' => '🎬',
            'avi' => '🎬',
            'wmv' => '🎬',
            'flv' => '🎬',
            
            // Archives
            'zip' => '📦',
            'rar' => '📦',
            '7z' => '📦',
        ];
    }
    
    /**
     * Render body for issues state
     */
    public function render_issues_body(int $phase, array $state, array $other_phase_state): void {
        $scanned_at = $state['scanned_at'] ?? '';
        $total = $state['total_scanned'] ?? 0;
        $duration = $state['duration'] ?? '';
        $issue_counts = $state['issue_counts'] ?? [];
        $scan_id = $state['scan_id'] ?? '';
        
        ?>
        <p class="sqmedia-last-scan">
            <?php
            echo esc_html(sprintf(
                // translators: %1$s: time ago, %2$s: number of media items, %3$s: duration or empty
                __('Last scan: %1$s • Scanned %2$s WP media%3$s', 'staticq-media'),
                human_time_diff(strtotime($scanned_at), strtotime(current_time('mysql'))) . ' ' . __('ago', 'staticq-media'),
                number_format_i18n($total),
                $duration ? ' ' . sprintf(
                    // translators: %s: scan duration
                    __('in %s', 'staticq-media'),
                    $duration
                ) : ''
            ));
            ?>
        </p>

        <?php
        // Show warning if other phase has pending issues
        if ($phase === 2 && $other_phase_state['status'] === SQMedia_File_State_Renderer::STATUS_ISSUES): ?>
            <div class="sqmedia-alert sqmedia-alert-warning" style="margin-bottom: 15px;">
                <span class="sqmedia-alert-icon">⚠️</span>
                <div><?php esc_html_e('Step 1 has pending issues. Fixing Step 1 will require rescanning Step 2.', 'staticq-media'); ?></div>
            </div>
        <?php endif; ?>

        <?php $this->render_issues_summary($phase, $state); ?>

        <div class="sqmedia-button-group">
            <button type="button" class="button button-secondary sqmedia-view-details-btn" data-phase="<?php echo esc_attr($phase); ?>">
                👁 <?php esc_html_e('View Details', 'staticq-media'); ?>
            </button>
            <button type="button" class="button button-primary sqmedia-fix-all-btn" data-phase="<?php echo esc_attr($phase); ?>" data-scan-id="<?php echo esc_attr($scan_id); ?>">
                🔧 <?php esc_html_e('Fix All', 'staticq-media'); ?>
            </button>
        </div>

        <!-- Details panel (hidden by default) -->
        <div class="sqmedia-details-panel" id="sqmedia-phase-<?php echo esc_attr($phase); ?>-details" style="display: none;">
            <?php $this->render_details_panel($phase, $state); ?>
        </div>
        <?php
    }
    
    /**
     * Render critical issues section
     */
    public function render_critical_section(array $state): void {
        $scan_id = $state['scan_id'];
        $critical_items = $this->db->get_scan_items($scan_id, 'critical_missing', 10);
        $critical_count = $state['issue_counts']['critical_missing'] ?? 0;
        
        ?>
        <div class="sqmedia-critical-section">
            <div class="sqmedia-critical-header">
                🔴 <?php echo esc_html(sprintf(
                    // translators: %d: number of attachments
                    __('%d Attachments Cannot Be Repaired', 'staticq-media'),
                    $critical_count
                )); ?>
            </div>
            <p class="sqmedia-critical-description">
                <?php esc_html_e('These attachments have no source file available. Manual action required.', 'staticq-media'); ?>
            </p>
            <ul class="sqmedia-critical-list">
                <?php foreach ($critical_items as $item): ?>
                    <li>
                        <span class="sqmedia-attachment-id">#<?php echo esc_html($item['attachment_id']); ?></span>
                        <span class="sqmedia-filename"><?php echo esc_html($item['details']['filename'] ?? 'Unknown'); ?></span>
                        <span class="sqmedia-critical-reason">— <?php esc_html_e('Master missing from all locations', 'staticq-media'); ?></span>
                    </li>
                <?php endforeach; ?>
                <?php if ($critical_count > 10): ?>
                    <li class="sqmedia-more-items">
                        <?php
                        echo esc_html(sprintf(
                            // translators: %d: number of additional items not shown
                            __('... and %d more', 'staticq-media'),
                            $critical_count - 10
                        ));
                        ?>
                    </li>
                <?php endif; ?>
            </ul>
        </div>
        <?php
    }
    
    /**
     * Render issues summary using cascading report format
     */
    public function render_issues_summary(int $phase, array $state): void {
        // Use the same cascading format as clean summary
        $this->render_scan_summary($phase, $state, false);
    }
    
    /**
     * Render verification checklist and type breakdown
     * @deprecated Now handled by render_scan_summary cascade view
     */
    private function render_verification_and_types(int $phase, array $state): void {
        // This method is kept for backward compatibility but is no longer used
        // The cascade view in render_scan_summary includes type breakdown
    }
    
    /**
     * Render details panel with tabs
     * Works for both clean state (all tabs show success) and issues state
     */
    public function render_details_panel(int $phase, array $state): void {
        $scan_id = $state['scan_id'];
        $issue_counts = $state['issue_counts'] ?? [];
        $total_issues = array_sum($issue_counts);
        $total_scanned = $state['total_scanned'] ?? 0;

        // "All" tab counts distinct affected attachments (not the sum of per-category
        // counts — one attachment can match several categories). Falls back to the
        // category sum if the scan state predates the attachments_with_issues field.
        $all_tab_count = (int) ($state['attachments_with_issues'] ?? $total_issues);

        // Settings page URL with anchor
        $settings_url = admin_url('admin.php?page=sqmedia-settings');
        $webp_settings_url = $settings_url . '#sqmedia_webp_row';
        $storage_settings_url = $settings_url . '#sqmedia_storage_mode';
        
        // Check if WebP is enabled
        $webp_enabled = SQMedia_Settings::is_webp_enabled();
        
        // Define tabs for this phase with full descriptions
        // Order matches the scan test sequence
        if ($phase === 1) {
            // Check if there are critical (unrecoverable) issues
            $has_critical = !empty($issue_counts[SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING]);

            $tabs = [
                '' => [
                    'label' => __('All', 'staticq-media'),
                    'count' => $all_tab_count,
                    'description' => __('All issues found during the database scan.', 'staticq-media'),
                    'success_message' => $has_critical
                        ? __('No fixable issues found. See Critical tab for unrecoverable attachments.', 'staticq-media')
                        : sprintf(
                            // translators: %s: number of media items
                            __('All %s WP media passed every database check.', 'staticq-media'),
                            number_format_i18n($total_scanned)
                        ),
                ],
                SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING => [
                    'label' => __('Critical', 'staticq-media'),
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING] ?? 0,
                    'description' => __('Master files that are missing from ALL storage locations (local and bucket) and cannot be recovered. These attachments have corrupted metadata that cannot be fixed because the source file is gone. Consider re-uploading the original files or removing these attachments from the media library.', 'staticq-media'),
                    'success_message' => __('All master files with metadata issues were found and can be fixed.', 'staticq-media'),
                    'is_critical' => true,
                ],
                SQMedia_File_State_Scanner::CAT_CORRUPTED => [
                    'label' => __('Corrupted', 'staticq-media'),
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_CORRUPTED] ?? 0,
                    'description' => __('Checks that every attachment has valid metadata for the master file: file path, width, height, and filesize. Corrupted metadata can prevent images from displaying correctly or break image processing.', 'staticq-media'),
                    'success_message' => __('All WP media have valid, complete metadata.', 'staticq-media'),
                ],
                SQMedia_File_State_Scanner::CAT_MISSING_SIZES => [
                    'label' => __('Missing Sizes', 'staticq-media'), 
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_MISSING_SIZES] ?? 0,
                    'description' => __('Identifies attachments missing sizes that are defined in current settings. This typically happens when new image sizes are added to your theme or plugin after images were already uploaded.', 'staticq-media'),
                    'success_message' => __('All WP media have entries for all required image sizes.', 'staticq-media'),
                ],
                SQMedia_File_State_Scanner::CAT_WRONG_DIMENSIONS => [
                    'label' => __('Wrong Dimensions', 'staticq-media'), 
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_WRONG_DIMENSIONS] ?? 0,
                    'description' => __('Verifies that each image size has the correct dimensions and filename based on current settings. Mismatches occur when size settings (width, height, or crop mode) are changed after images were uploaded.', 'staticq-media'),
                    'success_message' => __('All image sizes have correct dimensions matching current settings.', 'staticq-media'),
                ],
                SQMedia_File_State_Scanner::CAT_DEPRECATED_SIZES => [
                    'label' => __('Deprecated', 'staticq-media'), 
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_DEPRECATED_SIZES] ?? 0,
                    'description' => __('Finds sizes in metadata that no longer exist in current settings. This happens when image sizes are removed from your theme or disabled in settings. Fixing removes the metadata entries; the actual files can be cleaned up later with the Orphan Files tool.', 'staticq-media'),
                    'success_message' => __('No deprecated size entries found in metadata.', 'staticq-media'),
                ],
            ];
            
            // Only include WebP tab if WebP is enabled
            if ($webp_enabled) {
                $tabs[SQMedia_File_State_Scanner::CAT_MISSING_WEBP] = [
                    'label' => __('Missing WebP', 'staticq-media'), 
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_MISSING_WEBP] ?? 0,
                    'description' => sprintf(
                        // translators: %1$s: opening link tag, %2$s: closing link tag
                        __('Checks that WebP entries exist in metadata for each image size. WebP is a modern format that provides superior compression. You can %1$smanage WebP settings here%2$s.', 'staticq-media'),
                        '<a href="' . esc_url($webp_settings_url) . '">',
                        '</a>'
                    ),
                    'success_message' => __('All WP media have WebP entries in metadata.', 'staticq-media'),
                    'allow_html' => true,
                ];
            } else {
                $tabs[SQMedia_File_State_Scanner::CAT_MISSING_WEBP] = [
                    'label' => __('WebP', 'staticq-media'),
                    'count' => 0,
                    'description' => sprintf(
                        // translators: %1$s: opening link tag, %2$s: closing link tag
                        __('WebP generation is currently disabled. Enable Cloudflare image resizing in %1$ssettings%2$s to automatically create WebP variants for all your images.', 'staticq-media'),
                        '<a href="' . esc_url($webp_settings_url) . '">',
                        '</a>'
                    ),
                    'success_message' => __('WebP generation is not enabled — no check performed.', 'staticq-media'),
                    'allow_html' => true,
                ];
            }
        } else {
            // Check if there are critical (unrecoverable) issues
            $has_critical = !empty($issue_counts[SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING]);
            
            $tabs = [
                '' => [
                    'label' => __('All', 'staticq-media'),
                    'count' => $all_tab_count,
                    'description' => __('All file sync issues found during the scan.', 'staticq-media'),
                    'success_message' => $has_critical
                        ? __('No fixable issues found. See Critical tab for unrecoverable attachments.', 'staticq-media')
                        : sprintf(
                            // translators: %s: number of media items
                            __('All %s WP media passed every file check.', 'staticq-media'),
                            number_format_i18n($total_scanned)
                        ),
                ],
                SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING => [
                    'label' => __('Critical', 'staticq-media'), 
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING] ?? 0,
                    'description' => __('Identifies master files that are missing from ALL storage locations and cannot be recovered. These attachments may need to be re-uploaded or removed from the media library.', 'staticq-media'),
                    'success_message' => __('All master files are present and accounted for.', 'staticq-media'),
                    'is_critical' => true,
                ],
                SQMedia_File_State_Scanner::CAT_GENERATE_SIZES => [
                    'label' => __('Generate', 'staticq-media'),
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES] ?? 0,
                    'description' => __('Image sizes that are missing from all storage locations but can be regenerated from the master file. This typically includes thumbnails and WebP variants.', 'staticq-media'),
                    'success_message' => $has_critical
                        ? __('No additional sizes need to be generated.', 'staticq-media')
                        : __('All image sizes and variants exist in the expected locations.', 'staticq-media'),
                ],
            ];

            // Add upload/download tabs: strictly gated by storage mode
            $legacy_storage_mode = SQMedia_Settings::get_storage_mode();
            $legacy_includes_bucket = ($legacy_storage_mode === 'cloud_only' || $legacy_storage_mode === 'cloud_and_local');

            if ($legacy_includes_bucket) {
                // Insert before generate_sizes tab
                $generate_tab = $tabs[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES];
                unset($tabs[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES]);
                $tabs[SQMedia_File_State_Scanner::CAT_UPLOAD_TO_BUCKET] = [
                    'label' => __('Upload', 'staticq-media'),
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_UPLOAD_TO_BUCKET] ?? 0,
                    'description' => sprintf(
                        // translators: %1$s: opening link tag, %2$s: closing link tag
                        __('Files that exist locally but are missing from the cloud bucket. You can %1$sreview storage settings here%2$s.', 'staticq-media'),
                        '<a href="' . esc_url($storage_settings_url) . '">', '</a>'
                    ),
                    'success_message' => $has_critical
                        ? __('No additional files need to be uploaded to the bucket.', 'staticq-media')
                        : __('All required files are present in the cloud bucket.', 'staticq-media'),
                    'allow_html' => true,
                ];
                $tabs[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES] = $generate_tab;
            }
            if ($legacy_storage_mode === 'cloud_and_local') {
                $generate_tab = $tabs[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES];
                unset($tabs[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES]);
                $tabs[SQMedia_File_State_Scanner::CAT_DOWNLOAD_TO_LOCAL] = [
                    'label' => __('Download', 'staticq-media'),
                    'count' => $issue_counts[SQMedia_File_State_Scanner::CAT_DOWNLOAD_TO_LOCAL] ?? 0,
                    'description' => sprintf(
                        // translators: %1$s: opening link tag, %2$s: closing link tag
                        __('Files in the cloud bucket but missing locally. You can %1$sreview storage settings here%2$s.', 'staticq-media'),
                        '<a href="' . esc_url($storage_settings_url) . '">', '</a>'
                    ),
                    'success_message' => $has_critical
                        ? __('No additional files need to be downloaded.', 'staticq-media')
                        : __('All required files are present locally.', 'staticq-media'),
                    'allow_html' => true,
                ];
                $tabs[SQMedia_File_State_Scanner::CAT_GENERATE_SIZES] = $generate_tab;
            }

            // Original image tabs (only show when issues exist)
            $legacy_original_misplaced = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISPLACED] ?? 0;
            $legacy_original_policy = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_POLICY] ?? 0;

            if ($legacy_original_misplaced > 0) {
                $tabs[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISPLACED] = [
                    'label' => __('Original Location', 'staticq-media'),
                    'count' => $legacy_original_misplaced,
                    'description' => __('Original image files found in wrong location per current settings. Fixing moves them to the correct path.', 'staticq-media'),
                    'success_message' => __('All original images are in their expected locations.', 'staticq-media'),
                ];
            }

            if ($legacy_original_policy > 0) {
                $tabs[SQMedia_File_State_Scanner::CAT_ORIGINAL_POLICY] = [
                    'label' => __('Original & Scaling', 'staticq-media'),
                    'count' => $legacy_original_policy,
                    'description' => __('Images that need scaling down to match your size threshold, or originals pending removal.', 'staticq-media'),
                    'success_message' => __('All images match your current size and original file settings.', 'staticq-media'),
                ];
            }

            $legacy_original_missing = $issue_counts[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISSING] ?? 0;
            if ($legacy_original_missing > 0) {
                $tabs[SQMedia_File_State_Scanner::CAT_ORIGINAL_MISSING] = [
                    'label' => __('Original Missing', 'staticq-media'),
                    'count' => $legacy_original_missing,
                    'description' => __('WordPress metadata references an original (pre-scaled) image, but the file was not found locally or in the bucket. This is informational — the scaled master file is unaffected. If this is unexpected, check your storage for the missing files.', 'staticq-media'),
                    'success_message' => __('All referenced original images are present.', 'staticq-media'),
                    'is_info' => true,
                ];
            }
        }
        
        // Build tab data for JS (descriptions and success messages)
        $tab_data = [];
        foreach ($tabs as $type => $tab) {
            $tab_data[$type] = [
                'description' => $tab['description'],
                'success_message' => $tab['success_message'],
                'count' => $tab['count'],
                'allow_html' => $tab['allow_html'] ?? false,
                'is_critical' => $tab['is_critical'] ?? false,
            ];
        }
        
        ?>
        <div class="sqmedia-details-header">
            <h3><?php esc_html_e('Scan Results', 'staticq-media'); ?></h3>
        </div>
        
        <!-- Tabs Navigation - Show ALL tabs, with checkmarks for clean ones -->
        <div class="sqmedia-tabs-nav" data-phase="<?php echo esc_attr($phase); ?>">
            <?php foreach ($tabs as $type => $tab): ?>
                <button type="button" 
                        class="sqmedia-tab-btn <?php echo esc_attr($type === '' ? 'active' : ''); ?> <?php echo esc_attr($tab['count'] === 0 && $type !== '' ? 'sqmedia-tab-success' : ''); ?>" 
                        data-type="<?php echo esc_attr($type); ?>"
                        data-phase="<?php echo esc_attr($phase); ?>">
                    <?php echo esc_html($tab['label']); ?>
                    <?php if ($tab['count'] > 0): ?>
                        <span class="sqmedia-tab-count"><?php echo esc_html($tab['count']); ?></span>
                    <?php else: ?>
                        <span class="sqmedia-tab-check">✓</span>
                    <?php endif; ?>
                </button>
            <?php endforeach; ?>
        </div>
        
        <!-- Tab Description Area -->
        <div class="sqmedia-tab-description-area" id="sqmedia-phase-<?php echo esc_attr($phase); ?>-tab-area">
            <div class="sqmedia-tab-description" id="sqmedia-phase-<?php echo esc_attr($phase); ?>-tab-desc"></div>
            <div class="sqmedia-tab-success-message" id="sqmedia-phase-<?php echo esc_attr($phase); ?>-success-msg" style="display: none;">
                <span class="sqmedia-success-icon">✓</span>
                <span class="sqmedia-success-text"></span>
                <span class="sqmedia-success-count"></span>
            </div>
        </div>
        
        <!-- Search Bar (only show when there are issues) -->
        <div class="sqmedia-search-bar" id="sqmedia-phase-<?php echo esc_attr($phase); ?>-search" style="<?php echo esc_attr($total_issues === 0 ? 'display:none;' : ''); ?>">
            <input type="search" class="sqmedia-filter-search" data-phase="<?php echo esc_attr($phase); ?>"
                   placeholder="<?php esc_attr_e('Search by filename or ID...', 'staticq-media'); ?>">
        </div>
        
        <!-- Tab Data (hidden, used by JS) -->
        <script type="application/json" id="sqmedia-tab-data-<?php echo esc_attr($phase); ?>">
            <?php
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON_HEX_* flags neutralize <, >, &, ', " so the encoded value is safe inside a <script type="application/json"> data island.
            echo wp_json_encode($tab_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
            ?>
        </script>
        
        <!-- Total scanned count for success messages -->
        <input type="hidden" id="sqmedia-phase-<?php echo esc_attr($phase); ?>-total-scanned" value="<?php echo esc_attr($total_scanned); ?>">
        
        <div class="sqmedia-details-table-container" id="sqmedia-phase-<?php echo esc_attr($phase); ?>-table">
            <?php if ($total_issues > 0): ?>
                <?php $this->render_issues_table($phase, $scan_id); ?>
            <?php else: ?>
                <div class="sqmedia-all-clear">
                    <span class="sqmedia-all-clear-icon">✓</span>
                    <p><?php
                        echo esc_html(sprintf(
                            // translators: %s: number of media items
                            __('Excellent! All %s WP media passed every check.', 'staticq-media'),
                            number_format_i18n($total_scanned)
                        ));
                    ?></p>
                </div>
            <?php endif; ?>
        </div>
        
        <?php if ($total_issues > 0): ?>
        <div class="sqmedia-button-group">
            <button type="button" class="button button-primary sqmedia-fix-all-btn" data-phase="<?php echo esc_attr($phase); ?>" data-scan-id="<?php echo esc_attr($scan_id); ?>">
                🔧 <?php echo esc_html(sprintf(
                    // translators: %d: number of attachments
                    __('Fix All %d Attachments', 'staticq-media'),
                    $state['results']['summary']['with_issues'] ?? $state['total_issues']
                )); ?>
            </button>
        </div>
        <?php endif; ?>
        <?php
    }
    
    /**
     * Render issues table
     */
    public function render_issues_table(int $phase, string $scan_id, string $filter_type = '', int $page = 1, int $per_page = 25): void {
        global $wpdb;
        
        $table = SQMedia_Database::get_table(SQMedia_Database::TABLE_SCAN_ITEMS);
        
        $where = $wpdb->prepare("scan_id = %s", $scan_id);
        if ($filter_type) {
            $where .= $wpdb->prepare(" AND action_type = %s", $filter_type);
        }
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $total = (int) $wpdb->get_var("SELECT COUNT(DISTINCT attachment_id) FROM {$table} WHERE {$where}");
        $offset = ($page - 1) * $per_page;
        
        // Get attachment IDs for this page
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $attachment_ids = $wpdb->get_col(
            "SELECT DISTINCT attachment_id
             FROM {$table}
             WHERE {$where}
             ORDER BY attachment_id DESC
             LIMIT {$per_page} OFFSET {$offset}"
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        
        // Get all issues for these attachments with full details
        $items_data = [];
        if (!empty($attachment_ids)) {
            $ids_placeholder = implode(',', array_map('intval', $attachment_ids));
            // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $all_issues = $wpdb->get_results($wpdb->prepare(
                "SELECT attachment_id, action_type, details
                 FROM {$table}
                 WHERE scan_id = %s AND attachment_id IN ({$ids_placeholder})
                 ORDER BY attachment_id DESC, action_type",
                $scan_id
            ));
            // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            
            // Group issues by attachment, then by type
            foreach ($all_issues as $issue) {
                $aid = (int) $issue->attachment_id;
                if (!isset($items_data[$aid])) {
                    $items_data[$aid] = [];
                }
                
                $type = $issue->action_type;
                if (!isset($items_data[$aid][$type])) {
                    $items_data[$aid][$type] = [];
                }
                
                $raw_details = json_decode($issue->details, true) ?: [];
                
                // Handle new aggregated format (items array) or old single-item format
                $items_list = $raw_details['items'] ?? [$raw_details];
                
                foreach ($items_list as $item_details) {
                    $items_data[$aid][$type][] = $item_details;
                }
            }
        }
        
        ?>
        <table class="sqmedia-details-table">
            <thead>
                <tr>
                    <th style="width: 30px;"></th>
                    <th style="width: 70px;"><?php esc_html_e('ID', 'staticq-media'); ?></th>
                    <th style="width: 180px;"><?php esc_html_e('Filename', 'staticq-media'); ?></th>
                    <th><?php esc_html_e('Issues', 'staticq-media'); ?></th>
                    <th style="width: 70px;"><?php esc_html_e('Actions', 'staticq-media'); ?></th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($attachment_ids as $attachment_id):
                    $attachment_id = (int) $attachment_id;
                    $file_path = get_post_meta($attachment_id, '_wp_attached_file', true);
                    // Unified (0) and Phase 2: show full relative path; Phase 1: just filename
                    $filename = $file_path ? ($phase !== 1 ? $file_path : basename($file_path)) : __('Unknown', 'staticq-media');
                    $issues_by_type = $items_data[$attachment_id] ?? [];

                    // Check if this attachment ONLY has critical (unfixable) issues
                    $has_fixable_issues = false;
                    foreach (array_keys($issues_by_type) as $issue_type) {
                        if ($issue_type !== SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING) {
                            $has_fixable_issues = true;
                            break;
                        }
                    }
                ?>
                    <tr data-attachment-id="<?php echo esc_attr($attachment_id); ?>">
                        <td>
                            <button type="button" class="sqmedia-expand-row"
                                    data-attachment-id="<?php echo esc_attr($attachment_id); ?>"
                                    data-scan-id="<?php echo esc_attr($scan_id); ?>">▶</button>
                        </td>
                        <td class="sqmedia-attachment-id">
                            <a href="<?php echo esc_url(get_edit_post_link($attachment_id)); ?>" target="_blank">
                                #<?php echo esc_html($attachment_id); ?>
                            </a>
                        </td>
                        <td class="sqmedia-filename"><?php echo esc_html($filename); ?></td>
                        <td class="sqmedia-issues-cell">
                            <div class="sqmedia-issues-compact">
                                <?php foreach ($issues_by_type as $type => $issues_list):
                                    $css_class = $this->get_issue_tag_class($type);
                                    $count = count($issues_list);
                                ?>
                                    <span class="sqmedia-issue-badge sqmedia-issue-<?php echo esc_attr($css_class); ?>">
                                        <?php echo esc_html($this->get_issue_type_label($type)); ?>
                                        <?php if ($count > 1): ?>
                                            <span class="sqmedia-badge-count"><?php echo esc_html($count); ?></span>
                                        <?php endif; ?>
                                    </span>
                                <?php endforeach; ?>
                            </div>
                        </td>
                        <td>
                            <?php if ($has_fixable_issues): ?>
                            <button type="button" class="button button-secondary sqmedia-fix-single-btn"
                                    data-attachment-id="<?php echo esc_attr($attachment_id); ?>"
                                    style="padding: 4px 10px; font-size: 12px;">
                                <?php esc_html_e('Fix', 'staticq-media'); ?>
                            </button>
                            <?php else: ?>
                            <span class="sqmedia-unfixable" style="color: #999; font-size: 11px;" title="<?php esc_attr_e('Master file missing - cannot be fixed automatically', 'staticq-media'); ?>">
                                <?php esc_html_e('N/A', 'staticq-media'); ?>
                            </span>
                            <?php endif; ?>
                        </td>
                    </tr>
                <?php endforeach; ?>
                
                <?php if (empty($attachment_ids)): ?>
                    <tr>
                        <td colspan="5" style="text-align: center; color: #646970;">
                            <?php esc_html_e('No issues found.', 'staticq-media'); ?>
                        </td>
                    </tr>
                <?php endif; ?>
            </tbody>
        </table>
        
        <?php if ($total > $per_page):
            $total_pages = ceil($total / $per_page);
        ?>
            <div class="sqmedia-pagination" data-total-pages="<?php echo esc_attr($total_pages); ?>" data-current-page="<?php echo esc_attr($page); ?>">
                <div class="sqmedia-pagination-info">
                    <?php
                    echo esc_html(sprintf(
                        // translators: %1$d: first item number, %2$d: last item number, %3$d: total attachments
                        __('Showing %1$d-%2$d of %3$d attachments', 'staticq-media'),
                        $offset + 1,
                        min($offset + $per_page, $total),
                        $total
                    ));
                    ?>
                </div>
                <div class="sqmedia-pagination-buttons">
                    <?php $this->render_pagination_buttons($page, $total_pages); ?>
                </div>
            </div>
        <?php endif; ?>
        <?php
    }

    /**
     * Render smart pagination buttons
     * Shows: Prev, first page, ellipsis, pages around current, ellipsis, last page, Next
     *
     * @param int $current Current page number
     * @param int $total_pages Total number of pages
     */
    public function render_pagination_buttons(int $current, int $total_pages): void {
        if ($total_pages <= 1) {
            return;
        }

        $prev_disabled = $current <= 1;
        $prev_page = max(1, $current - 1);
        $prev_classes = 'sqmedia-pagination-btn sqmedia-pagination-prev' . ($prev_disabled ? ' disabled' : '');
        ?>
        <button type="button" class="<?php echo esc_attr($prev_classes); ?>" data-page="<?php echo esc_attr($prev_page); ?>" <?php disabled($prev_disabled); ?>>&#8249;</button>
        <?php

        // Determine which page numbers to show
        $pages_to_show = [];

        if ($total_pages <= 7) {
            // Show all pages if 7 or fewer
            for ($i = 1; $i <= $total_pages; $i++) {
                $pages_to_show[] = $i;
            }
        } else {
            // Always show first page
            $pages_to_show[] = 1;

            // Calculate range around current page
            $range_start = max(2, $current - 1);
            $range_end = min($total_pages - 1, $current + 1);

            // Adjust range to always show 3 middle pages when possible
            if ($range_start <= 3) {
                $range_start = 2;
                $range_end = min(4, $total_pages - 1);
            }
            if ($range_end >= $total_pages - 2) {
                $range_end = $total_pages - 1;
                $range_start = max(2, $total_pages - 3);
            }

            // Add ellipsis before range if needed
            if ($range_start > 2) {
                $pages_to_show[] = '...';
            }

            // Add range pages
            for ($i = $range_start; $i <= $range_end; $i++) {
                $pages_to_show[] = $i;
            }

            // Add ellipsis after range if needed
            if ($range_end < $total_pages - 1) {
                $pages_to_show[] = '...';
            }

            // Always show last page
            $pages_to_show[] = $total_pages;
        }

        foreach ($pages_to_show as $page_item) {
            if ($page_item === '...') {
                ?>
                <span class="sqmedia-pagination-ellipsis">…</span>
                <?php
            } else {
                $page_classes = 'sqmedia-pagination-btn' . ($page_item === $current ? ' active' : '');
                ?>
                <button type="button" class="<?php echo esc_attr($page_classes); ?>" data-page="<?php echo esc_attr($page_item); ?>"><?php echo esc_html($page_item); ?></button>
                <?php
            }
        }

        $next_disabled = $current >= $total_pages;
        $next_page = min($total_pages, $current + 1);
        $next_classes = 'sqmedia-pagination-btn sqmedia-pagination-next' . ($next_disabled ? ' disabled' : '');
        ?>
        <button type="button" class="<?php echo esc_attr($next_classes); ?>" data-page="<?php echo esc_attr($next_page); ?>" <?php disabled($next_disabled); ?>>&#8250;</button>
        <?php
    }

    /**
     * Get issue type label
     */
    public function get_issue_type_label(string $type): string {
        // Use constants from SQMedia_File_State_Scanner for consistency
        $labels = [
            SQMedia_File_State_Scanner::CAT_CORRUPTED => __('Corrupted', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_WRONG_DIMENSIONS => __('Wrong Dimensions', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_MISSING_SIZES => __('Missing Sizes', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_DEPRECATED_SIZES => __('Deprecated', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_MISSING_WEBP => __('Missing WebP', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_UPLOAD_TO_BUCKET => __('Upload to Bucket', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_DOWNLOAD_TO_LOCAL => __('Download to Local', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_GENERATE_SIZES => __('Generate', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING => __('Critical', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_ORIGINAL_MISPLACED => __('Original: stale copy in old folder', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_ORIGINAL_POLICY => __('Original & Scaling', 'staticq-media'),
            SQMedia_File_State_Scanner::CAT_POST_CONTENT_REF => __('Post content reference', 'staticq-media'),
        ];

        return $labels[$type] ?? $type;
    }

    /**
     * Get issue tag CSS class (severity-based)
     */
    public function get_issue_tag_class(string $type): string {
        $classes = [
            SQMedia_File_State_Scanner::CAT_CORRUPTED => 'error',
            SQMedia_File_State_Scanner::CAT_WRONG_DIMENSIONS => 'warning',
            SQMedia_File_State_Scanner::CAT_MISSING_SIZES => 'warning',
            SQMedia_File_State_Scanner::CAT_DEPRECATED_SIZES => 'info',
            SQMedia_File_State_Scanner::CAT_MISSING_WEBP => 'info',
            SQMedia_File_State_Scanner::CAT_UPLOAD_TO_BUCKET => 'info',
            SQMedia_File_State_Scanner::CAT_DOWNLOAD_TO_LOCAL => 'info',
            SQMedia_File_State_Scanner::CAT_GENERATE_SIZES => 'warning',
            SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING => 'error',
            SQMedia_File_State_Scanner::CAT_ORIGINAL_MISPLACED => 'warning',
            SQMedia_File_State_Scanner::CAT_ORIGINAL_POLICY => 'warning',
            SQMedia_File_State_Scanner::CAT_POST_CONTENT_REF => 'warning',
        ];

        return $classes[$type] ?? 'default';
    }
    
    /**
     * Format issue display HTML based on type
     */
    public function format_issue_display(string $type, array $details): string {
        $size = $details['size'] ?? $details['file'] ?? null;
        
        switch ($type) {
            case SQMedia_File_State_Scanner::CAT_WRONG_DIMENSIONS:
                $current = ($details['current_width'] ?? '?') . '×' . ($details['current_height'] ?? '?');
                $expected = ($details['expected_width'] ?? '?') . '×' . ($details['expected_height'] ?? '?');
                return sprintf(
                    '<span class="sqmedia-issue-size-name">%s:</span> ' .
                    '<span class="sqmedia-issue-dimensions">%s</span>' .
                    '<span class="sqmedia-issue-arrow">→</span>' .
                    '<span class="sqmedia-issue-dimensions sqmedia-issue-expected">%s</span>',
                    esc_html($size ?: 'unknown'),
                    esc_html($current),
                    esc_html($expected)
                );
                
            case SQMedia_File_State_Scanner::CAT_MISSING_SIZES:
                $dimensions = '';
                if (!empty($details['expected_width']) && !empty($details['expected_height'])) {
                    $dimensions = ' (' . $details['expected_width'] . '×' . $details['expected_height'] . ')';
                }
                return sprintf(
                    '<span class="sqmedia-issue-size-name">%s</span><span class="sqmedia-issue-display">%s</span>',
                    esc_html($size ?: 'unknown'),
                    esc_html($dimensions)
                );
                
            case SQMedia_File_State_Scanner::CAT_DEPRECATED_SIZES:
                return sprintf(
                    '<span class="sqmedia-issue-size-name">%s</span>',
                    esc_html($size ?: 'unknown')
                );
                
            case SQMedia_File_State_Scanner::CAT_MISSING_WEBP:
                $missing = $details['missing'] ?? 1;
                return sprintf(
                    '<span class="sqmedia-issue-display">%s</span>',
                    // translators: %d is the number of WebP files to create
                    esc_html(sprintf(__('%d WebP file(s) to create', 'staticq-media'), $missing))
                );
                
            case SQMedia_File_State_Scanner::CAT_CORRUPTED:
                $reason = $details['reason'] ?? 'invalid_metadata';
                $reason_labels = [
                    'no_metadata' => __('No metadata found', 'staticq-media'),
                    'invalid_dimensions' => __('Invalid dimensions (width/height missing or zero)', 'staticq-media'),
                ];
                return sprintf(
                    '<span class="sqmedia-issue-display">%s</span>',
                    esc_html($reason_labels[$reason] ?? $reason)
                );
                
            case SQMedia_File_State_Scanner::CAT_UPLOAD_TO_BUCKET:
            case SQMedia_File_State_Scanner::CAT_DOWNLOAD_TO_LOCAL:
            case SQMedia_File_State_Scanner::CAT_GENERATE_SIZES:
                return sprintf(
                    '<span class="sqmedia-issue-size-name">%s</span>',
                    esc_html($size ?: $details['file'] ?? 'master')
                );
                
            case SQMedia_File_State_Scanner::CAT_CRITICAL_MISSING:
                return sprintf(
                    '<span class="sqmedia-issue-display" style="color: #721c24;">%s</span>',
                    esc_html(__('Master file missing - cannot recover', 'staticq-media'))
                );
                
            default:
                return sprintf(
                    '<span class="sqmedia-issue-display">%s</span>',
                    esc_html($size ?: $type)
                );
        }
    }
    
    /**
     * Render tip box
     */
    public function render_tip_box(array $phase1, array $phase2): void {
        // Show tip only when both phases are clean
        if ($phase1['status'] !== SQMedia_File_State_Renderer::STATUS_CLEAN || $phase2['status'] !== SQMedia_File_State_Renderer::STATUS_CLEAN) {
            return;
        }
        
        ?>
        <div class="sqmedia-alert sqmedia-alert-info" style="margin-top: 20px;">
            <span class="sqmedia-alert-icon">💡</span>
            <div>
                <strong><?php esc_html_e('Tip:', 'staticq-media'); ?></strong>
                <?php esc_html_e('Run the Orphan Scanner to find and clean up unused files from previous configurations.', 'staticq-media'); ?>
            </div>
        </div>
        <?php
    }
}