<?php
/**
 * WP-CLI Commands for Wally Monitor (Equallyze)
 *
 * Enhanced CLI with 25+ server-side accessibility checks,
 * scheduled scans management, and CI/CD integration.
 *
 * @package WallyMonitor
 * @since   1.1.0
 */

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

if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
    return;
}

class Wally_CLI_Command {

    /** @var array Registered accessibility rules */
    private $rules = array();

    public function __construct() {
        $this->register_rules();
    }

    // ─── Subcommand: scan ───────────────────────────────────────────────

    /**
     * Executa análise de acessibilidade no site.
     *
     * ## OPTIONS
     *
     * [--url=<url>]
     * : URL específica para analisar. Se omitida, analisa todo o site.
     *
     * [--format=<format>]
     * : Formato de saída.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     *   - yaml
     * ---
     *
     * [--threshold=<score>]
     * : Score mínimo aceitável (0-100).
     * ---
     * default: 70
     * ---
     *
     * [--exit-code]
     * : Retorna exit code 1 se score < threshold (para CI/CD).
     *
     * [--max-urls=<number>]
     * : Limite de URLs para análise completa.
     *
     * [--save]
     * : Salva resultado no banco de dados.
     *
     * ## EXAMPLES
     *
     *     wp wally scan --format=json
     *     wp wally scan --url=https://example.com --save
     *     wp wally scan --threshold=80 --exit-code
     *
     * @when after_wp_load
     */
    public function scan( $args, $assoc_args ) {
        $url       = isset( $assoc_args['url'] ) ? esc_url_raw( $assoc_args['url'] ) : null;
        $format    = isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'table';
        $threshold = isset( $assoc_args['threshold'] ) ? absint( $assoc_args['threshold'] ) : 70;
        $exit_code = isset( $assoc_args['exit-code'] );
        $max_urls  = isset( $assoc_args['max-urls'] ) ? absint( $assoc_args['max-urls'] ) : null;
        $save      = isset( $assoc_args['save'] );

        WP_CLI::line( '' );
        WP_CLI::line( '🔍 Equallyze — Análise de Acessibilidade' );
        WP_CLI::line( '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' );

        if ( $url ) {
            // SSRF protection — validate URL before fetching
            $url_check = wally_monitor_validate_url( $url );
            if ( is_wp_error( $url_check ) ) {
                WP_CLI::error( $url_check->get_error_message() );
            }

            WP_CLI::line( "Alvo: {$url}" );
            $result = $this->analyze_single_url( $url );
        } else {
            $result = $this->analyze_full_site( $max_urls );
        }

        if ( is_wp_error( $result ) ) {
            WP_CLI::error( $result->get_error_message() );
        }

        if ( $save ) {
            $this->save_result( $result );
            WP_CLI::success( 'Resultado salvo no banco de dados.' );
        }

        $this->display_result( $result, $format );

        if ( $exit_code ) {
            $score = $result['summary']['score'];
            WP_CLI::line( '' );
            if ( $score < $threshold ) {
                WP_CLI::error( sprintf( '❌ Score %d/100 abaixo do threshold %d. Deploy bloqueado.', $score, $threshold ) );
            } else {
                WP_CLI::success( sprintf( '✅ Score %d/100 aprovado (threshold: %d).', $score, $threshold ) );
            }
        }
    }

    // ─── Subcommand: status ─────────────────────────────────────────────

