<?php
/**
 * [FEAT 1.6.11] CSV VALIDATOR — validação proactiva antes de processar o CSV.
 *
 * Detecta problemas comuns que causam imports a falhar silenciosamente:
 *   - Encoding errado (não UTF-8)
 *   - BOM presente (informativo, já é tratado)
 *   - Separador inconsistente entre linhas
 *   - Linhas com número de colunas diferente do cabeçalho
 *   - Linhas vazias no meio do ficheiro
 *   - Caracteres invisíveis no cabeçalho (NBSP, espaços duplos)
 *   - Ficheiros suspeitamente grandes
 *
 * Devolve estrutura:
 *   [
 *     'ok'       => bool (false só se houver erro crítico),
 *     'errors'   => [ ['code', 'message', 'detail'?], ... ],
 *     'warnings' => [ ['code', 'message', 'detail'?], ... ],
 *     'info'     => [ ['code', 'message'], ... ],
 *     'stats'    => [ 'lines', 'separator', 'encoding', 'size_mb', 'has_bom' ],
 *   ]
 *
 * Usa só PHP nativo — não depende de iconv ou mb_string (que podem não estar
 * disponíveis em todas as instalações).
 */

defined( 'ABSPATH' ) or exit;

if ( ! function_exists( 'rmbz_validate_csv_file' ) ) {

function rmbz_validate_csv_file( $file_path, $max_lines_scan = 5000 ) {
    $result = [
        'ok'       => true,
        'errors'   => [],
        'warnings' => [],
        'info'     => [],
        'stats'    => [
            'lines'      => 0,
            'separator'  => '?',
            'encoding'   => '?',
            'size_mb'    => 0,
            'has_bom'    => false,
        ],
    ];

    // ─────────────────────────────────────────────────────────────────────
    // 1. EXISTÊNCIA E TAMANHO
    // ─────────────────────────────────────────────────────────────────────
    if ( ! is_readable( $file_path ) ) {
        $result['ok'] = false;
        $result['errors'][] = [
            'code'    => 'unreadable',
            'message' => 'Ficheiro não existe ou não é legível.',
        ];
        return $result;
    }
    $size_bytes = filesize( $file_path );
    $size_mb    = $size_bytes / 1048576;
    $result['stats']['size_mb'] = round( $size_mb, 2 );

    if ( $size_bytes === 0 ) {
        $result['ok'] = false;
        $result['errors'][] = [
            'code'    => 'empty',
            'message' => 'Ficheiro está vazio (0 bytes).',
        ];
        return $result;
    }
    if ( $size_mb > 50 ) {
        $result['warnings'][] = [
            'code'    => 'large_file',
            'message' => sprintf(
                'Ficheiro muito grande (%s MB) — pode demorar bastante a processar ou exceder limites do servidor.',
                number_format( $size_mb, 1 )
            ),
        ];
    }

    // ─────────────────────────────────────────────────────────────────────
    // 2. LER OS PRIMEIROS BYTES — detectar BOM e encoding
    // ─────────────────────────────────────────────────────────────────────
    $fh = fopen( $file_path, 'rb' );
    if ( ! $fh ) {
        $result['ok'] = false;
        $result['errors'][] = [
            'code'    => 'open_failed',
            'message' => 'Não foi possível abrir o ficheiro para leitura.',
        ];
        return $result;
    }
    $first_bytes = fread( $fh, 4096 ); // primeiros 4KB para análise

    // BOM UTF-8 (EF BB BF)
    $has_bom = ( substr( $first_bytes, 0, 3 ) === "\xEF\xBB\xBF" );
    $result['stats']['has_bom'] = $has_bom;
    if ( $has_bom ) {
        $result['info'][] = [
            'code'    => 'bom_present',
            'message' => 'Ficheiro tem BOM UTF-8 (removido automaticamente).',
        ];
    }

    // Detecção de encoding heurística (sem iconv)
    // Procurar bytes >127 e ver se seguem padrão UTF-8 válido
    $sample = $has_bom ? substr( $first_bytes, 3 ) : $first_bytes;
    $encoding = rmbz_csv_detect_encoding( $sample );
    $result['stats']['encoding'] = $encoding;

    if ( $encoding === 'invalid_utf8' ) {
        $result['errors'][] = [
            'code'    => 'bad_encoding',
            'message' => 'O ficheiro NÃO está em UTF-8 — provavelmente Windows-1252 ou Latin-1.',
            'detail'  => 'Caracteres acentuados (á, é, ã, ç) vão aparecer corrompidos. Reabre o CSV no Excel/Calc, vai a "Guardar como" e escolhe codificação "UTF-8".',
        ];
        $result['ok'] = false;
    } elseif ( $encoding === 'ascii' ) {
        $result['info'][] = [
            'code'    => 'ascii_safe',
            'message' => 'Codificação ASCII pura (compatível com UTF-8).',
        ];
    } elseif ( $encoding === 'utf8' ) {
        $result['info'][] = [
            'code'    => 'utf8_ok',
            'message' => 'Codificação UTF-8 válida.',
        ];
    }

    // ─────────────────────────────────────────────────────────────────────
    // 3. DETECTAR SEPARADOR — analisar primeira linha
    // ─────────────────────────────────────────────────────────────────────
    rewind( $fh );
    if ( $has_bom ) fread( $fh, 3 ); // skip BOM
    $first_line = fgets( $fh );
    if ( $first_line === false || trim( $first_line ) === '' ) {
        $result['ok'] = false;
        $result['errors'][] = [
            'code'    => 'no_header',
            'message' => 'Primeira linha (cabeçalho) está vazia.',
        ];
        fclose( $fh );
        return $result;
    }

    $separator_counts = [
        ','  => substr_count( $first_line, ',' ),
        ';'  => substr_count( $first_line, ';' ),
        "\t" => substr_count( $first_line, "\t" ),
        '|'  => substr_count( $first_line, '|' ),
    ];
    arsort( $separator_counts );
    $detected_sep = key( $separator_counts );
    $max_count    = current( $separator_counts );

    if ( $max_count === 0 ) {
        $result['errors'][] = [
            'code'    => 'no_separator',
            'message' => 'Não foi possível detectar separador na primeira linha (nenhum dos comuns: vírgula, ponto-e-vírgula, tab, pipe).',
            'detail'  => 'Verifica se o ficheiro é mesmo um CSV. Pode ser texto livre ou outro formato.',
        ];
        $result['ok'] = false;
        fclose( $fh );
        return $result;
    }

    $sep_label = [ ',' => 'vírgula (,)', ';' => 'ponto-e-vírgula (;)', "\t" => 'tab', '|' => 'pipe (|)' ][ $detected_sep ] ?? $detected_sep;
    $result['stats']['separator'] = $sep_label;
    $expected_cols = $max_count + 1; // N separadores = N+1 colunas

    // ─────────────────────────────────────────────────────────────────────
    // 4. ANÁLISE DO CABEÇALHO — caracteres invisíveis, colunas duplicadas
    // ─────────────────────────────────────────────────────────────────────
    $header_str_clean = $has_bom ? preg_replace( '/^\xEF\xBB\xBF/', '', $first_line ) : $first_line;
    $header_parsed = str_getcsv( $header_str_clean, $detected_sep );

    $weird_headers = [];
    $duplicates    = [];
    $seen          = [];
    foreach ( $header_parsed as $i => $col ) {
        $col_trim = trim( $col );
        // NBSP, tab, espaços duplos, controlo
        if ( preg_match( '/[\xC2\xA0]/', $col ) ) {
            $weird_headers[] = "Coluna #" . ($i+1) . " tem espaço não-quebrável (NBSP) — pode causar match incorrecto: \"" . esc_html( $col ) . "\"";
        }
        if ( strlen( $col ) !== strlen( $col_trim ) ) {
            $weird_headers[] = "Coluna #" . ($i+1) . " tem espaços no início ou no fim: \"" . esc_html( $col ) . "\"";
        }
        if ( $col_trim === '' ) {
            $weird_headers[] = "Coluna #" . ($i+1) . " está vazia (sem nome)";
        }
        $key = strtolower( $col_trim );
        if ( $key !== '' && isset( $seen[$key] ) ) {
            $duplicates[] = "Coluna \"" . esc_html( $col_trim ) . "\" aparece mais que uma vez (posições #" . ($seen[$key]+1) . " e #" . ($i+1) . ")";
        }
        $seen[$key] = $i;
    }
    if ( ! empty( $weird_headers ) ) {
        $result['warnings'][] = [
            'code'    => 'weird_header',
            'message' => 'Cabeçalho tem caracteres invisíveis ou colunas vazias.',
            'detail'  => $weird_headers,
        ];
    }
    if ( ! empty( $duplicates ) ) {
        $result['warnings'][] = [
            'code'    => 'duplicate_cols',
            'message' => 'Cabeçalho tem colunas duplicadas — só a primeira vai ser usada.',
            'detail'  => $duplicates,
        ];
    }

    // ─────────────────────────────────────────────────────────────────────
    // 5. SCAN DAS LINHAS DE DADOS — colunas inconsistentes, vazias
    // ─────────────────────────────────────────────────────────────────────
    $lines_total      = 1; // já contámos o cabeçalho
    $lines_empty      = 0;
    $lines_with_wrong_cols = []; // [ line_num => actual_cols ]
    $lines_short      = 0;
    $sample_lines     = 0;

    while ( ! feof( $fh ) && $sample_lines < $max_lines_scan ) {
        $line = fgets( $fh );
        if ( $line === false ) break;
        $lines_total++;
        $sample_lines++;
        $line_trim = trim( $line );
        if ( $line_trim === '' ) {
            $lines_empty++;
            continue;
        }
        // Contar colunas (rápido, não usa str_getcsv para ser rápido)
        $actual_cols = substr_count( $line_trim, $detected_sep ) + 1;
        if ( $actual_cols !== $expected_cols ) {
            // Tolerância: aspas-escapadas podem ter separadores dentro
            // Usar str_getcsv para confirmar
            $parsed = str_getcsv( $line_trim, $detected_sep );
            $real_cols = count( $parsed );
            if ( $real_cols !== $expected_cols ) {
                if ( count( $lines_with_wrong_cols ) < 10 ) { // limite a 10 amostras
                    $lines_with_wrong_cols[ $lines_total ] = $real_cols;
                }
                if ( $real_cols < $expected_cols / 2 ) {
                    $lines_short++;
                }
            }
        }
    }
    fclose( $fh );

    // Se ficheiro maior que sample, contar linhas estimadas
    $estimated_total = $lines_total;
    if ( $sample_lines >= $max_lines_scan && $size_bytes > 0 ) {
        // Estimativa: linhas no sample × (tamanho_total / tamanho_lido)
        $bytes_per_line = ftell_helper( $sample_lines, $size_bytes );
        $estimated_total = (int) ( $size_bytes / max(1, $bytes_per_line) );
        $result['info'][] = [
            'code'    => 'sample_only',
            'message' => sprintf(
                'Ficheiro tem ~%s linhas; analisadas as primeiras %d (estatísticas são uma amostra).',
                number_format( $estimated_total ),
                $sample_lines
            ),
        ];
    }
    $result['stats']['lines'] = $estimated_total;

    if ( $lines_empty > 0 ) {
        $result['warnings'][] = [
            'code'    => 'empty_lines',
            'message' => sprintf(
                'Encontradas %d linha%s vazia%s no meio do ficheiro — vão ser ignoradas.',
                $lines_empty,
                $lines_empty === 1 ? '' : 's',
                $lines_empty === 1 ? '' : 's'
            ),
        ];
    }
    if ( ! empty( $lines_with_wrong_cols ) ) {
        $sample_list = array_slice( $lines_with_wrong_cols, 0, 5, true );
        $detail = [];
        foreach ( $sample_list as $ln => $cols ) {
            $detail[] = "Linha {$ln}: tem {$cols} colunas (cabeçalho tem {$expected_cols})";
        }
        $severity = $lines_short > 5 ? 'errors' : 'warnings';
        $result[ $severity ][] = [
            'code'    => 'col_mismatch',
            'message' => sprintf(
                '%d linha%s com número de colunas diferente do cabeçalho.',
                count( $lines_with_wrong_cols ),
                count( $lines_with_wrong_cols ) === 1 ? '' : 's'
            ),
            'detail'  => array_merge( $detail, [ 'Possível causa: aspas mal-escapadas ou separador dentro de valores não envolvidos em aspas.' ] ),
        ];
        if ( $severity === 'errors' ) $result['ok'] = false;
    }

    if ( $lines_total === 1 ) {
        $result['errors'][] = [
            'code'    => 'no_data',
            'message' => 'CSV só tem cabeçalho, sem linhas de dados.',
        ];
        $result['ok'] = false;
    }

    return $result;
}

/**
 * Detecção heurística de encoding (sem iconv/mbstring).
 * Devolve: 'ascii', 'utf8', 'invalid_utf8'
 */
function rmbz_csv_detect_encoding( $sample ) {
    $has_high_bytes = false;
    $len = strlen( $sample );
    for ( $i = 0; $i < $len; $i++ ) {
        $b = ord( $sample[$i] );
        if ( $b > 127 ) {
            $has_high_bytes = true;
            break;
        }
    }
    if ( ! $has_high_bytes ) return 'ascii';

    // Validar UTF-8 — sequence rules
    $i = 0;
    while ( $i < $len ) {
        $b = ord( $sample[$i] );
        if ( $b < 0x80 ) {
            $i++;
        } elseif ( ( $b & 0xE0 ) === 0xC0 ) {
            // 2-byte: 110xxxxx 10xxxxxx
            if ( $i + 1 >= $len || ( ord( $sample[$i+1] ) & 0xC0 ) !== 0x80 ) return 'invalid_utf8';
            $i += 2;
        } elseif ( ( $b & 0xF0 ) === 0xE0 ) {
            // 3-byte
            if ( $i + 2 >= $len ) break; // amostra truncada — não conclui
            if ( ( ord( $sample[$i+1] ) & 0xC0 ) !== 0x80 ) return 'invalid_utf8';
            if ( ( ord( $sample[$i+2] ) & 0xC0 ) !== 0x80 ) return 'invalid_utf8';
            $i += 3;
        } elseif ( ( $b & 0xF8 ) === 0xF0 ) {
            // 4-byte
            if ( $i + 3 >= $len ) break;
            for ( $j = 1; $j <= 3; $j++ ) {
                if ( ( ord( $sample[$i+$j] ) & 0xC0 ) !== 0x80 ) return 'invalid_utf8';
            }
            $i += 4;
        } else {
            return 'invalid_utf8';
        }
    }
    return 'utf8';
}

function ftell_helper( $sample_lines, $size_bytes ) {
    // Heurística simples — 80 chars/linha média se desconhecido
    return $sample_lines > 0 ? max( 1, $size_bytes / max( 1, $sample_lines ) ) : 80;
}

} // if ! function_exists

