<?php
/**
 * includes/distribution/distribution-intelligence.php
 *
 * Camada de inteligência/detecção para o workflow de aprovação.
 * Três responsabilidades:
 *
 *   1. FRAUD DETECTION — heurísticas para detectar submissões suspeitas.
 *      Cada sinal devolve uma "flag" que o admin vê ao lado do release.
 *      As flags NUNCA bloqueiam aprovação automática (demasiados falsos
 *      positivos em PT/MZ) — servem para chamar a atenção do revisor.
 *
 *   2. CHECKLIST RIGOROSO — extensão do `rmbz_admin_compute_review_checklist`
 *      com mais 5 verificações determinísticas (loudness, silence leading,
 *      duração razoável, ISRC duplicado, capa quadrada confirmada).
 *      Estas SIM bloqueiam aprovação porque são objectivas.
 *
 *   3. ADMIN OVERRIDE DE DSPs — o admin pode adicionar/remover DSPs à
 *      selecção feita pelo artista, com auditoria (fica registado quem
 *      mudou, o quê e quando, na rmbz_distro_status_history).
 *
 * Todas as flags são computadas em tempo real (não há tabela persistida).
 * As listas de palavras problemáticas / nomes famosos estão em options
 * WordPress para o admin poder editar sem tocar em código:
 *
 *   - rmbz_fraud_blocked_words         (array de strings)
 *   - rmbz_fraud_famous_artists_hint   (array de strings, para "copycat")
 */

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


/* ============================================================================
 * SEED — listas por defeito (o admin pode ampliar em Settings)
 * ============================================================================ */

function rmbz_fraud_default_blocked_words() {
    // Lista de arranque muito conservadora — incentivos claros a drogas/ódio.
    // O admin DEVE ampliar esta lista consoante as decisões editoriais da 1MZ,
    // idealmente em português de MZ + changana + macua. Fica em option para
    // não precisar de deploy de código.
    return [
        // Inglês (ódio)
        'kill all', 'hate crime',
        // PT (drogas / violência explícita em título)
        'cocaina', 'heroina', 'metanfetamina',
        // Mais termos devem ser adicionados pelo admin em
        // Settings → 1MZ → Fraud filters
    ];
}

function rmbz_fraud_default_famous_artists() {
    // Nomes com copyright de trademark forte — usados só para "copycat" check.
    // NÃO bloqueia por ter parte do nome (ex: "Mr. Bow Junior" é legítimo).
    // O sinal só dispara quando o nome submetido é quase igual (Levenshtein).
    return [
        'Beyoncé', 'Drake', 'The Weeknd', 'Rihanna', 'Kanye West',
        'Taylor Swift', 'Bad Bunny', 'Burna Boy', 'Wizkid', 'Davido',
        'Tems', 'Rema', 'Asake',
        // Artistas MZ/PALOP com base de fãs grande (trademark informal)
        'Mr. Bow', 'Marllen', 'Neyma', 'Lizha James', 'Ziqo',
        'Valete', 'Dino d\'Santiago', 'Plutónio',
    ];
}

function rmbz_fraud_get_blocked_words() {
    $stored = get_option('rmbz_fraud_blocked_words', null);
    if ( ! is_array($stored) ) $stored = rmbz_fraud_default_blocked_words();
    return array_filter( array_map('trim', $stored) );
}

function rmbz_fraud_get_famous_artists() {
    $stored = get_option('rmbz_fraud_famous_artists_hint', null);
    if ( ! is_array($stored) ) $stored = rmbz_fraud_default_famous_artists();
    return array_filter( array_map('trim', $stored) );
}


/* ============================================================================
 * UTILIDADES — normalização e ficheiros áudio
 * ============================================================================ */

/**
 * Normaliza uma string para matching fuzzy: minúsculas, sem acentos,
 * sem pontuação, sem espaços múltiplos.
 */
function rmbz_fraud_normalize( $s ) {
    $s = mb_strtolower( trim( (string) $s ), 'UTF-8' );
    // Remover acentos (usa remove_accents do WP se disponível)
    if ( function_exists('remove_accents') ) $s = remove_accents( $s );
    $s = preg_replace('/[^a-z0-9\s]+/u', ' ', $s);
    $s = preg_replace('/\s+/', ' ', $s);
    return trim($s);
}

/**
 * Converte URL /uploads/... em path local, se possível.
 */
function rmbz_fraud_url_to_path( $url ) {
    if ( empty($url) ) return '';
    $upload_dir = wp_upload_dir();
    if ( empty($upload_dir['baseurl']) ) return '';
    $path = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $url );
    return $path !== $url ? $path : '';
}


/* ============================================================================
 * SINAL 1 — Copycat de nome famoso
 * ============================================================================
 * Devolve flag se o nome do artista estiver muito próximo de um nome famoso.
 * Usa distância de Levenshtein normalizada: <= 2 carácteres de diferença em
 * nomes de comprimento similar (evita false positives como "Mr. Bow Junior").
 */