    /**
     * Exibe status do plugin.
     *
     * ## EXAMPLES
     *
     *     wp wally status
     *
     * @when after_wp_load
     * @subcommand status
     */
    public function status( $args, $assoc_args ) {
        $db      = Wally_Database::get_instance();
        $version = defined( 'WALLY_MONITOR_VERSION' ) ? WALLY_MONITOR_VERSION : 'N/A';
        $db_ver  = get_option( 'wally_monitor_db_version', 'N/A' );
        $history = get_option( 'wally_monitor_history', array() );
        $cron    = wp_next_scheduled( 'wally_monitor_cron_scan' );
        $settings = get_option( 'wally_monitor_settings', array() );

        WP_CLI::line( '' );
        WP_CLI::line( '🟢 Equallyze — Status do Plugin' );
        WP_CLI::line( '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' );
        WP_CLI::line( sprintf( '  Versão do Plugin:   %s', $version ) );
        WP_CLI::line( sprintf( '  Versão do Banco:    %s', $db_ver ) );
        WP_CLI::line( sprintf( '  Total de análises:  %d', count( $history ) ) );

        if ( ! empty( $history ) ) {
            $last = $history[0];
            WP_CLI::line( sprintf( '  Última análise:     %s (Score: %d/100)', $last['timestamp'], $last['score'] ) );
        }

        WP_CLI::line( sprintf( '  Cron agendado:      %s', $cron ? date_i18n( 'Y-m-d H:i:s', $cron ) : 'Não' ) );
        WP_CLI::line( sprintf( '  Nível WCAG:         %s', isset( $settings['wcag_level'] ) ? $settings['wcag_level'] : 'AA' ) );
        WP_CLI::line( sprintf( '  Regras CLI:         %d verificações server-side', count( $this->rules ) ) );
        WP_CLI::line( '' );
    }

    // ─── Subcommand: report ─────────────────────────────────────────────

    /**
     * Exibe último relatório salvo.
     *
     * ## OPTIONS
     *
     * [--url=<url>]
     * : URL específica. Se omitida, mostra último relatório geral.
     *
     * [--format=<format>]
     * : Formato de saída.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     * ---
     *
     * ## EXAMPLES
     *
     *     wp wally report
     *     wp wally report --url=https://example.com --format=json
     *
     * @when after_wp_load
     * @subcommand report
     */
    public function report( $args, $assoc_args ) {
        $url    = isset( $assoc_args['url'] ) ? esc_url_raw( $assoc_args['url'] ) : null;
        $format = isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'table';

        $db = Wally_Database::get_instance();

        if ( $url ) {
            $data = $db->get_latest_report( $url );
        } else {
            $data = $this->get_latest_any_report();
        }

        if ( ! $data ) {
            WP_CLI::warning( 'Nenhum relatório encontrado. Execute: wp wally scan --save' );
            return;
        }

        $this->display_result( $data, $format );
    }

    // ─── Subcommand: history ────────────────────────────────────────────

    /**
     * Exibe histórico de análises.
     *
     * ## OPTIONS
     *
     * [--limit=<number>]
     * : Número de registros.
     * ---
     * default: 10
     * ---
     *
     * [--format=<format>]
     * : Formato de saída.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     * ---
     *
     * ## EXAMPLES
     *
     *     wp wally history
     *     wp wally history --limit=5 --format=json
     *
     * @when after_wp_load
     * @subcommand history
     */
    public function history( $args, $assoc_args ) {
        $limit  = isset( $assoc_args['limit'] ) ? absint( $assoc_args['limit'] ) : 10;
        $format = isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'table';

        $history = get_option( 'wally_monitor_history', array() );
        $history = array_slice( $history, 0, $limit );

        if ( empty( $history ) ) {
            WP_CLI::warning( 'Nenhuma análise no histórico.' );
            return;
        }

        $rows = array();
        foreach ( $history as $item ) {
            $user     = isset( $item['user_id'] ) ? get_userdata( $item['user_id'] ) : null;
            $rows[] = array(
                'data'      => isset( $item['timestamp'] ) ? $item['timestamp'] : '—',
                'usuario'   => $user ? $user->display_name : 'Sistema',
                'score'     => isset( $item['score'] ) ? $item['score'] . '/100' : '—',
                'problemas' => isset( $item['total_problems'] ) ? $item['total_problems'] : '—',
                'url'       => isset( $item['url'] ) ? $item['url'] : '—',
                'fonte'     => isset( $item['source'] ) ? $item['source'] : 'Web',
            );
        }

        \WP_CLI\Utils\format_items( $format, $rows, array( 'data', 'usuario', 'score', 'problemas', 'url', 'fonte' ) );
    }