/**
 * Renderer HTML do painel de validação.
 */
if ( ! function_exists( 'rmbz_render_csv_validation_panel' ) ) {

function rmbz_render_csv_validation_panel( $validation ) {
    if ( empty( $validation ) ) return;
    $has_errors   = ! empty( $validation['errors'] );
    $has_warnings = ! empty( $validation['warnings'] );

    if ( ! $has_errors && ! $has_warnings ) {
        // Tudo OK — mostrar painel verde compacto com stats
        ?>
        <div class="rmbz-csv-valid rmbz-csv-valid-ok">
            <div class="rmbz-csv-valid-head">
                <span class="rmbz-csv-valid-icon">✓</span>
                <strong>CSV validado — pronto para processar</strong>
                <span class="rmbz-csv-valid-stats">
                    <?php echo number_format( (int) $validation['stats']['lines'] ); ?> linhas ·
                    <?php echo esc_html( $validation['stats']['separator'] ); ?> ·
                    <?php echo esc_html( strtoupper( $validation['stats']['encoding'] ) ); ?> ·
                    <?php echo esc_html( $validation['stats']['size_mb'] ); ?> MB
                </span>
            </div>
        </div>
        <?php
        return;
    }

    ?>
    <div class="rmbz-csv-valid <?php echo $has_errors ? 'rmbz-csv-valid-error' : 'rmbz-csv-valid-warn'; ?>">
        <div class="rmbz-csv-valid-head">
            <span class="rmbz-csv-valid-icon"><?php echo $has_errors ? '⚠' : '⚠'; ?></span>
            <strong>
                <?php if ( $has_errors ): ?>
                    Problemas detectados no CSV — corrige antes de continuar
                <?php else: ?>
                    Avisos do CSV — podes continuar mas verifica
                <?php endif; ?>
            </strong>
            <span class="rmbz-csv-valid-stats">
                <?php echo number_format( (int) $validation['stats']['lines'] ); ?> linhas ·
                <?php echo esc_html( $validation['stats']['separator'] ); ?> ·
                <?php echo esc_html( strtoupper( $validation['stats']['encoding'] ) ); ?>
            </span>
        </div>

        <?php if ( $has_errors ): ?>
            <div class="rmbz-csv-valid-section">
                <div class="rmbz-csv-valid-section-title rmbz-csv-valid-section-error">
                    <?php echo count( $validation['errors'] ); ?> erro<?php echo count( $validation['errors'] ) === 1 ? '' : 's'; ?> crítico<?php echo count( $validation['errors'] ) === 1 ? '' : 's'; ?>
                </div>
                <?php foreach ( $validation['errors'] as $err ): ?>
                    <div class="rmbz-csv-valid-item rmbz-csv-valid-item-error">
                        <div class="rmbz-csv-valid-msg"><?php echo esc_html( $err['message'] ); ?></div>
                        <?php if ( ! empty( $err['detail'] ) ): ?>
                            <div class="rmbz-csv-valid-detail">
                                <?php if ( is_array( $err['detail'] ) ): ?>
                                    <ul><?php foreach ( $err['detail'] as $d ): ?><li><?php echo esc_html( $d ); ?></li><?php endforeach; ?></ul>
                                <?php else: ?>
                                    <?php echo esc_html( $err['detail'] ); ?>
                                <?php endif; ?>
                            </div>
                        <?php endif; ?>
                    </div>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>

        <?php if ( $has_warnings ): ?>
            <div class="rmbz-csv-valid-section">
                <div class="rmbz-csv-valid-section-title rmbz-csv-valid-section-warn">
                    <?php echo count( $validation['warnings'] ); ?> aviso<?php echo count( $validation['warnings'] ) === 1 ? '' : 's'; ?>
                </div>
                <?php foreach ( $validation['warnings'] as $warn ): ?>
                    <div class="rmbz-csv-valid-item rmbz-csv-valid-item-warn">
                        <div class="rmbz-csv-valid-msg"><?php echo esc_html( $warn['message'] ); ?></div>
                        <?php if ( ! empty( $warn['detail'] ) ): ?>
                            <div class="rmbz-csv-valid-detail">
                                <?php if ( is_array( $warn['detail'] ) ): ?>
                                    <ul><?php foreach ( $warn['detail'] as $d ): ?><li><?php echo esc_html( $d ); ?></li><?php endforeach; ?></ul>
                                <?php else: ?>
                                    <?php echo esc_html( $warn['detail'] ); ?>
                                <?php endif; ?>
                            </div>
                        <?php endif; ?>
                    </div>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>
    </div>

    <style>
    .rmbz-csv-valid {
        border-radius: 10px;
        margin: 0 0 16px 0;
        overflow: hidden;
        font-size: 13px;
    }
    .rmbz-csv-valid-ok {
        background: linear-gradient(135deg, #ecfdf5 0%, #f0fdfa 100%);
        border: 1px solid #6ee7b7;
        border-left: 4px solid #10b981;
    }
    .rmbz-csv-valid-warn {
        background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
        border: 1px solid #fcd34d;
        border-left: 4px solid #f59e0b;
    }
    .rmbz-csv-valid-error {
        background: linear-gradient(135deg, #fee2e2 0%, #fef2f2 100%);
        border: 1px solid #fca5a5;
        border-left: 4px solid #dc2626;
    }
    .rmbz-csv-valid-head {
        display: flex; align-items: center; gap: 10px;
        padding: 12px 18px;
        flex-wrap: wrap;
    }
    .rmbz-csv-valid-icon { font-size: 18px; }
    .rmbz-csv-valid-ok .rmbz-csv-valid-head { color: #065f46; }
    .rmbz-csv-valid-warn .rmbz-csv-valid-head { color: #92400e; }
    .rmbz-csv-valid-error .rmbz-csv-valid-head { color: #7f1d1d; }
    .rmbz-csv-valid-stats {
        margin-left: auto;
        font-size: 11px;
        color: #6b7280;
        font-family: ui-monospace, monospace;
    }
    .rmbz-csv-valid-section {
        background: #fff;
        margin: 0 14px 12px 14px;
        border-radius: 7px;
        padding: 10px 14px;
    }
    .rmbz-csv-valid-section-title {
        font-size: 10px; text-transform: uppercase;
        letter-spacing: 0.05em; font-weight: 700;
        margin-bottom: 8px;
    }
    .rmbz-csv-valid-section-error { color: #7f1d1d; }
    .rmbz-csv-valid-section-warn  { color: #92400e; }
    .rmbz-csv-valid-item {
        padding: 6px 0;
        border-bottom: 1px solid #f3f4f6;
    }
    .rmbz-csv-valid-item:last-child { border-bottom: none; }
    .rmbz-csv-valid-msg { font-weight: 500; color: #1a1a1a; }
    .rmbz-csv-valid-detail {
        font-size: 11.5px; color: #6b7280;
        margin-top: 4px; line-height: 1.5;
    }
    .rmbz-csv-valid-detail ul { margin: 4px 0 0 18px; padding: 0; }
    .rmbz-csv-valid-detail li { padding: 1px 0; }
    </style>
    <?php
}

} // if ! function_exists

// Carregar automaticamente
if ( ! function_exists( 'rmbz_csv_validator_loaded' ) ) {
    function rmbz_csv_validator_loaded() { return true; }
}