function rmbz_fraud_check_copycat( $artist_name ) {
    $artist_name = trim( (string) $artist_name );
    if ( $artist_name === '' ) return null;

    $needle = rmbz_fraud_normalize( $artist_name );
    if ( strlen($needle) < 3 ) return null;

    foreach ( rmbz_fraud_get_famous_artists() as $famous ) {
        $target = rmbz_fraud_normalize( $famous );
        if ( $target === '' ) continue;

        // Match exacto (case-insensitive, sem acentos) — mais suspeito
        if ( $needle === $target ) {
            return [
                'severity' => 'high',
                'label'    => sprintf('Nome idêntico a "%s"', $famous),
                'detail'   => 'Submissão pode ser copycat/impostora. Verifica identidade via redes oficiais do artista famoso.',
            ];
        }

        // Levenshtein <= 2 em nomes de comprimento similar
        $len_diff = abs( strlen($needle) - strlen($target) );
        if ( $len_diff <= 2 && strlen($target) >= 4 ) {
            $dist = levenshtein( $needle, $target );
            if ( $dist > 0 && $dist <= 2 ) {
                return [
                    'severity' => 'medium',
                    'label'    => sprintf('Nome muito parecido com "%s"', $famous),
                    'detail'   => sprintf('Distância: %d caracteres. Pode ser coincidência, mas vale a pena verificar.', $dist),
                ];
            }
        }
    }
    return null;
}


/* ============================================================================
 * SINAL 2 — Música muito curta
 * ============================================================================ */
function rmbz_fraud_check_short_tracks( $tracks ) {
    $short = [];
    foreach ( (array) $tracks as $t ) {
        $duration = 0;
        if ( ! empty($t->extra_data) ) {
            $extra = json_decode( $t->extra_data, true );
            if ( is_array($extra) && ! empty($extra['duration_seconds']) ) {
                $duration = (int) $extra['duration_seconds'];
            }
        }
        // Se não temos duration na metadata, tentamos ler do ficheiro
        if ( $duration === 0 && ! empty($t->audio_url) ) {
            $path = rmbz_fraud_url_to_path( $t->audio_url );
            if ( $path && file_exists($path) && function_exists('wp_read_audio_metadata') ) {
                require_once( ABSPATH . 'wp-admin/includes/media.php' );
                $meta = wp_read_audio_metadata( $path );
                if ( ! empty($meta['length']) ) $duration = (int) round($meta['length']);
            }
        }
        if ( $duration > 0 && $duration < 30 ) {
            $short[] = sprintf('"%s" (%ds)', $t->track_title ?? 'sem título', $duration);
        }
    }
    if ( empty($short) ) return null;
    return [
        'severity' => 'high',
        'label'    => count($short) === 1 ? 'Faixa muito curta' : count($short) . ' faixas muito curtas',
        'detail'   => 'Os DSPs rejeitam faixas abaixo de 30s (algumas até 60s). Verifica: ' . implode(', ', array_slice($short, 0, 3)),
    ];
}


/* ============================================================================
 * SINAL 3 — Spam (várias submissões em pouco tempo)
 * ============================================================================ */
function rmbz_fraud_check_spam( $artist_telemovel, $release_id ) {
    if ( empty($artist_telemovel) ) return null;
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_distro_releases';
    $count = (int) $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT(*) FROM $t
         WHERE artist_telemovel = %s
           AND id != %d
           AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)",
        $artist_telemovel, $release_id
    ) );
    if ( $count < 3 ) return null;
    return [
        'severity' => 'medium',
        'label'    => sprintf('%d submissões nas últimas 24h', $count + 1),
        'detail'   => 'Muitas submissões em pouco tempo podem indicar uploads automatizados ou tentativa de saturar a fila. Verifica todas antes de aprovar.',
    ];
}


/* ============================================================================
 * SINAL 4 — Primeiro envio (sem histórico)
 * ============================================================================ */
function rmbz_fraud_check_first_submission( $artist_telemovel, $release_id ) {
    if ( empty($artist_telemovel) ) return null;
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_distro_releases';
    // Primeira submissão = este é o primeiro release de sempre OU todos os
    // anteriores ainda estão em `incomplete`.
    $approved_before = (int) $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT(*) FROM $t
         WHERE artist_telemovel = %s
           AND id != %d
           AND status IN ('approved','delivered','live','distribuido')",
        $artist_telemovel, $release_id
    ) );
    if ( $approved_before > 0 ) return null;
    return [
        'severity' => 'low',
        'label'    => 'Primeiro lançamento do artista',
        'detail'   => 'Artista ainda não tem releases aprovados. Recomenda-se revisão mais atenta — especialmente identidade e direitos.',
    ];
}


/* ============================================================================
 * SINAL 5 — Metadata contraditória
 * ============================================================================ */