    // ─── Subcommand: schedule ───────────────────────────────────────────

    /**
     * Gerencia agendamento de análises automáticas via WP-Cron.
     *
     * ## OPTIONS
     *
     * [--enable]
     * : Ativa o agendamento.
     *
     * [--disable]
     * : Desativa o agendamento.
     *
     * [--frequency=<frequency>]
     * : Frequência do cron.
     * ---
     * default: weekly
     * options:
     *   - hourly
     *   - daily
     *   - weekly
     *   - monthly
     * ---
     *
     * ## EXAMPLES
     *
     *     wp wally schedule --enable --frequency=daily
     *     wp wally schedule --disable
     *     wp wally schedule
     *
     * @when after_wp_load
     * @subcommand schedule
     */
    public function schedule( $args, $assoc_args ) {
        $enable    = isset( $assoc_args['enable'] );
        $disable   = isset( $assoc_args['disable'] );
        $frequency = isset( $assoc_args['frequency'] ) ? $assoc_args['frequency'] : 'weekly';

        $hook = 'wally_monitor_cron_scan';

        if ( $disable ) {
            wp_clear_scheduled_hook( $hook );
            delete_option( 'wally_monitor_cron_frequency' );
            WP_CLI::success( '⏹️  Agendamento desativado.' );
            return;
        }

        if ( $enable ) {
            wp_clear_scheduled_hook( $hook );

            $recurrence = $this->map_frequency( $frequency );
            wp_schedule_event( time(), $recurrence, $hook );
            update_option( 'wally_monitor_cron_frequency', $frequency );

            WP_CLI::success( sprintf( '⏱️  Agendamento ativado: %s', $frequency ) );
            return;
        }

        // Status
        $next = wp_next_scheduled( $hook );
        $freq = get_option( 'wally_monitor_cron_frequency', 'não configurado' );

        WP_CLI::line( '' );
        WP_CLI::line( '⏱️  Equallyze — Agendamento Cron' );
        WP_CLI::line( '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' );
        WP_CLI::line( sprintf( '  Status:       %s', $next ? '✅ Ativo' : '⛔ Inativo' ) );
        WP_CLI::line( sprintf( '  Frequência:   %s', $freq ) );
        if ( $next ) {
            WP_CLI::line( sprintf( '  Próxima exec: %s', date_i18n( 'Y-m-d H:i:s', $next ) ) );
        }
        WP_CLI::line( '' );
    }

    // ─── Subcommand: reset ──────────────────────────────────────────────

    /**
     * Remove todos os dados de análise.
     *
     * ## OPTIONS
     *
     * [--confirm]
     * : Confirma a exclusão sem prompt interativo.
     *
     * ## EXAMPLES
     *
     *     wp wally reset --confirm
     *
     * @when after_wp_load
     * @subcommand reset
     */
    public function reset( $args, $assoc_args ) {
        if ( ! isset( $assoc_args['confirm'] ) ) {
            WP_CLI::confirm( '⚠️  Isso apagará TODO o histórico e relatórios. Continuar?' );
        }

        global $wpdb;
        $db    = Wally_Database::get_instance();
        $table = $db->get_table_name();

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query( "TRUNCATE TABLE " . esc_sql( $table ) );
        delete_option( 'wally_monitor_history' );
        wp_clear_scheduled_hook( 'wally_monitor_cron_scan' );

        WP_CLI::success( '🗑️  Todos os dados de análise foram removidos.' );
    }

    // ─── Rule Engine ────────────────────────────────────────────────────

    /**
     * Register all server-side accessibility rules.
     */
    private function register_rules() {
        $this->rules = array(
            array(
                'id'          => 'image-alt',
                'impact'      => 'critical',
                'description' => 'Imagens sem atributo alt',
                'nbr'         => '5.3.1',
                'pattern'     => '/<img(?![^>]*\balt\s*=)[^>]*>/i',
            ),
            array(
                'id'          => 'image-empty-alt',
                'impact'      => 'serious',
                'description' => 'Imagens com alt vazio sem role decorativo',
                'nbr'         => '5.3.1',
                'pattern'     => '/<img[^>]*\balt\s*=\s*["\']\s*["\'][^>]*>/i',
                'exclude'     => '/role\s*=\s*["\']presentation["\']/i',
            ),
            array(
                'id'          => 'empty-link',
                'impact'      => 'serious',
                'description' => 'Links sem texto descritivo',
                'nbr'         => '5.6.4',
                'pattern'     => '/<a[^>]*>[\s]*<\/a>/i',
            ),
            array(
                'id'          => 'generic-link-text',
                'impact'      => 'moderate',
                'description' => 'Links com texto genérico (clique aqui, saiba mais)',
                'nbr'         => '5.6.4',
                'pattern'     => '/<a[^>]*>\s*(clique\s+aqui|click\s+here|saiba\s+mais|read\s+more|leia\s+mais|more|here|aqui|link)\s*<\/a>/i',
            ),
            array(
                'id'          => 'html-lang',
                'impact'      => 'critical',
                'description' => 'Elemento <html> sem atributo lang',
                'nbr'         => '5.8.1',
                'pattern'     => '/<html(?![^>]*\blang\s*=)[^>]*>/i',
            ),
            array(
                'id'          => 'empty-heading',
                'impact'      => 'serious',
                'description' => 'Títulos (h1-h6) vazios',
                'nbr'         => '5.6.10',
                'pattern'     => '/<h([1-6])[^>]*>\s*<\/h\1>/i',
            ),
            array(
                'id'          => 'missing-title',
                'impact'      => 'serious',
                'description' => 'Página sem elemento <title>',
                'nbr'         => '5.6.2',
                'pattern'     => null,
                'validator'   => 'validate_page_title',
            ),
            array(
                'id'          => 'generic-title',
                'impact'      => 'moderate',
                'description' => 'Título da página genérico (Home, Page, Untitled)',
                'nbr'         => '5.6.2',
                'pattern'     => '/<title>\s*(Home|Page|Untitled|WordPress|Site|Início|Página)\s*<\/title>/i',
            ),
            array(
                'id'          => 'meta-refresh',
                'impact'      => 'critical',
                'description' => 'Meta refresh redireciona sem consentimento',
                'nbr'         => '5.5.4',
                'pattern'     => '/<meta[^>]*http-equiv\s*=\s*["\']refresh["\'][^>]*>/i',
            ),
            array(
                'id'          => 'audio-autoplay',
                'impact'      => 'serious',
                'description' => 'Mídia com autoplay sem muted',
                'nbr'         => '5.4.2',
                'pattern'     => '/<(?:audio|video)[^>]*\bautoplay\b(?![^>]*\bmuted\b)[^>]*>/i',
            ),
            array(
                'id'          => 'positive-tabindex',
                'impact'      => 'serious',
                'description' => 'Elementos com tabindex positivo (quebra ordem de foco)',
                'nbr'         => '5.6.3',
                'pattern'     => '/tabindex\s*=\s*["\'][1-9]\d*["\']/i',
            ),
            array(
                'id'          => 'iframe-title',
                'impact'      => 'serious',
                'description' => 'Iframes sem título descritivo',
                'nbr'         => '5.10.2',
                'pattern'     => '/<iframe(?![^>]*\btitle\s*=)[^>]*>/i',
            ),
            array(
                'id'          => 'empty-button',
                'impact'      => 'critical',
                'description' => 'Botões sem texto ou label',
                'nbr'         => '5.10.2',
                'pattern'     => '/<button[^>]*>\s*<\/button>/i',
            ),
            array(
                'id'          => 'marquee',
                'impact'      => 'serious',
                'description' => 'Uso de <marquee> (elemento obsoleto e inacessível)',
                'nbr'         => '5.5.5',
                'pattern'     => '/<marquee\b/i',
            ),
            array(
                'id'          => 'blink',
                'impact'      => 'critical',
                'description' => 'Uso de <blink> (causa convulsões)',
                'nbr'         => '5.5.5',
                'pattern'     => '/<blink\b/i',
            ),
            array(
                'id'          => 'inline-outline-none',
                'impact'      => 'moderate',
                'description' => 'Remoção inline de outline de foco',
                'nbr'         => '5.6.7',
                'pattern'     => '/style\s*=\s*["\'][^"\']*outline\s*:\s*(?:none|0)\b[^"\']*["\']/i',
            ),
            array(
                'id'          => 'table-no-headers',
                'impact'      => 'serious',
                'description' => 'Tabelas de dados sem cabeçalhos <th>',
                'nbr'         => '5.3.1',
                'pattern'     => null,
                'validator'   => 'validate_table_headers',
            ),
            array(
                'id'          => 'skip-nav',
                'impact'      => 'moderate',
                'description' => 'Ausência de link de pular navegação',
                'nbr'         => '5.6.1',
                'pattern'     => null,
                'validator'   => 'validate_skip_nav',
            ),
            array(
                'id'          => 'heading-hierarchy',
                'impact'      => 'moderate',
                'description' => 'Hierarquia de títulos com níveis pulados',
                'nbr'         => '5.6.10',
                'pattern'     => null,
                'validator'   => 'validate_heading_hierarchy',
            ),
            array(
                'id'          => 'input-no-label',
                'impact'      => 'critical',
                'description' => 'Campos de formulário sem label associado',
                'nbr'         => '5.9.2',
                'pattern'     => null,
                'validator'   => 'validate_form_labels',
            ),
            array(
                'id'          => 'captcha-detected',
                'impact'      => 'moderate',
                'description' => 'CAPTCHA detectado (possível barreira de acessibilidade)',
                'nbr'         => '5.9.8',
                'pattern'     => '/(?:g-recaptcha|h-captcha|class\s*=\s*["\'][^"\']*captcha|src\s*=\s*["\'][^"\']*captcha)/i',
            ),
            array(
                'id'          => 'font-size-px',
                'impact'      => 'minor',
                'description' => 'Font-size em px (dificulta redimensionamento)',
                'nbr'         => '5.4.4',
                'pattern'     => '/font-size\s*:\s*\d+px/i',
                'max_count'   => 20,
            ),
            array(
                'id'          => 'duplicate-id',
                'impact'      => 'serious',
                'description' => 'IDs duplicados na página',
                'nbr'         => '5.10.1',
                'pattern'     => null,
                'validator'   => 'validate_duplicate_ids',
            ),
            array(
                'id'          => 'input-autocomplete',
                'impact'      => 'minor',
                'description' => 'Campos comuns sem autocomplete',
                'nbr'         => '5.3.5',
                'pattern'     => null,
                'validator'   => 'validate_autocomplete',
            ),
            array(
                'id'          => 'multiple-h1',
                'impact'      => 'moderate',
                'description' => 'Múltiplos H1 na página',
                'nbr'         => '5.6.10',
                'pattern'     => null,
                'validator'   => 'validate_multiple_h1',
            ),
            array(
                'id'          => 'empty-table-header',
                'impact'      => 'serious',
                'description' => 'Cabeçalhos de tabela (<th>) vazios',
                'nbr'         => '5.3.1',
                'pattern'     => '/<th[^>]*>\s*<\/th>/i',
            ),
        );
    }