function rmbz_fraud_check_metadata_contradiction( $tracks ) {
    $issues = [];
    foreach ( (array) $tracks as $t ) {
        if ( empty($t->extra_data) ) continue;
        $extra = json_decode( $t->extra_data, true );
        if ( ! is_array($extra) ) continue;

        $is_instr = ! empty($extra['is_instrumental']);
        $has_lyr  = ! empty($extra['has_lyrics']);
        $lyrics   = trim( (string) ($extra['lyrics'] ?? '') );

        if ( $is_instr && ($has_lyr || $lyrics !== '') ) {
            $issues[] = sprintf('"%s" marcada como instrumental mas com letra', $t->track_title ?? 'faixa');
        }
        if ( $has_lyr && $lyrics === '' ) {
            $issues[] = sprintf('"%s" diz ter letra mas letra está vazia', $t->track_title ?? 'faixa');
        }
        // Explícito + instrumental é raro mas legítimo (ex: violência instrumental em hip-hop).
        // Não flag.

        // Cover marcado mas sem compositor original
        if ( ! empty($extra['is_cover']) && empty( trim( (string) ($extra['cover_original_composer'] ?? '') ) ) ) {
            $issues[] = sprintf('"%s" é cover mas sem compositor original declarado', $t->track_title ?? 'faixa');
        }

        // Previously released mas sem ISRC original
        if ( ! empty($extra['previously_released']) && empty( trim( (string) ($extra['original_isrc'] ?? '') ) ) ) {
            $issues[] = sprintf('"%s" previamente lançada mas sem ISRC original', $t->track_title ?? 'faixa');
        }
    }
    if ( empty($issues) ) return null;
    return [
        'severity' => 'medium',
        'label'    => count($issues) === 1 ? 'Metadata contraditória' : count($issues) . ' contradições de metadata',
        'detail'   => implode(' | ', array_slice($issues, 0, 4)),
    ];
}


/* ============================================================================
 * SINAL 6 — Palavras problemáticas em títulos
 * ============================================================================ */
function rmbz_fraud_check_blocked_words( $release, $tracks ) {
    $blocked = rmbz_fraud_get_blocked_words();
    if ( empty($blocked) ) return null;

    $texts = [];
    $texts['título release'] = $release->title ?? '';
    foreach ( (array) $tracks as $t ) {
        $texts['faixa: ' . ($t->track_title ?? '?')] = $t->track_title ?? '';
    }

    $hits = [];
    foreach ( $texts as $label => $text ) {
        $norm = rmbz_fraud_normalize( $text );
        if ( $norm === '' ) continue;
        foreach ( $blocked as $word ) {
            $word_norm = rmbz_fraud_normalize( $word );
            if ( $word_norm === '' ) continue;
            // Match por palavra inteira (word boundary) para evitar "scatter" > "hate"
            if ( preg_match('/\b' . preg_quote($word_norm, '/') . '\b/', $norm) ) {
                $hits[] = sprintf('"%s" em %s', $word, $label);
                break; // uma só flag por local chega
            }
        }
    }
    if ( empty($hits) ) return null;
    return [
        'severity' => 'high',
        'label'    => count($hits) === 1 ? 'Termo problemático detectado' : count($hits) . ' termos problemáticos',
        'detail'   => implode(' | ', array_slice($hits, 0, 3)) . '. Verifica se está dentro da política editorial.',
    ];
}


/* ============================================================================
 * SINAL 7 — Capa "provavelmente IA" (heurística fraca, avisa explicitamente)
 * ============================================================================
 * Atenção: esta heurística tem MUITOS falsos positivos (capas minimalistas
 * legítimas). Marca como severity 'low' e explicita a limitação no detail.
 * Detecta duas coisas:
 *   (a) Paleta extremamente concentrada (< 5 cores dominantes distintas).
 *   (b) Dimensões "excessivamente redondas" típicas de generators (1024×1024).
 */
function rmbz_fraud_check_cover_ai( $release ) {
    if ( empty($release->cover_image) ) return null;
    $path = rmbz_fraud_url_to_path( $release->cover_image );
    if ( ! $path || ! file_exists($path) ) return null;
    if ( ! function_exists('imagecreatefromstring') ) return null; // GD necessário

    $info = @getimagesize( $path );
    if ( ! $info ) return null;
    list($w, $h) = $info;

    $signals = [];

    // Dimensões típicas de geradores de IA
    $ai_sizes = [ [1024,1024], [768,768], [512,512], [2048,2048] ];
    foreach ( $ai_sizes as $pair ) {
        if ( $w === $pair[0] && $h === $pair[1] ) {
            $signals[] = sprintf('dimensão exacta %d×%d (típica de geradores)', $w, $h);
            break;
        }
    }

    // Análise rápida de paleta (só se imagem ≤ 2048px, senão ficheiro muito grande)
    if ( max($w, $h) <= 2048 ) {
        $content = @file_get_contents($path);
        if ( $content ) {
            $img = @imagecreatefromstring($content);
            if ( $img ) {
                // Sample de 40×40 pontos distribuídos
                $palette = [];
                for ( $x = 0; $x < 40; $x++ ) {
                    for ( $y = 0; $y < 40; $y++ ) {
                        $px = imagecolorat( $img, (int) ($x * $w / 40), (int) ($y * $h / 40) );
                        // Quantizar para 64 níveis por canal (reduz ruído JPEG)
                        $r = ($px >> 16 & 0xFF) >> 2;
                        $g = ($px >> 8 & 0xFF) >> 2;
                        $b = ($px & 0xFF) >> 2;
                        $key = ($r << 12) | ($g << 6) | $b;
                        $palette[$key] = ($palette[$key] ?? 0) + 1;
                    }
                }
                imagedestroy($img);
                // Top 5 cores ocupam X% da imagem
                arsort($palette);
                $total = array_sum($palette);
                $top5  = array_sum( array_slice($palette, 0, 5, true) );
                $top5_pct = $total > 0 ? ($top5 / $total) : 0;
                if ( $top5_pct > 0.85 ) {
                    $signals[] = sprintf('paleta muito concentrada (%.0f%% da imagem em só 5 tons)', $top5_pct * 100);
                }
            }
        }
    }

    if ( empty($signals) ) return null;
    return [
        'severity' => 'low',
        'label'    => 'Capa possivelmente IA-generated',
        'detail'   => 'Heurística fraca — pode ser capa minimalista legítima. Sinais: ' . implode(' + ', $signals) . '. NÃO bloqueia.',
    ];
}


/* ============================================================================
 * API PÚBLICA — compila todas as flags
 * ============================================================================ */
function rmbz_fraud_compute_flags( $release, $tracks ) {
    if ( empty($release) ) return [];

    // Nome do artista — tentar buscar da wallet
    global $wpdb;
    $artist_name = '';
    $tele = $release->artist_telemovel ?? '';
    if ( $tele ) {
        $artist_name = $wpdb->get_var( $wpdb->prepare(
            "SELECT user_nome FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s",
            $tele
        ) ) ?: '';
    }

    $flags = [];
    $candidates = [
        rmbz_fraud_check_copycat( $artist_name ),
        rmbz_fraud_check_short_tracks( $tracks ),
        rmbz_fraud_check_spam( $tele, (int) ($release->id ?? 0) ),
        rmbz_fraud_check_first_submission( $tele, (int) ($release->id ?? 0) ),
        rmbz_fraud_check_metadata_contradiction( $tracks ),
        rmbz_fraud_check_blocked_words( $release, $tracks ),
        rmbz_fraud_check_cover_ai( $release ),
    ];
    foreach ( $candidates as $f ) {
        if ( $f ) $flags[] = $f;
    }
    return $flags;
}


/* ============================================================================
 * CHECKLIST RIGOROSO — 5 checks determinísticos extra
 * ============================================================================
 * Ao contrário das flags (heurísticas), estes SIM bloqueiam aprovação porque
 * são objectivos. Adicionam-se ao checklist existente via filter.
 */