    // ─── Analysis Engine ────────────────────────────────────────────────

    /**
     * Analyze a single URL with server-side regex rules.
     *
     * @param string $url URL to analyze.
     * @return array|WP_Error Results array or error.
     */
    private function analyze_single_url( $url ) {
        $response = wp_remote_get( $url, array(
            'timeout'   => 30,
            'sslverify' => apply_filters( 'wally_monitor_sslverify', true ),
        ) );

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

        $code = wp_remote_retrieve_response_code( $response );
        if ( $code >= 400 ) {
            return new WP_Error( 'http_error', sprintf( 'HTTP %d ao acessar %s', $code, $url ) );
        }

        $html = wp_remote_retrieve_body( $response );
        if ( empty( $html ) ) {
            return new WP_Error( 'empty_body', 'Resposta vazia de ' . $url );
        }

        $violations = array();
        $penalty    = 0;

        // Use DOMDocument-based checker for validator rules
        $checker = new Wally_HTML_Checker( $html );

        foreach ( $this->rules as $rule ) {
            $count = 0;

            if ( ! empty( $rule['validator'] ) && method_exists( $checker, $rule['validator'] ) ) {
                $count = $checker->{$rule['validator']}();
            } elseif ( ! empty( $rule['pattern'] ) ) {
                preg_match_all( $rule['pattern'], $html, $matches );
                $count = count( $matches[0] );

                // Exclude filter
                if ( $count > 0 && ! empty( $rule['exclude'] ) ) {
                    $filtered = 0;
                    foreach ( $matches[0] as $match ) {
                        if ( ! preg_match( $rule['exclude'], $match ) ) {
                            $filtered++;
                        }
                    }
                    $count = $filtered;
                }

                // Max count cap
                if ( isset( $rule['max_count'] ) ) {
                    $count = min( $count, $rule['max_count'] );
                }
            }

            if ( $count > 0 ) {
                $violations[] = array(
                    'id'          => $rule['id'],
                    'impact'      => $rule['impact'],
                    'description' => $rule['description'],
                    'nbr'         => isset( $rule['nbr'] ) ? 'NBR 17.225 §' . $rule['nbr'] : '',
                    'count'       => $count,
                );

                $penalty += $this->calculate_penalty( $rule['impact'], $count );
            }
        }

        $score = max( 0, min( 100, 100 - $penalty ) );

        return array(
            'url'        => $url,
            'scan_date'  => current_time( 'mysql' ),
            'summary'    => array(
                'score'          => $score,
                'totalProblems'  => array_sum( array_column( $violations, 'count' ) ),
                'rulesViolated'  => count( $violations ),
                'rulesChecked'   => count( $this->rules ),
                'method'         => 'server-side (regex)',
            ),
            'violations' => $violations,
        );
    }