add_filter('rmbz_admin_review_checklist_extra', 'rmbz_fraud_extra_checklist', 10, 3);
function rmbz_fraud_extra_checklist( $checks, $release, $tracks ) {
    global $wpdb;

    // Check 1 — Duração razoável (>= 30s e <= 15min, limites dos DSPs)
    $duration_ok = true;
    $bad = [];
    foreach ( (array) $tracks as $t ) {
        $dur = 0;
        if ( ! empty($t->extra_data) ) {
            $e = json_decode( $t->extra_data, true );
            if ( is_array($e) && ! empty($e['duration_seconds']) ) $dur = (int) $e['duration_seconds'];
        }
        if ( $dur > 0 && ( $dur < 30 || $dur > 900 ) ) {
            $duration_ok = false;
            $bad[] = ($t->track_title ?? '?') . ' (' . $dur . 's)';
        }
    }
    $checks[] = [
        'label' => 'Duração das faixas entre 30s e 15min' . (!$duration_ok ? ' — ' . implode(', ', array_slice($bad, 0, 2)) : ''),
        'pass'  => $duration_ok,
    ];

    // Check 2 — Nenhum ISRC duplicado com outro release da BD
    $isrc_dup = false;
    $dup_list = [];
    $t_tracks = $wpdb->prefix . 'rmbz_distro_tracks';
    foreach ( (array) $tracks as $t ) {
        $isrc = trim( (string) ($t->isrc_code ?? '') );
        if ( $isrc === '' ) continue;
        $count = (int) $wpdb->get_var( $wpdb->prepare(
            "SELECT COUNT(*) FROM $t_tracks WHERE isrc_code = %s AND id != %d",
            $isrc, (int) $t->id
        ) );
        if ( $count > 0 ) {
            $isrc_dup = true;
            $dup_list[] = $isrc;
        }
    }
    $checks[] = [
        'label' => 'Nenhum ISRC duplicado na plataforma' . ($isrc_dup ? ' — duplicados: ' . implode(', ', array_slice($dup_list, 0, 2)) : ''),
        'pass'  => ! $isrc_dup,
    ];

    // Check 3 — Capa existe fisicamente e é quadrada
    $cover_ok  = false;
    $cover_msg = 'Capa não verificável';
    if ( ! empty($release->cover_image) ) {
        $path = rmbz_fraud_url_to_path( $release->cover_image );
        if ( $path && file_exists($path) ) {
            $info = @getimagesize( $path );
            if ( $info ) {
                list($w, $h) = $info;
                // Tolerância 2% como no upload handler
                if ( $w >= 1400 && $h >= 1400 && abs($w - $h) <= max($w, $h) * 0.02 ) {
                    $cover_ok = true;
                    $cover_msg = sprintf('Capa %d×%d OK', $w, $h);
                } else {
                    $cover_msg = sprintf('Capa %d×%d — deve ser ≥1400×1400 e quadrada', $w, $h);
                }
            } else {
                $cover_msg = 'Ficheiro de capa ilegível';
            }
        } else {
            $cover_msg = 'Ficheiro de capa não encontrado no servidor';
        }
    }
    $checks[] = [
        'label' => 'Capa válida (≥1400², quadrada, ficheiro existe) — ' . $cover_msg,
        'pass'  => $cover_ok,
    ];

    // Check 4 — Loudness razoável e sem clipping extremo
    // Usa a audio_info guardada no extra_data (populada pelo audio validator).
    // Só falha se tivermos dados e eles mostrarem clipping severo.
    $loudness_ok = true;
    $clip_tracks = [];
    foreach ( (array) $tracks as $t ) {
        if ( empty($t->extra_data) ) continue;
        $e = json_decode( $t->extra_data, true );
        if ( ! is_array($e) ) continue;
        $ai = $e['audio_info'] ?? [];
        // Se o validator guardou peak_dbfs > -0.1 é clipping
        if ( isset($ai['peak_dbfs']) && (float) $ai['peak_dbfs'] > -0.1 ) {
            $loudness_ok = false;
            $clip_tracks[] = ($t->track_title ?? '?');
        }
        // Bitrate demasiado baixo para MP3 (< 192 kbps)
        if ( isset($ai['format'], $ai['bitrate_kbps'])
             && $ai['format'] === 'mp3'
             && (int) $ai['bitrate_kbps'] < 192 ) {
            $loudness_ok = false;
            $clip_tracks[] = ($t->track_title ?? '?') . ' (' . (int) $ai['bitrate_kbps'] . 'kbps)';
        }
    }
    $checks[] = [
        'label' => 'Qualidade áudio adequada (sem clipping, bitrate ≥192kbps)' . (!$loudness_ok ? ' — ' . implode(', ', array_slice(array_unique($clip_tracks), 0, 2)) : ''),
        'pass'  => $loudness_ok,
    ];

    // Check 5 — Silence leading (primeiros 2s não todos mudos) — exige análise
    // do ficheiro que é cara. Só computamos se o validator guardou a info.
    // Caso contrário, não adicionamos o check (não queremos passar/falhar no
    // escuro). É por isso que este check é opcional.
    $any_silence_info = false;
    $silence_bad = [];
    foreach ( (array) $tracks as $t ) {
        if ( empty($t->extra_data) ) continue;
        $e = json_decode( $t->extra_data, true );
        if ( ! is_array($e) ) continue;
        $ai = $e['audio_info'] ?? [];
        if ( isset($ai['leading_silence_seconds']) ) {
            $any_silence_info = true;
            if ( (float) $ai['leading_silence_seconds'] > 3.0 ) {
                $silence_bad[] = ($t->track_title ?? '?') . ' (' . round($ai['leading_silence_seconds'], 1) . 's)';
            }
        }
    }
    if ( $any_silence_info ) {
        $checks[] = [
            'label' => 'Silêncio inicial ≤ 3s' . (!empty($silence_bad) ? ' — ' . implode(', ', array_slice($silence_bad, 0, 2)) : ''),
            'pass'  => empty($silence_bad),
        ];
    }

    return $checks;
}

/**
 * Patch no compute_review_checklist original para aplicar o filter.
 * Feito via wrapping — o ficheiro admin-distribution-releases.php chama
 * `rmbz_admin_compute_review_checklist`. Esta função substitui a original
 * via prioridade mais alta de include (carregada DEPOIS).
 *
 * NOTA IMPORTANTE: o admin-distribution-releases.php define a função
 * original no próprio ficheiro. Para estender sem substituir, usamos um
 * filter que ele próprio vai ter de chamar, OU intercetamos via wrapping.
 *
 * Como não podemos redefinir `rmbz_admin_compute_review_checklist` (PHP
 * reclama), a estratégia é: o ficheiro admin-distribution-releases.php
 * passa a chamar `apply_filters('rmbz_admin_review_checklist_extra', ...)`
 * depois do seu próprio cálculo, e este módulo regista-se nesse filter.
 * O patch está no próprio admin-distribution-releases.php.
 */


/* ============================================================================
 * UI — renderizador de flags para o admin colar no detail view
 * ============================================================================ */
function rmbz_fraud_render_flags_panel( $release, $tracks ) {
    $flags = rmbz_fraud_compute_flags( $release, $tracks );
    if ( empty($flags) ) {
        ?>
        <div class="rmbz-fraud-panel clean">
            <div class="rmbz-fraud-head">
                <span class="dot"></span> Análise de risco
            </div>
            <div class="rmbz-fraud-body">✓ Sem sinais suspeitos detectados automaticamente.</div>
        </div>
        <style>
        .rmbz-fraud-panel { background:#f6f7f7; border:1px solid #dee2e6; border-radius:6px; padding:10px 12px; margin:12px 0; font-size:12px; }
        .rmbz-fraud-panel.clean { border-left:3px solid #2ecc71; }
        .rmbz-fraud-head { font-size:11px; text-transform:uppercase; color:#666; letter-spacing:0.3px; margin-bottom:6px; font-weight:600; display:flex; align-items:center; gap:6px; }
        .rmbz-fraud-panel.clean .dot { width:7px; height:7px; border-radius:50%; background:#2ecc71; }
        .rmbz-fraud-body { color:#666; }
        </style>
        <?php
        return;
    }

    // Contar por severidade
    $sev_count = ['high' => 0, 'medium' => 0, 'low' => 0];
    foreach ( $flags as $f ) $sev_count[$f['severity']]++;
    $panel_class = $sev_count['high'] > 0 ? 'high' : ($sev_count['medium'] > 0 ? 'medium' : 'low');
    ?>
    <div class="rmbz-fraud-panel <?php echo esc_attr($panel_class); ?>">
        <div class="rmbz-fraud-head">
            <span class="icon">⚠️</span>
            Análise de risco —
            <?php echo count($flags); ?> sinal<?php echo count($flags) > 1 ? 'is' : ''; ?>
            <?php if ( $sev_count['high'] > 0 ): ?>
                · <strong style="color:#c0392b;"><?php echo $sev_count['high']; ?> alto</strong>
            <?php endif; ?>
            <?php if ( $sev_count['medium'] > 0 ): ?>
                · <span style="color:#e67e22;"><?php echo $sev_count['medium']; ?> médio</span>
            <?php endif; ?>
            <?php if ( $sev_count['low'] > 0 ): ?>
                · <span style="color:#888;"><?php echo $sev_count['low']; ?> baixo</span>
            <?php endif; ?>
        </div>
        <div class="rmbz-fraud-list">
            <?php foreach ( $flags as $f ): ?>
                <div class="rmbz-flag rmbz-flag-<?php echo esc_attr($f['severity']); ?>">
                    <div class="rmbz-flag-title">
                        <span class="sev-pill sev-<?php echo esc_attr($f['severity']); ?>"><?php echo strtoupper($f['severity']); ?></span>
                        <?php echo esc_html($f['label']); ?>
                    </div>
                    <div class="rmbz-flag-detail"><?php echo esc_html($f['detail']); ?></div>
                </div>
            <?php endforeach; ?>
        </div>
        <p class="rmbz-fraud-foot">
            Flags são heurísticas — <strong>não bloqueiam aprovação automaticamente</strong>.
            Servem de lembrete para o revisor humano decidir.
        </p>
    </div>
    <style>
    .rmbz-fraud-panel { background:#fffbea; border:1px solid #f0d878; border-left:3px solid #f0b90b; border-radius:6px; padding:10px 12px; margin:12px 0; font-size:12px; }
    .rmbz-fraud-panel.high { background:#fdecea; border-color:#f5b7b1; border-left-color:#e74c3c; }
    .rmbz-fraud-panel.medium { background:#fff4e5; border-color:#f6c78a; border-left-color:#e67e22; }
    .rmbz-fraud-panel.low { background:#f6f7f7; border-color:#dee2e6; border-left-color:#95a5a6; }
    .rmbz-fraud-head { font-size:11px; text-transform:uppercase; color:#666; letter-spacing:0.3px; margin-bottom:8px; font-weight:600; display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
    .rmbz-fraud-list { display:flex; flex-direction:column; gap:6px; }
    .rmbz-flag { background:#fff; border:1px solid rgba(0,0,0,0.06); border-radius:4px; padding:7px 9px; }
    .rmbz-flag-title { font-weight:600; color:#333; margin-bottom:3px; font-size:12px; }
    .rmbz-flag-detail { color:#666; font-size:11px; line-height:1.4; }
    .sev-pill { display:inline-block; padding:1px 6px; border-radius:3px; font-size:9px; font-weight:700; letter-spacing:0.5px; margin-right:6px; vertical-align:middle; }
    .sev-pill.sev-high { background:#e74c3c; color:#fff; }
    .sev-pill.sev-medium { background:#e67e22; color:#fff; }
    .sev-pill.sev-low { background:#95a5a6; color:#fff; }
    .rmbz-fraud-foot { margin:8px 0 0 0; font-size:10px; color:#888; font-style:italic; }
    </style>
    <?php
}


/* ============================================================================
 * DSP OVERRIDE — admin edita lista de DSPs de um release
 * ============================================================================ */

/**
 * Handler: adicionar/remover DSPs de um release.
 * O admin escolhe o conjunto final; este handler calcula o diff face ao
 * que estava e regista no audit log (rmbz_distro_status_history).
 */
function rmbz_admin_release_override_dsps_handler() {
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');
    $release_id = (int) ($_POST['release_id'] ?? 0);
    if ( ! $release_id ) wp_die('ID inválido.');
    check_admin_referer('rmbz_override_dsps_' . $release_id);

    global $wpdb;
    $t_plats = $wpdb->prefix . 'rmbz_distro_release_platforms';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t_plats'") !== $t_plats ) {
        wp_die('Tabela de plataformas não existe.');
    }

    $catalog = function_exists('rmbz_distro_dsp_catalog') ? rmbz_distro_dsp_catalog() : [];
    $valid_slugs = array_keys( $catalog );

    $submitted = is_array($_POST['dsps'] ?? null) ? $_POST['dsps'] : [];
    $clean = [];
    foreach ( $submitted as $slug ) {
        $slug = sanitize_key( $slug );
        if ( in_array($slug, $valid_slugs, true) ) $clean[] = $slug;
    }
    $clean = array_values( array_unique( $clean ) );
    if ( empty($clean) ) {
        wp_safe_redirect( admin_url('admin.php?page=rmbz-admin-distribution&tab=releases&view=detail&id=' . $release_id . '&msg=dsps_error') );
        exit;
    }

    // Ler estado actual para diff
    $row = $wpdb->get_row( $wpdb->prepare(
        "SELECT dsp_list FROM $t_plats WHERE release_id = %d", $release_id
    ) );
    $before = [];
    if ( $row && ! empty($row->dsp_list) ) {
        $decoded = json_decode( $row->dsp_list, true );
        if ( is_array($decoded) ) $before = $decoded;
    }

    $added   = array_values( array_diff( $clean, $before ) );
    $removed = array_values( array_diff( $before, $clean ) );

    // Upsert
    $data = [
        'dsp_list'    => wp_json_encode( $clean ),
        'updated_at'  => current_time('mysql'),
    ];
    if ( $row ) {
        $wpdb->update( $t_plats, $data, [ 'release_id' => $release_id ], [ '%s', '%s' ], [ '%d' ] );
    } else {
        $wpdb->insert( $t_plats, array_merge( $data, [
            'release_id'     => $release_id,
            'user_confirmed' => 1,
            'created_at'     => current_time('mysql'),
        ] ) );
    }

    // Audit log (diff legível)
    if ( function_exists('rmbz_admin_log_status_change') && ( !empty($added) || !empty($removed) ) ) {
        $parts = [];
        if ( $added )   $parts[] = '+ ' . implode(', ', $added);
        if ( $removed ) $parts[] = '− ' . implode(', ', $removed);
        // Não transição de estado; usamos mesmo status antes/depois mas com nota
        $current_status = $wpdb->get_var( $wpdb->prepare(
            "SELECT status FROM {$wpdb->prefix}rmbz_distro_releases WHERE id = %d", $release_id
        ) );
        rmbz_admin_log_status_change(
            $release_id,
            $current_status,
            $current_status,
            'DSPs alterados pelo admin: ' . implode(' | ', $parts)
        );
    }

    wp_safe_redirect( admin_url('admin.php?page=rmbz-admin-distribution&tab=releases&view=detail&id=' . $release_id . '&msg=dsps_saved') );
    exit;
}
add_action('admin_post_rmbz_release_override_dsps', 'rmbz_admin_release_override_dsps_handler');

/**
 * UI — editor completo de DSPs (substitui a linha "+16 mais" no detail).
 * Mostra os 24 DSPs agrupados por tier, com checkboxes, e permite guardar.
 */
function rmbz_admin_render_dsp_override_panel( $release, $platforms ) {
    if ( empty($release->id) ) return;
    if ( ! function_exists('rmbz_distro_dsp_catalog') ) return;

    $catalog  = rmbz_distro_dsp_catalog();
    $selected = [];
    if ( isset($platforms->_dsp_list) && is_array($platforms->_dsp_list) ) {
        $selected = $platforms->_dsp_list;
    }

    // Agrupar por tier
    $by_tier = [1 => [], 2 => [], 3 => []];
    foreach ( $catalog as $slug => $meta ) {
        $tier = (int) ($meta['tier'] ?? 3);
        $by_tier[$tier][$slug] = $meta;
    }

    $tier_names = [
        1 => ['Tier 1 — essenciais (cobrem >90% do mercado global)', '#2ecc71'],
        2 => ['Tier 2 — regionais e de nicho relevantes (África, MENA, Índia, US)', '#3498db'],
        3 => ['Tier 3 — Ásia, audiófilo, especialistas', '#9b59b6'],
    ];
    $rid = (int) $release->id;
    ?>
    <div class="rmbz-dsp-override">
        <div class="rmbz-dsp-head">
            <h3 style="margin:0;">🌍 Edição de lojas (DSPs) — <?php echo count($selected); ?> / <?php echo count($catalog); ?> seleccionadas</h3>
            <small style="color:#666;">O artista escolheu esta lista. Podes adicionar ou remover lojas — a alteração fica registada no histórico.</small>
        </div>

        <form method="post" action="<?php echo esc_url( admin_url('admin-post.php') ); ?>">
            <?php wp_nonce_field('rmbz_override_dsps_' . $rid); ?>
            <input type="hidden" name="action" value="rmbz_release_override_dsps">
            <input type="hidden" name="release_id" value="<?php echo $rid; ?>">

            <?php foreach ( $by_tier as $tier => $items ):
                if ( empty($items) ) continue;
                list($tier_label, $tier_color) = $tier_names[$tier];
            ?>
                <div class="rmbz-dsp-tier-block">
                    <div class="rmbz-dsp-tier-head" style="border-left:3px solid <?php echo esc_attr($tier_color); ?>;">
                        <strong><?php echo esc_html($tier_label); ?></strong>
                        <button type="button" class="button button-small rmbz-tier-toggle" data-tier="<?php echo $tier; ?>">Todos / Nenhum</button>
                    </div>
                    <div class="rmbz-dsp-grid">
                        <?php foreach ( $items as $slug => $meta ):
                            $is_sel = in_array($slug, $selected, true);
                        ?>
                            <label class="rmbz-dsp-cell <?php echo $is_sel ? 'selected' : ''; ?>" data-tier="<?php echo $tier; ?>">
                                <input type="checkbox" name="dsps[]" value="<?php echo esc_attr($slug); ?>" <?php checked($is_sel); ?>>
                                <span class="rmbz-dsp-name"><?php echo esc_html($meta['name']); ?></span>
                                <span class="rmbz-dsp-slug"><?php echo esc_html($slug); ?></span>
                            </label>
                        <?php endforeach; ?>
                    </div>
                </div>
            <?php endforeach; ?>

            <div class="rmbz-dsp-actions">
                <button type="submit" class="button button-primary">💾 Guardar selecção</button>
                <span style="margin-left:12px; color:#888; font-size:12px;">Guardar registará um evento no histórico com o diff exacto.</span>
            </div>
        </form>
    </div>

    <style>
    .rmbz-dsp-override { background:#fff; border:1px solid #ccd0d4; border-radius:4px; padding:16px; margin-top:16px; }
    .rmbz-dsp-head { margin-bottom:16px; }
    .rmbz-dsp-tier-block { margin-bottom:14px; }
    .rmbz-dsp-tier-head { padding:6px 10px; background:#f6f7f7; display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; }
    .rmbz-dsp-tier-head strong { font-size:12px; color:#333; }
    .rmbz-dsp-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); gap:4px; }
    .rmbz-dsp-cell { display:flex; align-items:center; gap:8px; padding:6px 10px; border:1px solid #e0e0e0; border-radius:3px; cursor:pointer; background:#fafafa; transition:all 0.15s; }
    .rmbz-dsp-cell:hover { background:#f0f0f0; border-color:#999; }
    .rmbz-dsp-cell.selected { background:rgba(46,204,113,0.08); border-color:#2ecc71; }
    .rmbz-dsp-cell input { margin:0; }
    .rmbz-dsp-name { flex:1; font-size:12px; font-weight:500; color:#333; }
    .rmbz-dsp-slug { font-size:10px; color:#999; font-family:monospace; }
    .rmbz-dsp-actions { margin-top:12px; padding-top:12px; border-top:1px solid #eee; }
    </style>

    <script>
    (function() {
        // Selected class segue o checkbox em tempo real
        document.querySelectorAll('.rmbz-dsp-cell input[type=checkbox]').forEach(function(cb) {
            cb.addEventListener('change', function() {
                cb.closest('.rmbz-dsp-cell').classList.toggle('selected', cb.checked);
            });
        });
        // Toggle "Todos / Nenhum" por tier
        document.querySelectorAll('.rmbz-tier-toggle').forEach(function(btn) {
            btn.addEventListener('click', function() {
                var tier = btn.getAttribute('data-tier');
                var cells = document.querySelectorAll('.rmbz-dsp-cell[data-tier="' + tier + '"] input[type=checkbox]');
                var all_checked = Array.from(cells).every(function(c) { return c.checked; });
                cells.forEach(function(c) {
                    c.checked = !all_checked;
                    c.dispatchEvent(new Event('change'));
                });
            });
        });
    })();
    </script>
    <?php
}