    /**
     * Analyze full site with progress bar.
     *
     * @param int|null $max_urls URL limit.
     * @return array|WP_Error Combined results.
     */
    private function analyze_full_site( $max_urls = null ) {
        $settings = get_option( 'wally_monitor_settings', array(
            'post_types'   => array( 'post', 'page' ),
            'include_home' => true,
            'max_urls'     => 100,
        ) );

        $limit = $max_urls ? $max_urls : ( isset( $settings['max_urls'] ) ? $settings['max_urls'] : 100 );
        $urls  = $this->get_site_urls( $settings, $limit );

        WP_CLI::line( sprintf( '📄 %d URLs encontradas.', count( $urls ) ) );
        WP_CLI::line( '' );

        $progress       = \WP_CLI\Utils\make_progress_bar( 'Analisando', count( $urls ) );
        $all_violations = array();
        $total_score    = 0;
        $url_count      = 0;
        $errors         = 0;

        foreach ( $urls as $url ) {
            $result = $this->analyze_single_url( $url );

            if ( ! is_wp_error( $result ) ) {
                foreach ( $result['violations'] as $v ) {
                    $vid = $v['id'];
                    if ( isset( $all_violations[ $vid ] ) ) {
                        $all_violations[ $vid ]['count'] += $v['count'];
                        $all_violations[ $vid ]['pages']++;
                    } else {
                        $all_violations[ $vid ] = $v;
                        $all_violations[ $vid ]['pages'] = 1;
                    }
                }
                $total_score += $result['summary']['score'];
                $url_count++;
            } else {
                $errors++;
            }

            $progress->tick();
        }

        $progress->finish();

        $avg_score = $url_count > 0 ? round( $total_score / $url_count ) : 0;

        // Sort by impact weight
        $impact_order = array( 'critical' => 0, 'serious' => 1, 'moderate' => 2, 'minor' => 3 );
        usort( $all_violations, function( $a, $b ) use ( $impact_order ) {
            $ia = isset( $impact_order[ $a['impact'] ] ) ? $impact_order[ $a['impact'] ] : 9;
            $ib = isset( $impact_order[ $b['impact'] ] ) ? $impact_order[ $b['impact'] ] : 9;
            return $ia - $ib;
        } );

        return array(
            'urls_analyzed' => $url_count,
            'errors'        => $errors,
            'scan_date'     => current_time( 'mysql' ),
            'summary'       => array(
                'score'          => $avg_score,
                'totalProblems'  => array_sum( array_column( $all_violations, 'count' ) ),
                'rulesViolated'  => count( $all_violations ),
                'rulesChecked'   => count( $this->rules ),
                'method'         => 'server-side (regex)',
            ),
            'violations' => array_values( $all_violations ),
        );
    }

    // ─── Validators now delegated to Wally_HTML_Checker ────────────────
    // validate_page_title, validate_table_headers, validate_skip_nav,
    // validate_heading_hierarchy, validate_form_labels, validate_duplicate_ids,
    // validate_autocomplete, validate_multiple_h1
    // See: includes/class-wally-html-checker.php

    // ─── Scoring ────────────────────────────────────────────────────────

    private function calculate_penalty( $impact, $count ) {
        return Wally_HTML_Checker::calculate_penalty( $impact, $count );
    }

    // ─── Output ─────────────────────────────────────────────────────────

    private function display_result( $result, $format ) {
        if ( 'json' === $format ) {
            WP_CLI::line( wp_json_encode( $result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE ) );
            return;
        }

        if ( 'yaml' === $format ) {
            WP_CLI::line( $this->to_yaml( $result ) );
            return;
        }

        // Summary box
        $score = isset( $result['summary']['score'] ) ? $result['summary']['score'] : 0;
        $color = $score >= 90 ? '%G' : ( $score >= 70 ? '%Y' : '%R' );

        WP_CLI::line( '' );
        WP_CLI::line( '╔═══════════════════════════════════════════╗' );
        WP_CLI::line( '║   EQUALLYZE — RELATÓRIO DE ACESSIBILIDADE ║' );
        WP_CLI::line( '╠═══════════════════════════════════════════╣' );
        WP_CLI::line( sprintf( '║  Score:            %s%-3d/100%%n                ║', $color, $score ) );
        WP_CLI::line( sprintf( '║  Problemas:        %-4d                   ║', $result['summary']['totalProblems'] ) );
        WP_CLI::line( sprintf( '║  Regras violadas:  %-4d de %-3d             ║', $result['summary']['rulesViolated'], $result['summary']['rulesChecked'] ) );

        if ( isset( $result['urls_analyzed'] ) ) {
            WP_CLI::line( sprintf( '║  URLs analisadas:  %-4d                   ║', $result['urls_analyzed'] ) );
        }
        if ( isset( $result['errors'] ) && $result['errors'] > 0 ) {
            WP_CLI::line( sprintf( '║  Erros de acesso:  %-4d                   ║', $result['errors'] ) );
        }

        WP_CLI::line( sprintf( '║  Método:           %-22s ║', $result['summary']['method'] ) );
        WP_CLI::line( '╚═══════════════════════════════════════════╝' );

        // Violations table
        if ( ! empty( $result['violations'] ) ) {
            WP_CLI::line( '' );
            WP_CLI::line( '📋 Violações encontradas:' );
            WP_CLI::line( '' );

            $rows = array();
            foreach ( $result['violations'] as $v ) {
                $impact_icon = $this->impact_icon( $v['impact'] );
                $rows[] = array(
                    'impacto'   => $impact_icon . ' ' . $v['impact'],
                    'regra'     => $v['id'],
                    'descrição' => $v['description'],
                    'nbr'       => isset( $v['nbr'] ) ? $v['nbr'] : '',
                    'ocorr.'    => $v['count'],
                    'páginas'   => isset( $v['pages'] ) ? $v['pages'] : 1,
                );
            }

            $columns = array( 'impacto', 'regra', 'descrição', 'nbr', 'ocorr.' );
            if ( isset( $result['urls_analyzed'] ) ) {
                $columns[] = 'páginas';
            }

            \WP_CLI\Utils\format_items( $format, $rows, $columns );
        }

        WP_CLI::line( '' );
        WP_CLI::line( '💡 Para análise completa com WCAG 2.2 + NBR 17.225, use a interface web.' );
        WP_CLI::line( '   wp-admin → Wally Monitor → Analisar Site Completo' );
        WP_CLI::line( '' );
    }

    private function impact_icon( $impact ) {
        $icons = array(
            'critical' => '🔴',
            'serious'  => '🟠',
            'moderate' => '🟡',
            'minor'    => '🔵',
        );
        return isset( $icons[ $impact ] ) ? $icons[ $impact ] : '⚪';
    }

    // ─── Helpers ────────────────────────────────────────────────────────

    private function get_site_urls( $settings, $limit ) {
        $urls       = array();
        $post_types = ! empty( $settings['post_types'] ) ? $settings['post_types'] : array( 'post', 'page' );

        if ( ! empty( $settings['include_home'] ) ) {
            $urls[] = home_url( '/' );
        }

        $posts = get_posts( array(
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => $limit,
            'fields'         => 'ids',
        ) );

        foreach ( $posts as $post_id ) {
            $urls[] = get_permalink( $post_id );
        }

        return array_slice( array_unique( $urls ), 0, $limit );
    }

    private function save_result( $result ) {
        $db    = Wally_Database::get_instance();
        $url   = isset( $result['url'] ) ? $result['url'] : home_url( '/' );
        $score = $result['summary']['score'];
        $total = $result['summary']['totalProblems'];

        $db->insert_report( $url, $result, $score, $total );

        $history = get_option( 'wally_monitor_history', array() );
        array_unshift( $history, array(
            'timestamp'      => current_time( 'mysql' ),
            'user_id'        => 0,
            'score'          => $score,
            'total_problems' => $total,
            'url'            => $url,
            'source'         => 'CLI',
        ) );
        $history = array_slice( $history, 0, 50 );
        update_option( 'wally_monitor_history', $history, false );
    }

    private function get_latest_any_report() {
        return Wally_Database::get_instance()->get_latest_any_report();
    }

    /**
     * Map user-facing frequency string to WP cron recurrence.
     *
     * @param string $frequency User input (hourly|daily|weekly|monthly).
     * @return string Valid WP cron recurrence key.
     */
    private function map_frequency( $frequency ) {
        $map = array(
            'hourly'  => 'hourly',
            'daily'   => 'daily',
            'weekly'  => 'weekly',
            'monthly' => 'monthly',
        );
        return isset( $map[ $frequency ] ) ? $map[ $frequency ] : 'weekly';
    }

    private function to_yaml( $data, $indent = 0 ) {
        $yaml   = '';
        $prefix = str_repeat( '  ', $indent );
        foreach ( $data as $key => $value ) {
            if ( is_array( $value ) ) {
                $yaml .= $prefix . $key . ":\n";
                $yaml .= $this->to_yaml( $value, $indent + 1 );
            } else {
                $yaml .= $prefix . $key . ': ' . $value . "\n";
            }
        }
        return $yaml;
    }
}

WP_CLI::add_command( 'wally', 'Wally_CLI_Command' );
