<?php
/**
 * [1.5.213] App Version Control — Painel admin (REDESIGN MODERNO)
 *
 * Visual: dashboard com cards de estatísticas + form + kill switch.
 * Estatísticas em tempo real:
 *   - Online agora (últimos 5 min)
 *   - Activos 24h
 *   - Activos 7 dias
 *   - Total de dispositivos
 *   - Distribuição de versões com barras de progresso
 */

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

const RMBZ_APP_OPT_MIN_VERSION       = 'rmbz_app_min_version';
const RMBZ_APP_OPT_MIN_CODE          = 'rmbz_app_min_version_code';
const RMBZ_APP_OPT_LATEST_VERSION    = 'rmbz_app_latest_version';
const RMBZ_APP_OPT_LATEST_CODE       = 'rmbz_app_latest_version_code';
const RMBZ_APP_OPT_EMERGENCY_BLOCK   = 'rmbz_app_emergency_block';
const RMBZ_APP_OPT_FORCE_MESSAGE     = 'rmbz_app_force_message';
const RMBZ_APP_OPT_CHANGELOG         = 'rmbz_app_changelog';

function rmbz_app_version_register_menu() {
    add_submenu_page(
        'rmbz-dashboard',
        'Controlo de Versão da App',
        '📱 Versão da App',
        'manage_options',
        'rmbz-app-version',
        'rmbz_app_version_render_page'
    );
}
add_action( 'admin_menu', 'rmbz_app_version_register_menu', 90 );

function rmbz_app_version_render_page() {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( 'Sem permissões.' );
    }

    // SAVE
    if ( isset( $_POST['rmbz_app_version_save'] ) &&
         check_admin_referer( 'rmbz_app_version_save', 'rmbz_app_version_nonce' ) ) {

        // [1.5.278] wp_unslash() ANTES de sanitizar — o WordPress adiciona
        // barras (magic quotes) a todo o $_POST. Sem isto, escrever aspas no
        // changelog/mensagem gravava-as escapadas (ex.: \"Ouvir nas plataformas\").
        $posted_min_v = sanitize_text_field( wp_unslash( $_POST['min_version'] ?? '' ) );
        $posted_min_c = max( 0, (int) ( $_POST['min_version_code'] ?? 0 ) );
        $posted_lat_v = sanitize_text_field( wp_unslash( $_POST['latest_version'] ?? '' ) );
        $posted_lat_c = max( 0, (int) ( $_POST['latest_version_code'] ?? 0 ) );

        if ( $posted_min_c > 0 && $posted_lat_c > 0 && $posted_min_c > $posted_lat_c ) {
            echo '<div class="notice notice-error is-dismissible" style="margin:20px 0;"><p>⚠️ ' .
                 'Configuração não guardada: o versionCode mínimo não pode ser superior ao recomendado.</p></div>';
        } else {
            update_option( RMBZ_APP_OPT_MIN_VERSION,     $posted_min_v );
            update_option( RMBZ_APP_OPT_MIN_CODE,        $posted_min_c );
            update_option( RMBZ_APP_OPT_LATEST_VERSION,  $posted_lat_v );
            update_option( RMBZ_APP_OPT_LATEST_CODE,     $posted_lat_c );
            update_option( RMBZ_APP_OPT_EMERGENCY_BLOCK, isset( $_POST['emergency_block'] ) ? 1 : 0 );
            update_option( RMBZ_APP_OPT_FORCE_MESSAGE,   sanitize_textarea_field( wp_unslash( $_POST['force_message'] ?? '' ) ) );

            $changelog = array_values( array_filter( array_map( 'trim',
                preg_split( '/\r\n|\n|\r/', (string) wp_unslash( $_POST['changelog'] ?? '' ) )
            ) ) );
            update_option( RMBZ_APP_OPT_CHANGELOG, wp_json_encode( $changelog ) );

            echo '<div class="notice notice-success is-dismissible" style="margin:20px 0;"><p>✅ Configuração guardada.</p></div>';
        }
    }

    // [1.5.219] Upload do APK
    if ( function_exists( 'rmbz_app_handle_apk_upload' ) ) {
        rmbz_app_handle_apk_upload();
    }

    // [1.5.219] Toggle do pop-up de recomendação
    if ( isset( $_POST['rmbz_app_promo_save'] ) &&
         check_admin_referer( 'rmbz_app_promo_save', 'rmbz_app_promo_nonce' ) ) {
        update_option( RMBZ_APP_OPT_PROMO_ENABLED, isset( $_POST['promo_enabled'] ) ? 1 : 0 );
        echo '<div class="notice notice-success is-dismissible" style="margin:20px 0;"><p>✅ Definição do pop-up guardada.</p></div>';
    }

    // [1.5.221] Modo de distribuição (APK vs Play Store)
    if ( isset( $_POST['rmbz_app_dist_save'] ) &&
         check_admin_referer( 'rmbz_app_dist_save', 'rmbz_app_dist_nonce' ) ) {
        $mode = ( ( $_POST['dist_mode'] ?? '' ) === 'playstore' ) ? 'playstore' : 'apk';
        $ps_url = esc_url_raw( trim( (string) ( $_POST['playstore_url'] ?? '' ) ) );

        update_option( RMBZ_APP_OPT_DIST_MODE, $mode );
        update_option( RMBZ_APP_OPT_PLAYSTORE_URL, $ps_url );

        // Aviso se escolheu Play Store mas não meteu link
        if ( $mode === 'playstore' && empty( $ps_url ) ) {
            echo '<div class="notice notice-warning is-dismissible" style="margin:20px 0;"><p>⚠️ ' .
                 'Modo Play Store activado, mas o link está vazio. Os botões não ' .
                 'vão funcionar até preencheres o link da Play Store.</p></div>';
        } else {
            echo '<div class="notice notice-success is-dismissible" style="margin:20px 0;"><p>✅ ' .
                 'Modo de distribuição guardado: <strong>' .
                 ( $mode === 'playstore' ? 'Google Play Store' : 'APK directo' ) .
                 '</strong>.</p></div>';
        }
    }

    // KILL SWITCH
    if ( isset( $_POST['rmbz_app_kill_old'] ) &&
         check_admin_referer( 'rmbz_app_kill_old', 'rmbz_app_kill_nonce' ) ) {
        $lat_v = get_option( RMBZ_APP_OPT_LATEST_VERSION, '' );
        $lat_c = (int) get_option( RMBZ_APP_OPT_LATEST_CODE, 0 );
        if ( $lat_v && $lat_c > 0 ) {
            update_option( RMBZ_APP_OPT_MIN_VERSION, $lat_v );
            update_option( RMBZ_APP_OPT_MIN_CODE,    $lat_c );
            echo '<div class="notice notice-warning is-dismissible" style="margin:20px 0;"><p>💀 ' .
                 'Versão mínima agora é <strong>' . esc_html( $lat_v ) . '</strong>. ' .
                 'Utilizadores abaixo serão forçados a actualizar.</p></div>';
        }
    }

    // [1.5.214] REVOGAR DISPOSITIVO INDIVIDUAL
    if ( isset( $_POST['rmbz_revoke_device'] ) &&
         check_admin_referer( 'rmbz_revoke_device', 'rmbz_revoke_device_nonce' ) ) {
        $id = (int) ( $_POST['device_id'] ?? 0 );
        if ( $id > 0 ) {
            global $wpdb;

            // [1.5.215] Capturar telemovel ANTES do update (para kick total)
            $telemovel = (string) $wpdb->get_var( $wpdb->prepare(
                "SELECT user_telemovel FROM {$wpdb->prefix}rmbz_api_refresh_tokens WHERE id = %d",
                $id
            ) );

            $ok = $wpdb->update(
                $wpdb->prefix . 'rmbz_api_refresh_tokens',
                [
                    'revoked_at'    => current_time( 'mysql', 1 ),
                    'revoke_reason' => 'admin_revoke',
                ],
                [ 'id' => $id, 'revoked_at' => null ],
                [ '%s', '%s' ],
                [ '%d', '%s' ]
            );
            if ( $ok ) {
                // [1.5.215] KICK TOTAL — invalida access token de TODOS os
                // devices deste user. Razão: revogar tipicamente é por
                // suspeita; fechar tudo é a opção defensiva.
                if ( $telemovel ) {
                    // Também revogar OUTROS refresh tokens activos deste user
                    // para não precisar de mais um round-trip de admin.
                    $wpdb->query( $wpdb->prepare(
                        "UPDATE {$wpdb->prefix}rmbz_api_refresh_tokens
                         SET revoked_at = %s, revoke_reason = 'admin_revoke_cascade'
                         WHERE user_telemovel = %s AND revoked_at IS NULL",
                        current_time( 'mysql', 1 ),
                        $telemovel
                    ) );
                    // Marcar cutoff de access tokens — JWT em memória dos
                    // devices vai ser rejeitado na próxima chamada.
                    rmbz_app_token_mark_revoked( $telemovel );
                }

                echo '<div class="notice notice-success is-dismissible" style="margin:20px 0;"><p>🚪 ' .
                     'Dispositivo revogado. Todos os devices deste utilizador serão ' .
                     'desconectados na próxima acção (segundos).</p></div>';
            } else {
                echo '<div class="notice notice-warning is-dismissible" style="margin:20px 0;"><p>⚠️ ' .
                     'Dispositivo não foi alterado (já estava revogado ou não existe).</p></div>';
            }
        }
    }

    // [1.5.214] REVOGAR TODOS DE UMA VERSÃO
    if ( isset( $_POST['rmbz_revoke_version'] ) &&
         check_admin_referer( 'rmbz_revoke_version', 'rmbz_revoke_version_nonce' ) ) {
        $code = (int) ( $_POST['version_code'] ?? 0 );
        if ( $code > 0 ) {
            global $wpdb;

            // [1.5.215] Capturar lista de telemóveis afectados ANTES do update
            $affected = $wpdb->get_col( $wpdb->prepare(
                "SELECT DISTINCT user_telemovel FROM {$wpdb->prefix}rmbz_api_refresh_tokens
                 WHERE app_version_code = %d AND revoked_at IS NULL",
                $code
            ) );

            $count = (int) $wpdb->query( $wpdb->prepare(
                "UPDATE {$wpdb->prefix}rmbz_api_refresh_tokens
                 SET revoked_at = %s, revoke_reason = 'admin_bulk_revoke_version'
                 WHERE app_version_code = %d AND revoked_at IS NULL",
                current_time( 'mysql', 1 ),
                $code
            ) );

            // [1.5.215] Kick total para todos os afectados
            if ( ! empty( $affected ) ) {
                rmbz_app_token_mark_revoked_bulk( $affected );
            }

            echo '<div class="notice notice-warning is-dismissible" style="margin:20px 0;"><p>🚪 ' .
                 'Revogados <strong>' . $count . '</strong> dispositivo(s) com versionCode ' . $code .
                 '. Serão desconectados em segundos.</p></div>';
        }
    }

    // [1.5.237] REVOGAR EM MASSA — dispositivos seleccionados por checkbox.
    // Recebe device_ids[] (array de IDs). Segue o mesmo padrão defensivo
    // dos handlers acima: para cada device revogado, faz kick total do
    // utilizador (revoga todos os refresh tokens dele + marca cutoff).
    if ( isset( $_POST['rmbz_revoke_devices_bulk'] ) &&
         check_admin_referer( 'rmbz_revoke_devices_bulk', 'rmbz_revoke_devices_bulk_nonce' ) ) {

        $raw_ids = (array) ( $_POST['device_ids'] ?? [] );
        $ids     = array_values( array_unique( array_filter(
            array_map( 'intval', $raw_ids ),
            static function ( $v ) { return $v > 0; }
        ) ) );

        if ( empty( $ids ) ) {
            echo '<div class="notice notice-warning is-dismissible" style="margin:20px 0;"><p>⚠️ ' .
                 'Nenhum dispositivo seleccionado.</p></div>';
        } else {
            global $wpdb;
            $t = $wpdb->prefix . 'rmbz_api_refresh_tokens';

            // Capturar telemóveis afectados ANTES do update (para kick total).
            $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
            $affected = $wpdb->get_col( $wpdb->prepare(
                "SELECT DISTINCT user_telemovel FROM $t WHERE id IN ( $placeholders )",
                $ids
            ) );

            // Revogar os tokens seleccionados que ainda estão activos.
            $count = (int) $wpdb->query( $wpdb->prepare(
                "UPDATE $t
                 SET revoked_at = %s, revoke_reason = 'admin_bulk_revoke'
                 WHERE id IN ( $placeholders ) AND revoked_at IS NULL",
                array_merge( [ current_time( 'mysql', 1 ) ], $ids )
            ) );

            // Kick total — revoga também os restantes tokens activos de cada
            // utilizador afectado e marca o cutoff de access tokens.
            if ( ! empty( $affected ) ) {
                $aff_ph = implode( ',', array_fill( 0, count( $affected ), '%s' ) );
                $wpdb->query( $wpdb->prepare(
                    "UPDATE $t
                     SET revoked_at = %s, revoke_reason = 'admin_bulk_revoke_cascade'
                     WHERE user_telemovel IN ( $aff_ph ) AND revoked_at IS NULL",
                    array_merge( [ current_time( 'mysql', 1 ) ], $affected )
                ) );
                if ( function_exists( 'rmbz_app_token_mark_revoked_bulk' ) ) {
                    rmbz_app_token_mark_revoked_bulk( $affected );
                }
            }

            echo '<div class="notice notice-success is-dismissible" style="margin:20px 0;"><p>🚪 ' .
                 'Revogados <strong>' . $count . '</strong> dispositivo(s) seleccionado(s). ' .
                 'Os utilizadores afectados serão desconectados em segundos.</p></div>';
        }
    }

    // Search/pagination via GET
    $search   = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
    $page_num = max( 1, (int) ( $_GET['paged'] ?? 1 ) );

    // [1.5.237] Aba activa. 'versao' (default) = controlo de versões;
    // 'devices' = tabela de dispositivos com sessão.
    // [1.6.116.6] 'announcements' = avisos no app (integrado como tab).
    $active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'versao';
    if ( ! in_array( $active_tab, [ 'versao', 'devices', 'announcements' ], true ) ) {
        $active_tab = 'versao';
    }

    // Load values
    $min_v   = get_option( RMBZ_APP_OPT_MIN_VERSION, '' );
    $min_c   = (int) get_option( RMBZ_APP_OPT_MIN_CODE, 0 );
    $lat_v   = get_option( RMBZ_APP_OPT_LATEST_VERSION, '' );
    $lat_c   = (int) get_option( RMBZ_APP_OPT_LATEST_CODE, 0 );
    $emerg   = (int) get_option( RMBZ_APP_OPT_EMERGENCY_BLOCK, 0 );
    $force_m = (string) get_option( RMBZ_APP_OPT_FORCE_MESSAGE, '' );
    $changelog_json = (string) get_option( RMBZ_APP_OPT_CHANGELOG, '[]' );
    $changelog_arr = json_decode( $changelog_json, true );
    if ( ! is_array( $changelog_arr ) ) $changelog_arr = [];
    $changelog_text = implode( "\n", $changelog_arr );

    $stats = function_exists( 'rmbz_app_telemetry_stats' )
        ? rmbz_app_telemetry_stats()
        : [ 'online_now'=>0,'active_24h'=>0,'active_7d'=>0,'total_devices'=>0,'version_breakdown'=>[] ];

    // Diagnóstico operacional da publicação.
    $dist_mode_diag = function_exists( 'rmbz_app_dist_mode' ) ? rmbz_app_dist_mode() : 'apk';
    $playstore_diag = (string) get_option( 'rmbz_app_playstore_url', '' );
    $apk_url_diag   = (string) get_option( 'rmbz_app_apk_url', '' );
    $known_users = $recommended_users = $soft_update_users = $force_update_users = 0;
    foreach ( (array) $stats['version_breakdown'] as $version_row ) {
        $count = (int) ( $version_row['count'] ?? 0 );
        $code  = (int) ( $version_row['code'] ?? 0 );
        if ( $code <= 0 ) continue;
        $known_users += $count;
        if ( $lat_c > 0 && $code >= $lat_c ) {
            $recommended_users += $count;
        } elseif ( $min_c > 0 && $code < $min_c ) {
            $force_update_users += $count;
        } elseif ( $lat_c > 0 && $code < $lat_c ) {
            $soft_update_users += $count;
        }
    }
    $adoption_pct = $known_users > 0 ? round( ( $recommended_users / $known_users ) * 100, 1 ) : 0;
    $config_issues = [];
    if ( $lat_c <= 0 || $lat_v === '' ) $config_issues[] = 'Define a versão recomendada e o respetivo versionCode.';
    if ( $min_c <= 0 || $min_v === '' ) $config_issues[] = 'Define a versão mínima obrigatória.';
    if ( $lat_c > 0 && $min_c > $lat_c ) $config_issues[] = 'O versionCode mínimo não pode ser superior ao recomendado.';
    if ( $dist_mode_diag === 'playstore' && $playstore_diag === '' ) $config_issues[] = 'O modo Play Store está ativo, mas o link está vazio.';
    if ( $dist_mode_diag === 'apk' && $apk_url_diag === '' ) $config_issues[] = 'O modo APK direto está ativo, mas ainda não existe APK carregado.';
    if ( empty( $changelog_arr ) ) $config_issues[] = 'O changelog está vazio.';
    $release_health = empty( $config_issues ) ? 'ready' : ( count( $config_issues ) >= 3 ? 'danger' : 'warning' );

    // [1.5.214] Carregar lista paginada de dispositivos
    $devices_data = rmbz_app_load_devices( $search, $page_num, 20 );

    ?>
    <style>
        .rmbz-app-page { max-width: none; margin: 18px 18px 40px 0; }
        .rmbz-app-page * { box-sizing: border-box; }
        .rmbz-app-page h1 { font-size: 28px; font-weight: 800; margin: 0 0 8px; color: #1d1d1f; }
        .rmbz-app-page .subtitle { color: #6e6e73; font-size: 14px; margin: 0; }
        .rmbz-app-hero {
            padding:22px 24px; margin-bottom:18px; border:1px solid #e5e7eb; border-radius:16px;
            background:#fff; box-shadow:0 4px 14px rgba(17,24,39,.04);
        }
        .rmbz-app-hero h1 { color:#111827; }
        .rmbz-app-hero .subtitle { color:#6b7280; max-width:780px; line-height:1.6; }
        .rmbz-app-hero-note {
            display:inline-flex;align-items:center;gap:7px;margin-top:12px;padding:6px 10px;
            border-radius:8px;background:#f3f4f6;color:#4b5563;font-size:11px;font-weight:600;
        }
        .rmbz-app-feedback {
            margin:0 0 16px;padding:13px 15px;border:1px solid #a7f3d0;border-left:4px solid #10b981;
            border-radius:10px;background:#ecfdf5;color:#065f46;font-size:13px;font-weight:600;
        }

        .rmbz-stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 24px; }
        .rmbz-stat-card {
            background: #fff; border: 1px solid #e5e5ea; border-radius: 14px; padding: 18px 16px;
            position: relative; overflow: hidden;
        }
        .rmbz-stat-card .label {
            font-size: 11px; font-weight: 600; text-transform: uppercase;
            letter-spacing: 0.5px; color: #6e6e73; margin: 0 0 6px;
        }
        .rmbz-stat-card .value { font-size: 32px; font-weight: 700; color: #1d1d1f; line-height: 1; }
        .rmbz-stat-card .sublabel { font-size: 11px; color: #8e8e93; margin-top: 4px; }
        .rmbz-stat-card .icon {
            position: absolute; top: 14px; right: 14px;
            width: 36px; height: 36px; border-radius: 50%;
            display: flex; align-items: center; justify-content: center;
            font-size: 18px;
        }

        .rmbz-stat-online { border-left: 4px solid #34c759; }
        .rmbz-stat-online .icon { background: #e4f8ea; }
        .rmbz-stat-online .pulse {
            display: inline-block; width: 10px; height: 10px;
            background: #34c759; border-radius: 50%;
            margin-right: 8px; vertical-align: middle;
            animation: rmbzPulse 1.6s infinite;
            box-shadow: 0 0 0 0 rgba(52,199,89,0.7);
        }
        @keyframes rmbzPulse {
            0% { box-shadow: 0 0 0 0 rgba(52,199,89,0.7); }
            70% { box-shadow: 0 0 0 10px rgba(52,199,89,0); }
            100% { box-shadow: 0 0 0 0 rgba(52,199,89,0); }
        }

        .rmbz-stat-24h { border-left: 4px solid #007aff; }
        .rmbz-stat-24h .icon { background: #e3f0ff; }
        .rmbz-stat-7d { border-left: 4px solid #af52de; }
        .rmbz-stat-7d .icon { background: #f4e6fb; }
        .rmbz-stat-total { border-left: 4px solid #ff9500; }
        .rmbz-stat-total .icon { background: #fff0d9; }
        .rmbz-release-health {
            display:grid; grid-template-columns:1.2fr repeat(3,1fr); gap:14px; margin-bottom:20px;
        }
        .rmbz-health-main,.rmbz-impact-card {
            background:#fff;border:1px solid #e5e7eb;border-radius:16px;padding:18px;
        }
        .rmbz-health-main.ready { border-left:5px solid #10b981; }
        .rmbz-health-main.warning { border-left:5px solid #f59e0b; }
        .rmbz-health-main.danger { border-left:5px solid #ef4444; }
        .rmbz-health-main h3 { margin:0 0 5px;font-size:15px;color:#111827; }
        .rmbz-health-main p { margin:0;color:#6b7280;font-size:12px;line-height:1.5; }
        .rmbz-health-main ul { margin:10px 0 0;padding-left:18px;color:#92400e;font-size:11px;line-height:1.55; }
        .rmbz-impact-card .k { color:#6b7280;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px; }
        .rmbz-impact-card .v { margin-top:6px;color:#111827;font-size:25px;font-weight:800;line-height:1; }
        .rmbz-impact-card .s { margin-top:6px;color:#9ca3af;font-size:11px; }
        .rmbz-impact-card.good { background:#f0fdf4;border-color:#bbf7d0; }
        .rmbz-impact-card.warn { background:#fffbeb;border-color:#fde68a; }
        .rmbz-impact-card.bad { background:#fef2f2;border-color:#fecaca; }

        .rmbz-body { display: grid; grid-template-columns: minmax(0,1fr) 390px; gap: 20px; }
        @media (max-width: 900px) { .rmbz-body { grid-template-columns: 1fr; } }

        .rmbz-card {
            background: #fff; border: 1px solid #e5e5ea; border-radius: 14px;
            padding: 24px; margin-bottom: 20px;
        }
        .rmbz-card-title {
            font-size: 16px; font-weight: 700; color: #1d1d1f;
            margin: 0 0 6px; display: flex; align-items: center; gap: 10px;
        }
        .rmbz-card-subtitle { color: #6e6e73; font-size: 12px; margin: 0 0 20px; }
        .rmbz-divider { height: 1px; background: #f0f0f5; margin: 20px 0; border: 0; }

        .rmbz-form-row { margin-bottom: 18px; }
        .rmbz-form-row label.lbl {
            display: block; font-size: 13px; font-weight: 600;
            color: #1d1d1f; margin-bottom: 6px;
        }
        .rmbz-form-row .hint { font-size: 11px; color: #8e8e93; margin-top: 4px; }
        .rmbz-form-row input[type="text"],
        .rmbz-form-row input[type="number"],
        .rmbz-form-row textarea {
            width: 100%; padding: 10px 12px; border: 1px solid #d1d1d6;
            border-radius: 8px; font-size: 14px; background: #fff;
            transition: border-color 0.15s, box-shadow 0.15s;
            font-family: inherit;
        }
        .rmbz-form-row input:focus, .rmbz-form-row textarea:focus {
            outline: none; border-color: #007aff;
            box-shadow: 0 0 0 3px rgba(0,122,255,0.15);
        }
        .rmbz-form-row textarea { resize: vertical; }
        .rmbz-form-row.split { display: grid; grid-template-columns: 1fr 130px; gap: 10px; }

        .rmbz-check {
            display: flex; align-items: flex-start; gap: 10px;
            padding: 14px; background: #fff8f1; border: 1px solid #ffd599;
            border-radius: 10px; cursor: pointer; transition: background 0.15s;
        }
        .rmbz-check:hover { background: #ffefdf; }
        .rmbz-check input[type="checkbox"] {
            margin: 2px 0 0; width: 18px; height: 18px; cursor: pointer;
        }
        .rmbz-check .ctitle { font-weight: 600; color: #1d1d1f; font-size: 13px; }
        .rmbz-check .cdesc { font-size: 12px; color: #6e6e73; margin-top: 3px; }

        .rmbz-btn {
            display: inline-flex; align-items: center; gap: 8px;
            padding: 10px 18px; border-radius: 10px; font-size: 14px;
            font-weight: 600; cursor: pointer; transition: all 0.15s;
            text-decoration: none; border: 0; line-height: 1;
        }
        .rmbz-btn-primary { background: #007aff; color: #fff; }
        .rmbz-btn-primary:hover { background: #0066d6; color: #fff; }
        .rmbz-btn-danger { background: #ff3b30; color: #fff; }
        .rmbz-btn-danger:hover { background: #d70015; color: #fff; }
        .rmbz-btn-sm {
            padding: 6px 12px; font-size: 12px; border-radius: 8px;
        }
        .rmbz-btn-ghost {
            background: transparent; color: #6e6e73;
            border: 1px solid #d1d1d6;
        }
        .rmbz-btn-ghost:hover { background: #f5f5f7; color: #1d1d1f; }
        .rmbz-btn-revoke-mini {
            background: #fef0ef; color: #d70015; border: 1px solid #ffd5d2;
            padding: 4px 10px; font-size: 11px; border-radius: 6px;
            font-weight: 600; cursor: pointer; transition: all 0.15s;
        }
        .rmbz-btn-revoke-mini:hover { background: #ff3b30; color: #fff; border-color: #ff3b30; }
        .rmbz-btn-revoke-version {
            background: transparent; color: #ff3b30; border: 0;
            padding: 3px 8px; font-size: 10px; cursor: pointer;
            border-radius: 5px; font-weight: 600;
        }
        .rmbz-btn-revoke-version:hover { background: #ff3b30; color: #fff; }

        /* [1.5.237] Abas de navegação */
        .rmbz-tabs {
            margin: 0; padding:6px; border:1px solid #e5e7eb; background:#fff;
            border-radius:14px 14px 0 0; display:flex;gap:5px;
        }
        .rmbz-tabs .nav-tab {
            margin:0;border:0;border-radius:9px;background:transparent;padding:10px 16px;
            color:#6b7280;font-weight:700;
        }
        .rmbz-tabs .nav-tab-active { background:#111827;color:#fff; }
        .rmbz-tab-panel { margin-top: 0; }

        /* [1.5.237] Barra de revogação em massa */
        .rmbz-bulk-bar {
            display: flex; align-items: center; justify-content: space-between;
            gap: 12px; flex-wrap: wrap;
            padding: 10px 12px; margin-bottom: 12px;
            background: #fafafa; border: 1px solid #ededf0; border-radius: 8px;
        }
        .rmbz-bulk-select-all {
            display: flex; align-items: center; gap: 8px;
            font-size: 13px; color: #3c3c43; cursor: pointer;
        }
        .rmbz-btn-bulk-revoke {
            background: #ff3b30; color: #fff; border: 0;
            padding: 7px 14px; font-size: 12px; font-weight: 600;
            border-radius: 6px; cursor: pointer;
        }
        .rmbz-btn-bulk-revoke:hover:not(:disabled) { background: #d70015; }
        .rmbz-btn-bulk-revoke:disabled {
            background: #e5e5ea; color: #aeaeb2; cursor: not-allowed;
        }
        .rmbz-table th.cell-check,
        .rmbz-table td.cell-check {
            width: 36px; text-align: center; padding-left: 12px;
        }

        /* [1.5.238] Coluna Artista + selo VIP */
        .rmbz-table .cell-artist { color: #1d1d1f; }
        .rmbz-table .cell-artist-unknown {
            color: #aeaeb2; font-style: italic; font-size: 12px;
        }
        .rmbz-vip-badge {
            display: inline-block; padding: 3px 9px;
            border-radius: 11px; font-size: 11px; font-weight: 700;
            white-space: nowrap;
        }
        .rmbz-vip-badge.vip {
            background: #fff4d6; color: #9a6b00;
            border: 1px solid #f0d281;
        }
        .rmbz-vip-badge.free {
            background: #f0f0f5; color: #8e8e93;
            border: 1px solid #e2e2e7;
        }

        /* ===== Devices table ===== */
        .rmbz-devices-card { margin-top: 20px; }
        .rmbz-search-wrap {
            position: relative; margin-bottom: 16px;
        }
        .rmbz-search-input {
            width: 100%; padding: 10px 14px 10px 38px;
            border: 1px solid #d1d1d6; border-radius: 10px;
            font-size: 14px; background: #fff;
            transition: border-color 0.15s, box-shadow 0.15s;
        }
        .rmbz-search-input:focus {
            outline: none; border-color: #007aff;
            box-shadow: 0 0 0 3px rgba(0,122,255,0.15);
        }
        .rmbz-search-icon {
            position: absolute; left: 12px; top: 50%;
            transform: translateY(-50%); color: #8e8e93;
            pointer-events: none; font-size: 16px;
        }

        .rmbz-table {
            width: 100%; border-collapse: collapse; font-size: 13px;
        }
        .rmbz-table th {
            text-align: left; padding: 10px 12px; font-weight: 600;
            color: #6e6e73; font-size: 11px; text-transform: uppercase;
            letter-spacing: 0.4px; border-bottom: 1px solid #e5e5ea;
            background: #fafafa;
        }
        .rmbz-table th:first-child { border-top-left-radius: 8px; }
        .rmbz-table th:last-child { border-top-right-radius: 8px; text-align: right; }
        .rmbz-table td {
            padding: 12px; border-bottom: 1px solid #f0f0f5;
            color: #1d1d1f; vertical-align: middle;
        }
        .rmbz-table tr:last-child td { border-bottom: 0; }
        .rmbz-table tr:hover td { background: #fafafa; }
        .rmbz-table .cell-tel { font-weight: 600; }
        .rmbz-table .cell-version {
            display: inline-block; padding: 3px 8px; border-radius: 6px;
            font-size: 11px; font-weight: 600;
            background: #e8f4ff; color: #007aff;
        }
        .rmbz-table .cell-version.ok { background: #e4f8ea; color: #1f7b3e; }
        .rmbz-table .cell-version.outdated { background: #fff0d9; color: #b85f00; }
        .rmbz-table .cell-version.danger { background: #ffe5e0; color: #c41100; }
        .rmbz-table .cell-version.unknown { background: #f0f0f5; color: #6e6e73; }
        .rmbz-table .cell-device {
            color: #6e6e73; font-size: 12px;
        }
        .rmbz-table .cell-when {
            color: #6e6e73; font-size: 12px; white-space: nowrap;
        }
        .rmbz-table .cell-when.fresh { color: #1f7b3e; font-weight: 600; }
        .rmbz-table .cell-actions { text-align: right; }

        .rmbz-empty-table {
            text-align: center; padding: 40px 20px; color: #8e8e93;
            font-size: 13px;
        }

        .rmbz-pagination {
            display: flex; justify-content: space-between; align-items: center;
            margin-top: 14px; padding-top: 14px;
            border-top: 1px solid #f0f0f5; font-size: 12px;
            color: #6e6e73;
        }
        .rmbz-pagination .links { display: flex; gap: 6px; }
        .rmbz-pagination a, .rmbz-pagination span.current {
            padding: 5px 10px; border-radius: 6px; text-decoration: none;
            color: #007aff; font-weight: 500;
        }
        .rmbz-pagination a:hover { background: #f5f5f7; }
        .rmbz-pagination span.current {
            background: #007aff; color: #fff;
        }
        .rmbz-pagination .disabled { color: #c7c7cc; padding: 5px 10px; }

        .rmbz-version-list { display: flex; flex-direction: column; gap: 12px; }
        .rmbz-version-row { font-size: 13px; }
        .rmbz-version-head {
            display: flex; justify-content: space-between; margin-bottom: 5px;
            align-items: center;
        }
        .rmbz-version-name { font-weight: 600; color: #1d1d1f; }
        .rmbz-version-code { font-size: 10px; color: #8e8e93; margin-left: 4px; }
        .rmbz-version-count { color: #6e6e73; font-size: 12px; }
        .rmbz-bar-track {
            width: 100%; height: 8px; background: #f0f0f5;
            border-radius: 50px; overflow: hidden;
        }
        .rmbz-bar-fill {
            height: 100%; border-radius: 50px;
            background: linear-gradient(90deg, #34c759, #5ed47a);
            transition: width 0.6s ease-out;
        }
        .rmbz-bar-fill.outdated { background: linear-gradient(90deg, #ff9500, #ffcc00); }
        .rmbz-bar-fill.danger { background: linear-gradient(90deg, #ff3b30, #ff6450); }
        .rmbz-empty {
            text-align: center; padding: 30px 20px; color: #8e8e93;
            font-size: 13px; border: 1px dashed #d1d1d6; border-radius: 10px;
        }

        .rmbz-kill {
            background: linear-gradient(135deg, #fff5f5 0%, #ffe5e0 100%);
            border: 2px solid #ff3b30; border-radius: 14px; padding: 24px;
        }
        .rmbz-kill h2 {
            color: #d70015; margin: 0 0 10px; font-size: 18px;
            display: flex; align-items: center; gap: 8px;
        }
        .rmbz-kill p { color: #4a4a4f; margin: 0 0 16px; font-size: 13px; line-height: 1.5; }

        .rmbz-legend { list-style: none; padding: 0; margin: 0; }
        .rmbz-legend li {
            display: flex; gap: 12px; padding: 8px 0;
            font-size: 13px; color: #4a4a4f; line-height: 1.4;
        }
        .rmbz-legend .dot {
            display: inline-block; width: 10px; height: 10px; border-radius: 50%;
            margin-top: 5px; flex-shrink: 0;
        }
        .rmbz-legend .dot-ok { background: #34c759; }
        .rmbz-legend .dot-soft { background: #ff9500; }
        .rmbz-legend .dot-force { background: #ff3b30; }
        .rmbz-legend .dot-block { background: #8e8e93; }
        .rmbz-legend code {
            background: #f0f0f5; padding: 2px 6px; border-radius: 4px;
            font-size: 11px; font-family: 'SF Mono', Menlo, monospace;
        }

        .rmbz-preview {
            background: #1d1d1f; color: #d1d1d6; border-radius: 10px;
            padding: 16px; font-family: 'SF Mono', Menlo, monospace;
            font-size: 11px; overflow-x: auto;
        }
        .rmbz-preview .url { color: #5ac8fa; word-break: break-all; margin-bottom: 10px; }
        .rmbz-preview pre { margin: 0; white-space: pre-wrap; color: #d1d1d6; }

        /* [1.5.219] APK upload section */
        .rmbz-apk-admin {
            background: #fff; border-radius: 14px; padding: 20px;
            margin-top: 16px; border: 1px solid #e5e5e5;
        }
        .rmbz-apk-admin h2 { margin: 0 0 6px; font-size: 16px; }
        .rmbz-apk-status {
            background: #f0f9f0; border-radius: 10px; padding: 12px 14px;
            margin: 12px 0; font-size: 13px;
        }
        .rmbz-apk-status.rmbz-apk-empty { background: #fff8e8; color: #8a6d00; }
        .rmbz-apk-status ul { margin: 8px 0; padding-left: 18px; }
        .rmbz-apk-badge {
            display: inline-block; background: #34c759; color: #fff;
            padding: 2px 10px; border-radius: 50px; font-size: 11px; font-weight: 600;
        }
        .rmbz-apk-file-label {
            display: block; margin: 12px 0;
        }
        .rmbz-apk-file-label input[type=file] {
            font-size: 13px; width: 100%;
        }

        /* [1.5.221] Opções de modo de distribuição */
        .rmbz-dist-opt {
            display: flex; align-items: flex-start; gap: 10px;
            padding: 12px; margin: 8px 0;
            border: 1.5px solid #e5e5e5; border-radius: 12px;
            cursor: pointer; transition: border-color 0.15s;
        }
        .rmbz-dist-opt:hover { border-color: #DAA520; }
        .rmbz-dist-opt input[type=radio] { margin-top: 3px; }
        .rmbz-dist-opt strong { font-size: 14px; }
        .rmbz-dist-opt small { color: #888; font-size: 12px; }
        @media (max-width: 1100px) {
            .rmbz-release-health { grid-template-columns:repeat(2,1fr); }
        }
        @media (max-width: 700px) {
            .rmbz-stats-grid,.rmbz-release-health { grid-template-columns:1fr; }
            .rmbz-tabs { overflow-x:auto; }
            .rmbz-tabs .nav-tab { white-space:nowrap; }
        }
    </style>

    <div class="wrap rmbz-app-page">
        <div class="rmbz-app-hero">
            <h1>📱 Gestão e Atualizações da App</h1>
            <p class="subtitle">Defina a versão disponível, obrigue uma atualização quando necessário e envie avisos aos utilizadores.</p>
            <span class="rmbz-app-hero-note">Os detalhes técnicos e possíveis problemas aparecem no diagnóstico abaixo.</span>
        </div>

        <!-- STATS CARDS -->
        <div class="rmbz-stats-grid">
            <div class="rmbz-stat-card rmbz-stat-online">
                <div class="icon">🟢</div>
                <div class="label">Online agora</div>
                <div class="value">
                    <span class="pulse"></span><?php echo (int) $stats['online_now']; ?>
                </div>
                <div class="sublabel">activos nos últimos 5 min</div>
            </div>
            <div class="rmbz-stat-card rmbz-stat-24h">
                <div class="icon">📊</div>
                <div class="label">Activos hoje</div>
                <div class="value"><?php echo (int) $stats['active_24h']; ?></div>
                <div class="sublabel">últimas 24 horas</div>
            </div>
            <div class="rmbz-stat-card rmbz-stat-7d">
                <div class="icon">📅</div>
                <div class="label">Esta semana</div>
                <div class="value"><?php echo (int) $stats['active_7d']; ?></div>
                <div class="sublabel">últimos 7 dias</div>
            </div>
            <div class="rmbz-stat-card rmbz-stat-total">
                <div class="icon">📲</div>
                <div class="label">Dispositivos</div>
                <div class="value"><?php echo (int) $stats['total_devices']; ?></div>
                <div class="sublabel">com sessão activa</div>
            </div>
        </div>

        <div class="rmbz-release-health">
            <div class="rmbz-health-main <?php echo esc_attr( $release_health ); ?>">
                <h3><?php echo $release_health === 'ready' ? '✓ Publicação configurada corretamente' : '⚠ Diagnóstico da publicação'; ?></h3>
                <p>
                    Recomendada: <strong><?php echo esc_html( $lat_v ?: 'não definida' ); ?></strong>
                    (code <?php echo (int) $lat_c; ?>) · mínima:
                    <strong><?php echo esc_html( $min_v ?: 'não definida' ); ?></strong>
                    (code <?php echo (int) $min_c; ?>).
                </p>
                <?php if ( ! empty( $config_issues ) ): ?>
                    <ul>
                        <?php foreach ( $config_issues as $issue ): ?>
                            <li><?php echo esc_html( $issue ); ?></li>
                        <?php endforeach; ?>
                    </ul>
                <?php endif; ?>
            </div>
            <div class="rmbz-impact-card good">
                <div class="k">Adoção recomendada</div>
                <div class="v"><?php echo esc_html( $adoption_pct ); ?>%</div>
                <div class="s"><?php echo (int) $recommended_users; ?> de <?php echo (int) $known_users; ?> utilizadores conhecidos</div>
            </div>
            <div class="rmbz-impact-card warn">
                <div class="k">Atualização suave</div>
                <div class="v"><?php echo (int) $soft_update_users; ?></div>
                <div class="s">abaixo da recomendada, mas ainda suportados</div>
            </div>
            <div class="rmbz-impact-card bad">
                <div class="k">Atualização obrigatória</div>
                <div class="v"><?php echo (int) $force_update_users; ?></div>
                <div class="s">abaixo da versão mínima configurada</div>
            </div>
        </div>

        <?php
        // [1.5.237] Barra de abas. Mantém s/paged fora dos links para não
        // arrastar pesquisas/páginas de uma aba para a outra.
        $tab_base = remove_query_arg( [ 's', 'paged' ], add_query_arg( 'page', 'rmbz-app-version', admin_url( 'admin.php' ) ) );
        $tabs = [
            'versao'        => '⚙️ Versão & Actualizações',
            'devices'       => '📲 Dispositivos',
            'announcements' => '📣 Avisos no App',
        ];
        ?>
        <h2 class="nav-tab-wrapper rmbz-tabs">
            <?php foreach ( $tabs as $tab_key => $tab_label ) :
                $is_active = ( $active_tab === $tab_key );
                $tab_url   = esc_url( add_query_arg( 'tab', $tab_key, $tab_base ) );
            ?>
                <a href="<?php echo $tab_url; ?>"
                   class="nav-tab <?php echo $is_active ? 'nav-tab-active' : ''; ?>">
                    <?php echo esc_html( $tab_label ); ?>
                </a>
            <?php endforeach; ?>
        </h2>

        <?php /* ================= ABA: VERSÃO & ACTUALIZAÇÕES ================= */ ?>
        <div class="rmbz-tab-panel" <?php echo $active_tab === 'versao' ? '' : 'style="display:none;"'; ?>>
        <div class="rmbz-body">

            <!-- LEFT COL -->
            <div>
                <form method="post" class="rmbz-card">
                    <?php wp_nonce_field( 'rmbz_app_version_save', 'rmbz_app_version_nonce' ); ?>

                    <h2 class="rmbz-card-title">⚙️ Configurar versões</h2>
                    <p class="rmbz-card-subtitle">Define a versão recomendada e a mínima obrigatória.</p>

                    <div class="rmbz-form-row split">
                        <div>
                            <label class="lbl">Versão recomendada</label>
                            <input type="text" name="latest_version" placeholder="1.6.99"
                                   value="<?php echo esc_attr( $lat_v ); ?>" />
                            <div class="hint">String visível ao user</div>
                        </div>
                        <div>
                            <label class="lbl">versionCode</label>
                            <input type="number" name="latest_version_code" min="0" placeholder="199"
                                   value="<?php echo esc_attr( $lat_c ); ?>" />
                            <div class="hint">do build.gradle</div>
                        </div>
                    </div>

                    <div class="rmbz-form-row split">
                        <div>
                            <label class="lbl">Versão mínima obrigatória</label>
                            <input type="text" name="min_version" placeholder="1.6.95"
                                   value="<?php echo esc_attr( $min_v ); ?>" />
                            <div class="hint">Abaixo: ecrã bloqueante (force update)</div>
                        </div>
                        <div>
                            <label class="lbl">versionCode</label>
                            <input type="number" name="min_version_code" min="0" placeholder="195"
                                   value="<?php echo esc_attr( $min_c ); ?>" />
                            <div class="hint">&nbsp;</div>
                        </div>
                    </div>

                    <hr class="rmbz-divider" />

                    <h2 class="rmbz-card-title">🚨 Emergência</h2>
                    <p class="rmbz-card-subtitle">Para quando há bug grave ou problema de segurança.</p>

                    <div class="rmbz-form-row">
                        <label class="rmbz-check">
                            <input type="checkbox" name="emergency_block" value="1"
                                   <?php checked( $emerg, 1 ); ?> />
                            <div>
                                <div class="ctitle">🚨 Bloquear LOGIN para versões abaixo da mínima</div>
                                <div class="cdesc">
                                    Além do ecrã de force update, o backend rejeita auth
                                    de versões antigas (HTTP 426), impedindo qualquer uso.
                                </div>
                            </div>
                        </label>
                    </div>

                    <div class="rmbz-form-row">
                        <label class="lbl" for="force_message">Mensagem do force update</label>
                        <textarea id="force_message" name="force_message" rows="3"
                                  placeholder="Atualização importante. Por favor instala a versão mais recente."><?php
                            echo esc_textarea( $force_m );
                        ?></textarea>
                        <div class="hint">Mostrado no ecrã cheio. Deixa vazio para default.</div>
                    </div>

                    <hr class="rmbz-divider" />

                    <h2 class="rmbz-card-title">📝 Changelog</h2>
                    <p class="rmbz-card-subtitle">Mostrado no banner e no ecrã de force update.</p>

                    <div class="rmbz-form-row">
                        <textarea name="changelog" rows="6"
                                  placeholder="✨ Novos relatórios mensais
🐛 Correção de bugs nas estatísticas
⚡ Melhor performance"><?php echo esc_textarea( $changelog_text ); ?></textarea>
                        <div class="hint">Uma linha por item</div>
                    </div>

                    <button type="submit" name="rmbz_app_version_save" class="rmbz-btn rmbz-btn-primary">
                        💾 Guardar configuração
                    </button>
                </form>

                <?php
                // [1.5.219] Secção de gestão do APK
                $apk_url      = get_option( RMBZ_APP_OPT_APK_URL, '' );
                $apk_size     = (int) get_option( RMBZ_APP_OPT_APK_SIZE, 0 );
                $apk_uploaded = get_option( RMBZ_APP_OPT_APK_UPLOADED_AT, '' );
                $apk_downloads= (int) get_option( RMBZ_APP_OPT_APK_DOWNLOADS, 0 );
                $promo_on     = (int) get_option( RMBZ_APP_OPT_PROMO_ENABLED, 1 );
                // [1.5.221] Modo de distribuição
                $dist_mode    = rmbz_app_dist_mode();
                $ps_url       = get_option( RMBZ_APP_OPT_PLAYSTORE_URL, '' );
                ?>

                <form method="post" class="rmbz-apk-admin">
                    <?php wp_nonce_field( 'rmbz_app_dist_save', 'rmbz_app_dist_nonce' ); ?>
                    <h2>🚀 Modo de distribuição</h2>
                    <p style="color:#6e6e73;font-size:13px;">
                        Escolhe para onde os utilizadores são enviados ao clicar
                        em "baixar app" (na página de download e no pop-up).
                    </p>

                    <label class="rmbz-dist-opt">
                        <input type="radio" name="dist_mode" value="apk"
                               <?php checked( $dist_mode, 'apk' ); ?>>
                        <span>
                            <strong>📦 APK directo</strong><br>
                            <small>App ainda não está na Play Store. Download do .apk com modal + desafio.</small>
                        </span>
                    </label>

                    <label class="rmbz-dist-opt">
                        <input type="radio" name="dist_mode" value="playstore"
                               <?php checked( $dist_mode, 'playstore' ); ?>>
                        <span>
                            <strong>▶ Google Play Store</strong><br>
                            <small>App já publicada. Botões redireccionam directamente para a Play Store.</small>
                        </span>
                    </label>

                    <label style="display:block;margin-top:14px;font-size:13px;font-weight:600;">
                        Link da Play Store
                    </label>
                    <input type="url" name="playstore_url" class="regular-text"
                           style="width:100%;margin-top:4px;"
                           placeholder="https://play.google.com/store/apps/details?id=com.onemzrecords.artists"
                           value="<?php echo esc_attr( $ps_url ); ?>">
                    <p style="color:#999;font-size:11px;margin-top:4px;">
                        Cola aqui o link da app na Play Store. Necessário para o
                        modo "Google Play Store" funcionar.
                    </p>

                    <button type="submit" name="rmbz_app_dist_save" class="rmbz-btn rmbz-btn-primary" style="margin-top:8px;">
                        💾 Guardar modo
                    </button>
                </form>

                <form method="post" enctype="multipart/form-data" class="rmbz-apk-admin">
                    <?php wp_nonce_field( 'rmbz_app_apk_upload', 'rmbz_app_apk_nonce' ); ?>
                    <h2>📦 APK para download directo</h2>
                    <p style="color:#6e6e73;font-size:13px;">
                        <?php if ( $dist_mode === 'playstore' ) : ?>
                            <em>Modo Play Store activo — o APK aqui carregado não está
                            a ser usado de momento. Continua disponível caso voltes
                            ao modo APK.</em>
                        <?php else : ?>
                            Enquanto a app está em verificação na Play Store, os artistas
                            podem baixar o APK directamente. Carrega aqui o ficheiro .apk
                            gerado no Android Studio.
                        <?php endif; ?>
                    </p>

                    <?php if ( $apk_url ) : ?>
                        <div class="rmbz-apk-status">
                            <span class="rmbz-apk-badge">✅ APK disponível</span>
                            <ul>
                                <li><strong>Tamanho:</strong> <?php echo esc_html( size_format( $apk_size ) ); ?></li>
                                <li><strong>Carregado:</strong> <?php echo esc_html( $apk_uploaded ? date_i18n( 'd/m/Y H:i', strtotime( $apk_uploaded ) ) : '—' ); ?></li>
                                <li><strong>Transferências:</strong> <?php echo number_format_i18n( $apk_downloads ); ?></li>
                            </ul>
                            <a href="<?php echo esc_url( home_url( '/?rmbz_download_apk=1' ) ); ?>"
                               class="rmbz-btn" target="_blank" style="font-size:12px;">
                                ⬇ Testar download
                            </a>
                        </div>
                    <?php else : ?>
                        <div class="rmbz-apk-status rmbz-apk-empty">
                            ⚠️ Ainda não há APK carregado. A página de download mostra
                            "em breve" até carregares um ficheiro.
                        </div>
                    <?php endif; ?>

                    <label class="rmbz-apk-file-label">
                        <input type="file" name="apk_file" accept=".apk" required>
                    </label>
                    <button type="submit" name="rmbz_app_apk_upload" class="rmbz-btn rmbz-btn-primary">
                        ⬆ Carregar APK
                    </button>
                    <p style="color:#999;font-size:11px;margin-top:8px;">
                        O APK substitui o anterior. A versão e o changelog vêm dos
                        campos acima (Versão recomendada + Changelog).
                    </p>
                </form>

                <form method="post" class="rmbz-apk-admin">
                    <?php wp_nonce_field( 'rmbz_app_promo_save', 'rmbz_app_promo_nonce' ); ?>
                    <h2>🔔 Pop-up de recomendação</h2>
                    <p style="color:#6e6e73;font-size:13px;">
                        Mostra um pop-up no fundo do ecrã a recomendar a app, apenas
                        nas páginas oficiais (Minha Conta, Área de Artista, Distribuição).
                        O artista pode fechar — só reaparece após 1 dia.
                    </p>
                    <label style="display:flex;align-items:center;gap:8px;font-size:14px;">
                        <input type="checkbox" name="promo_enabled" value="1" <?php checked( $promo_on, 1 ); ?>>
                        Mostrar pop-up de recomendação
                    </label>
                    <button type="submit" name="rmbz_app_promo_save" class="rmbz-btn rmbz-btn-primary" style="margin-top:12px;">
                        💾 Guardar
                    </button>
                    <p style="color:#999;font-size:11px;margin-top:8px;">
                        O pop-up só aparece se houver um APK carregado.
                    </p>
                </form>

                <form method="post" class="rmbz-kill">
                    <?php wp_nonce_field( 'rmbz_app_kill_old', 'rmbz_app_kill_nonce' ); ?>
                    <h2>💀 Kill Switch</h2>
                    <p>
                        Marca a versão recomendada actual
                        (<strong><?php echo esc_html( $lat_v ?: '—' ); ?></strong>)
                        como <em>mínima</em>. Todos os utilizadores em versões anteriores
                        serão imediatamente forçados a actualizar.
                    </p>
                    <button type="submit" name="rmbz_app_kill_old" class="rmbz-btn rmbz-btn-danger"
                            onclick="return confirm('Tens a certeza? Utilizadores abaixo da versão <?php echo esc_js( $lat_v ?: '?' ); ?> serão forçados a actualizar.');">
                        💀 Matar versões abaixo de <?php echo esc_html( $lat_v ?: '?' ); ?>
                    </button>
                </form>
            </div>

            <!-- RIGHT COL -->
            <div>
                <!-- VERSION DISTRIBUTION -->
                <div class="rmbz-card">
                    <h2 class="rmbz-card-title">📊 Versões instaladas</h2>
                    <p class="rmbz-card-subtitle">Distribuição entre utilizadores activos (30d)</p>

                    <?php if ( empty( $stats['version_breakdown'] ) ) : ?>
                        <div class="rmbz-empty">
                            Sem dados ainda.<br>
                            Após alguns artistas usarem a app, esta distribuição aparece
                            aqui automaticamente.
                        </div>
                    <?php else : ?>
                        <div class="rmbz-version-list">
                            <?php foreach ( $stats['version_breakdown'] as $v ) :
                                $is_recommended = $lat_c > 0 && $v['code'] === $lat_c;
                                $is_below_min   = $min_c > 0 && $v['code'] < $min_c;
                                $is_below_lat   = $lat_c > 0 && $v['code'] < $lat_c;
                                $bar_class = 'updated';
                                if ( $is_below_min ) $bar_class = 'danger';
                                elseif ( $is_below_lat ) $bar_class = 'outdated';
                            ?>
                                <div class="rmbz-version-row">
                                    <div class="rmbz-version-head">
                                        <div>
                                            <span class="rmbz-version-name">
                                                <?php echo esc_html( $v['version'] ); ?>
                                                <?php if ( $is_recommended ) : ?>
                                                    <span style="color:#34c759;font-size:11px;font-weight:500;">✓ recomendada</span>
                                                <?php elseif ( $is_below_min ) : ?>
                                                    <span style="color:#ff3b30;font-size:11px;font-weight:500;">✗ bloqueada</span>
                                                <?php endif; ?>
                                            </span>
                                            <span class="rmbz-version-code">code <?php echo (int) $v['code']; ?></span>
                                        </div>
                                        <div class="rmbz-version-count">
                                            <strong style="color:#1d1d1f;"><?php echo (int) $v['count']; ?></strong>
                                            <span>· <?php echo $v['pct']; ?>%</span>
                                            <?php if ( $v['code'] > 0 ) : ?>
                                                <form method="post" style="display:inline; margin-left:6px;">
                                                    <?php wp_nonce_field( 'rmbz_revoke_version', 'rmbz_revoke_version_nonce' ); ?>
                                                    <input type="hidden" name="version_code" value="<?php echo (int) $v['code']; ?>" />
                                                    <button type="submit" name="rmbz_revoke_version"
                                                            class="rmbz-btn-revoke-version"
                                                            title="Revogar todos os dispositivos com esta versão"
                                                            onclick="return confirm('Revogar TODOS os <?php echo (int) $v['count']; ?> dispositivo(s) na versão <?php echo esc_js( $v['version'] ); ?>?\n\nVão ter de fazer login de novo.');">
                                                        🚪 revogar
                                                    </button>
                                                </form>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                    <div class="rmbz-bar-track">
                                        <div class="rmbz-bar-fill <?php echo esc_attr( $bar_class ); ?>"
                                             style="width: <?php echo esc_attr( $v['pct'] ); ?>%;"></div>
                                    </div>
                                </div>
                            <?php endforeach; ?>
                        </div>
                    <?php endif; ?>
                </div>

                <div class="rmbz-card">
                    <h2 class="rmbz-card-title">ℹ️ Como funciona</h2>
                    <ul class="rmbz-legend">
                        <li>
                            <span class="dot dot-ok"></span>
                            <span><code>versionCode >= recomendada</code><br>
                            <span style="color:#8e8e93;">App abre normal.</span></span>
                        </li>
                        <li>
                            <span class="dot dot-soft"></span>
                            <span><code>< recomendada</code> mas <code>>= mínima</code><br>
                            <span style="color:#8e8e93;">Banner suave a sugerir actualizar.</span></span>
                        </li>
                        <li>
                            <span class="dot dot-force"></span>
                            <span><code>< mínima</code><br>
                            <span style="color:#8e8e93;">Ecrã cheio bloqueante (Play In-App Update).</span></span>
                        </li>
                        <li>
                            <span class="dot dot-block"></span>
                            <span><strong>Emergência:</strong><br>
                            <span style="color:#8e8e93;">Backend rejeita LOGIN (HTTP 426).</span></span>
                        </li>
                    </ul>
                </div>

                <div class="rmbz-card">
                    <h2 class="rmbz-card-title">🔍 Preview da resposta</h2>
                    <p class="rmbz-card-subtitle">Endpoint público (sem auth)</p>
                    <div class="rmbz-preview">
                        <div class="url">GET <?php echo esc_html(
                            rest_url( '1mz/v1/app/version-check?platform=android&version_code=190' )
                        ); ?></div>
                        <pre><?php
                            $sample = rmbz_app_version_build_response( 190, 'android' );
                            echo esc_html( wp_json_encode( $sample,
                                JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE ) );
                        ?></pre>
                    </div>
                </div>
            </div>
        </div>
        </div><?php /* fim rmbz-body */ ?>

        <?php /* ===================== ABA: DISPOSITIVOS ===================== */ ?>
        <div class="rmbz-tab-panel" <?php echo $active_tab === 'devices' ? '' : 'style="display:none;"'; ?>>
        <!-- ========== DEVICES TABLE (full width) ========== -->
        <div class="rmbz-card rmbz-devices-card">
            <h2 class="rmbz-card-title">📲 Dispositivos com sessão</h2>
            <p class="rmbz-card-subtitle">
                <?php
                printf(
                    '%d dispositivo(s) activos · revoga individual ou em massa',
                    (int) $devices_data['total']
                );
                ?>
            </p>

            <form method="get" class="rmbz-search-wrap">
                <input type="hidden" name="page" value="rmbz-app-version" />
                <input type="hidden" name="tab" value="devices" />
                <span class="rmbz-search-icon">🔍</span>
                <input type="text" name="s" id="rmbz-search-input"
                       class="rmbz-search-input"
                       value="<?php echo esc_attr( $search ); ?>"
                       placeholder="Pesquisar por número de telemóvel (ex: 258...)..." />
            </form>

            <?php if ( empty( $devices_data['rows'] ) ) : ?>
                <div class="rmbz-empty-table">
                    <?php if ( $search !== '' ) : ?>
                        Nenhum dispositivo encontrado para "<strong><?php echo esc_html( $search ); ?></strong>".
                    <?php else : ?>
                        Ainda não há dispositivos registados.
                    <?php endif; ?>
                </div>
            <?php else : ?>
                <form method="post" id="rmbz-bulk-form">
                <?php wp_nonce_field( 'rmbz_revoke_devices_bulk', 'rmbz_revoke_devices_bulk_nonce' ); ?>

                <div class="rmbz-bulk-bar">
                    <label class="rmbz-bulk-select-all">
                        <input type="checkbox" id="rmbz-check-all" />
                        Seleccionar todos nesta página
                    </label>
                    <button type="submit" name="rmbz_revoke_devices_bulk"
                            id="rmbz-bulk-revoke-btn"
                            class="rmbz-btn-bulk-revoke" disabled
                            onclick="return rmbzConfirmBulkRevoke();">
                        🚪 Revogar seleccionados (<span id="rmbz-bulk-count">0</span>)
                    </button>
                </div>

                <table class="rmbz-table">
                    <thead>
                        <tr>
                            <th class="cell-check"></th>
                            <th>Telemóvel</th>
                            <th>Artista</th>
                            <th>VIP</th>
                            <th>Versão</th>
                            <th>Dispositivo</th>
                            <th>Último uso</th>
                            <th>Acções</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ( $devices_data['rows'] as $d ) :
                            // Classificar versão
                            $v_class = 'unknown';
                            $v_code = (int) ( $d->app_version_code ?? 0 );
                            if ( $v_code > 0 && $lat_c > 0 ) {
                                if ( $v_code >= $lat_c ) $v_class = 'ok';
                                elseif ( $min_c > 0 && $v_code < $min_c ) $v_class = 'danger';
                                else $v_class = 'outdated';
                            }

                            // Tempo desde último uso
                            $last_ts = $d->last_used_at ? strtotime( $d->last_used_at . ' UTC' ) : 0;
                            $now_ts  = time();
                            $diff    = $last_ts > 0 ? max( 0, $now_ts - $last_ts ) : -1;
                            $fresh   = $diff >= 0 && $diff < 5 * MINUTE_IN_SECONDS;
                            $when_label = rmbz_app_format_time_ago( $diff );

                            // [1.5.238] Nome do artista e estado VIP (via JOIN).
                            $artist_nome = isset( $d->artist_nome ) && $d->artist_nome !== ''
                                ? $d->artist_nome
                                : '';
                            // vip_until = data futura mais distante, ou NULL.
                            $is_vip = ! empty( $d->vip_until );
                        ?>
                            <tr>
                                <td class="cell-check">
                                    <input type="checkbox"
                                           class="rmbz-device-check"
                                           name="device_ids[]"
                                           value="<?php echo (int) $d->id; ?>" />
                                </td>
                                <td class="cell-tel"><?php echo esc_html( $d->user_telemovel ); ?></td>
                                <td class="cell-artist">
                                    <?php if ( $artist_nome !== '' ) : ?>
                                        <?php echo esc_html( $artist_nome ); ?>
                                    <?php else : ?>
                                        <span class="cell-artist-unknown">— sem registo —</span>
                                    <?php endif; ?>
                                </td>
                                <td class="cell-vip">
                                    <?php if ( $is_vip ) : ?>
                                        <span class="rmbz-vip-badge vip">★ VIP</span>
                                    <?php else : ?>
                                        <span class="rmbz-vip-badge free">Não-VIP</span>
                                    <?php endif; ?>
                                </td>
                                <td>
                                    <span class="cell-version <?php echo esc_attr( $v_class ); ?>">
                                        <?php
                                        $v_label = $d->app_version ?: ( $v_code > 0 ? "code $v_code" : '—' );
                                        echo esc_html( $v_label );
                                        ?>
                                    </span>
                                </td>
                                <td class="cell-device">
                                    <?php echo esc_html( $d->device_label ?: '—' ); ?>
                                </td>
                                <td class="cell-when <?php echo $fresh ? 'fresh' : ''; ?>">
                                    <?php echo esc_html( $when_label ); ?>
                                </td>
                                <td class="cell-actions">
                                    <?php
                                    // [1.5.237] Form de revogação individual movido
                                    // para FORA da tabela (ver abaixo do </table>).
                                    // O botão liga-se a ele via atributo form= —
                                    // HTML não permite <form> dentro de <form>.
                                    $rev_form_id = 'rmbz-rev-' . (int) $d->id;
                                    ?>
                                    <button type="submit" name="rmbz_revoke_device"
                                            form="<?php echo esc_attr( $rev_form_id ); ?>"
                                            class="rmbz-btn-revoke-mini"
                                            onclick="return confirm('Revogar sessão de <?php echo esc_js( $d->user_telemovel ); ?>?\n\nO utilizador terá de fazer login de novo.');">
                                        🚪 Revogar
                                    </button>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>

                <?php
                /* [1.5.237] Forms de revogação individual — um por dispositivo,
                 * colocados FORA da <table> e FORA do form de massa para não
                 * haver aninhamento de forms. Cada botão da coluna "Acções"
                 * liga-se ao seu via atributo form=. */
                foreach ( $devices_data['rows'] as $d ) : ?>
                    <form method="post" id="rmbz-rev-<?php echo (int) $d->id; ?>" style="display:none;">
                        <?php wp_nonce_field( 'rmbz_revoke_device', 'rmbz_revoke_device_nonce' ); ?>
                        <input type="hidden" name="device_id" value="<?php echo (int) $d->id; ?>" />
                    </form>
                <?php endforeach; ?>

                <?php if ( $devices_data['total_pages'] > 1 ) : ?>
                    <div class="rmbz-pagination">
                        <div>
                            Página <?php echo (int) $devices_data['page']; ?>
                            de <?php echo (int) $devices_data['total_pages']; ?>
                            <span style="color:#c7c7cc;">·</span>
                            <?php echo (int) $devices_data['total']; ?> dispositivos
                        </div>
                        <div class="links">
                            <?php
                            // [1.5.237] $base mantém tab=devices para a
                            // paginação não saltar de volta para a aba de versão.
                            $base = add_query_arg( 'tab', 'devices', remove_query_arg( 'paged' ) );
                            $cur  = (int) $devices_data['page'];
                            $tot  = (int) $devices_data['total_pages'];

                            if ( $cur > 1 ) {
                                printf( '<a href="%s">‹ Anterior</a>',
                                    esc_url( add_query_arg( 'paged', $cur - 1, $base ) ) );
                            } else {
                                echo '<span class="disabled">‹ Anterior</span>';
                            }
                            // Páginas (limita a 5 ao redor)
                            $start_p = max( 1, $cur - 2 );
                            $end_p   = min( $tot, $cur + 2 );
                            for ( $i = $start_p; $i <= $end_p; $i++ ) {
                                if ( $i === $cur ) {
                                    printf( '<span class="current">%d</span>', $i );
                                } else {
                                    printf( '<a href="%s">%d</a>',
                                        esc_url( add_query_arg( 'paged', $i, $base ) ), $i );
                                }
                            }
                            if ( $cur < $tot ) {
                                printf( '<a href="%s">Próxima ›</a>',
                                    esc_url( add_query_arg( 'paged', $cur + 1, $base ) ) );
                            } else {
                                echo '<span class="disabled">Próxima ›</span>';
                            }
                            ?>
                        </div>
                    </div>
                <?php endif; ?>
                </form><?php /* fim #rmbz-bulk-form */ ?>
            <?php endif; ?>
        </div>
        </div><?php /* fim .rmbz-tab-panel (aba devices) */ ?>

        <?php /* ================= ABA: AVISOS NO APP ================= */ ?>
        <div class="rmbz-tab-panel" <?php echo $active_tab === 'announcements' ? '' : 'style="display:none;"'; ?>>
            <?php
            // [1.6.116.6] Integração da feature "Avisos no App" como tab.
            // As funções vêm de includes/announcements/announcements-admin.php.

            // Notice de sucesso/erro (passada via query string pelos handlers)
            if ( isset( $_GET['ann_msg'] ) ) {
                $ann_msg_map = [
                    'created'    => 'Aviso enviado para a app com sucesso.',
                    'updated'    => 'Alterações do aviso guardadas com sucesso.',
                    'deleted'    => 'Aviso eliminado com sucesso.',
                    'toggled'    => 'Estado do aviso alterado com sucesso.',
                    'duplicated' => 'Aviso duplicado. A cópia ficou inativa até estar pronta.',
                ];
                $ann_msg = $ann_msg_map[ sanitize_key( $_GET['ann_msg'] ) ] ?? '';
                if ( $ann_msg ) {
                    echo '<div class="rmbz-app-feedback">✓ ' . esc_html( $ann_msg ) . '</div>';
                }
            }

            if ( function_exists( 'rmbz_announcements_render_styles' ) ) {
                rmbz_announcements_render_styles();
            }

            $ann_view = isset( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'list';
            if ( $ann_view === 'edit' || $ann_view === 'new' ) {
                $ann_id = (int) ( $_GET['id'] ?? 0 );
                if ( function_exists( 'rmbz_announcements_render_form' ) ) {
                    rmbz_announcements_render_form( $ann_id );
                }
            } elseif ( $ann_view === 'stats' ) {
                $ann_id = (int) ( $_GET['id'] ?? 0 );
                if ( function_exists( 'rmbz_announcements_render_stats' ) ) {
                    rmbz_announcements_render_stats( $ann_id );
                }
            } else {
                if ( function_exists( 'rmbz_announcements_render_list' ) ) {
                    rmbz_announcements_render_list();
                }
            }
            ?>
        </div><?php /* fim .rmbz-tab-panel (aba announcements) */ ?>

        <script>
            // [1.5.214] Debounce na pesquisa — submit auto após 400ms sem digitar
            (function() {
                var input = document.getElementById('rmbz-search-input');
                if ( ! input ) return;
                var t;
                input.addEventListener('input', function() {
                    clearTimeout(t);
                    t = setTimeout(function() {
                        input.form.submit();
                    }, 400);
                });
            })();

            // [1.5.237] Revogação em massa — checkboxes + contador + "todos".
            (function() {
                var checkAll = document.getElementById('rmbz-check-all');
                var btn      = document.getElementById('rmbz-bulk-revoke-btn');
                var countEl  = document.getElementById('rmbz-bulk-count');
                if ( ! btn || ! countEl ) return;

                var boxes = function() {
                    return Array.prototype.slice.call(
                        document.querySelectorAll('.rmbz-device-check')
                    );
                };
                function refresh() {
                    var n = boxes().filter(function(b) { return b.checked; }).length;
                    countEl.textContent = n;
                    btn.disabled = ( n === 0 );
                    if ( checkAll ) {
                        var all = boxes();
                        checkAll.checked = ( all.length > 0 &&
                            n === all.length );
                    }
                }
                if ( checkAll ) {
                    checkAll.addEventListener('change', function() {
                        boxes().forEach(function(b) {
                            b.checked = checkAll.checked;
                        });
                        refresh();
                    });
                }
                boxes().forEach(function(b) {
                    b.addEventListener('change', refresh);
                });
                refresh();
            })();

            // [1.5.237] Confirmação do botão de revogação em massa.
            function rmbzConfirmBulkRevoke() {
                var n = document.querySelectorAll('.rmbz-device-check:checked').length;
                if ( n === 0 ) { return false; }
                return confirm(
                    'Revogar ' + n + ' dispositivo(s) seleccionado(s)?\n\n' +
                    'Os utilizadores afectados terão de fazer login de novo.'
                );
            }
        </script>
    </div>
    <?php
}

/* ============================================================================
 * [1.5.214] DEVICES — query paginada
 * ============================================================================ */

/**
 * Carrega dispositivos paginados, com pesquisa opcional por telemóvel.
 * [1.5.238] Cada linha traz também artist_nome (rmbz_wallets) e
 * vip_until (rmbz_subscriptions; data futura mais distante ou NULL).
 *
 * @param string $search    Texto livre para LIKE em user_telemovel
 * @param int    $page      Página actual (>= 1)
 * @param int    $per_page  Items por página
 * @return array {
 *     @type array $rows        Linhas (objects) da página
 *     @type int   $total       Total de dispositivos (após filtro)
 *     @type int   $total_pages Páginas totais
 *     @type int   $page        Página actual normalizada
 * }
 */
function rmbz_app_load_devices( $search = '', $page = 1, $per_page = 20 ) {
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_api_refresh_tokens';

    if ( $wpdb->get_var( "SHOW TABLES LIKE '$t'" ) !== $t ) {
        return [ 'rows' => [], 'total' => 0, 'total_pages' => 0, 'page' => 1 ];
    }

    $per_page = max( 5, min( 100, (int) $per_page ) );
    $page = max( 1, (int) $page );
    $offset = ( $page - 1 ) * $per_page;

    // Cláusula WHERE — só sessões activas.
    // [1.5.238] Colunas qualificadas com $t. porque o rows_sql passou a
    // ter JOIN — sem o prefixo, user_telemovel seria ambíguo.
    $where = "WHERE $t.revoked_at IS NULL AND $t.expires_at > NOW()";
    $where_params = [];

    if ( $search !== '' ) {
        $where .= " AND $t.user_telemovel LIKE %s";
        $where_params[] = '%' . $wpdb->esc_like( $search ) . '%';
    }

    // Total (count) — sem JOIN, conta só linhas de $t.
    $count_sql = "SELECT COUNT(*) FROM $t $where";
    if ( $where_params ) {
        $count_sql = $wpdb->prepare( $count_sql, $where_params );
    }
    $total = (int) $wpdb->get_var( $count_sql );
    $total_pages = (int) ceil( $total / $per_page );

    // Verificar se as colunas de versão existem (auto-migration pode não ter corrido)
    $cols = (array) $wpdb->get_col( "SHOW COLUMNS FROM $t" );
    $select_av  = in_array( 'app_version', $cols, true ) ? "$t.app_version" : 'NULL AS app_version';
    $select_avc = in_array( 'app_version_code', $cols, true ) ? "$t.app_version_code" : 'NULL AS app_version_code';

    // [1.5.238] JOIN para trazer nome do artista e estado VIP.
    //  - Nome: rmbz_wallets.user_nome (a "conta" do artista).
    //  - VIP : existe linha em rmbz_subscriptions com end_date > NOW().
    // Match de telemóvel: directo, com fallback pelos últimos 9 dígitos
    // (apanha o caso de um lado ter +258 e o outro não — ver secção 8.2
    // do Handoff). Tabelas defensivamente verificadas antes de juntar.
    $t_wallets = $wpdb->prefix . 'rmbz_wallets';
    $t_subs    = $wpdb->prefix . 'rmbz_subscriptions';
    $has_wallets = ( $wpdb->get_var( "SHOW TABLES LIKE '$t_wallets'" ) === $t_wallets );
    $has_subs    = ( $wpdb->get_var( "SHOW TABLES LIKE '$t_subs'" ) === $t_subs );

    $tel_match = "( w.user_telemovel = $t.user_telemovel
                    OR RIGHT( w.user_telemovel, 9 ) = RIGHT( $t.user_telemovel, 9 ) )";
    $sub_match = "( s.user_telemovel = $t.user_telemovel
                    OR RIGHT( s.user_telemovel, 9 ) = RIGHT( $t.user_telemovel, 9 ) )";

    if ( $has_wallets ) {
        $select_nome = 'w.user_nome AS artist_nome';
        $join_wallet = "LEFT JOIN $t_wallets w ON $tel_match";
    } else {
        $select_nome = 'NULL AS artist_nome';
        $join_wallet = '';
    }
    if ( $has_subs ) {
        // MAX(end_date) futuro → VIP activo; usamos subquery correlacionada
        // para não multiplicar linhas se houver várias subscrições.
        $select_vip  = "( SELECT MAX( s.end_date ) FROM $t_subs s
                          WHERE $sub_match AND s.end_date > NOW() ) AS vip_until";
    } else {
        $select_vip  = 'NULL AS vip_until';
    }

    // Rows
    $rows_sql = "SELECT $t.id, $t.user_telemovel, $t.device_label, $t.ip_address,
                        $select_av, $select_avc,
                        $t.created_at, $t.last_used_at, $t.expires_at,
                        $select_nome,
                        $select_vip
                 FROM $t
                 $join_wallet
                 $where
                 ORDER BY $t.last_used_at DESC, $t.created_at DESC
                 LIMIT %d OFFSET %d";
    $rows_params = array_merge( $where_params, [ $per_page, $offset ] );
    $rows = $wpdb->get_results( $wpdb->prepare( $rows_sql, $rows_params ) );

    return [
        'rows'        => $rows ?: [],
        'total'       => $total,
        'total_pages' => $total_pages,
        'page'        => $page,
    ];
}

/**
 * Formata diferença em segundos para uma string PT-PT amigável.
 *
 *  -1   → "—"
 *  < 5min   → "agora"
 *  < 1h     → "há X min"
 *  < 24h    → "há X h"
 *  < 7d     → "há X d"
 *  >= 7d    → "há X sem"
 */
function rmbz_app_format_time_ago( $secs ) {
    if ( $secs < 0 ) return '—';
    if ( $secs < 5 * MINUTE_IN_SECONDS ) return 'agora';
    if ( $secs < HOUR_IN_SECONDS )     return 'há ' . floor( $secs / MINUTE_IN_SECONDS ) . ' min';
    if ( $secs < DAY_IN_SECONDS )      return 'há ' . floor( $secs / HOUR_IN_SECONDS ) . ' h';
    if ( $secs < WEEK_IN_SECONDS )     return 'há ' . floor( $secs / DAY_IN_SECONDS ) . ' d';
    return 'há ' . floor( $secs / WEEK_IN_SECONDS ) . ' sem';
}

/* ============================================================================
 * HELPERS — usado pelo endpoint e pelo preview
 * ============================================================================ */

function rmbz_app_version_build_response( $client_code, $platform = 'android' ) {
    $min_v     = (string) get_option( RMBZ_APP_OPT_MIN_VERSION, '' );
    $min_c     = (int)    get_option( RMBZ_APP_OPT_MIN_CODE, 0 );
    $lat_v     = (string) get_option( RMBZ_APP_OPT_LATEST_VERSION, '' );
    $lat_c     = (int)    get_option( RMBZ_APP_OPT_LATEST_CODE, 0 );
    $emerg     = (int)    get_option( RMBZ_APP_OPT_EMERGENCY_BLOCK, 0 );
    $force_msg = (string) get_option( RMBZ_APP_OPT_FORCE_MESSAGE, '' );
    $changelog_json = (string) get_option( RMBZ_APP_OPT_CHANGELOG, '[]' );
    $changelog = json_decode( $changelog_json, true );
    if ( ! is_array( $changelog ) ) $changelog = [];

    $status = 'ok';
    if ( $min_c > 0 && $client_code < $min_c ) {
        $status = $emerg ? 'blocked' : 'force_update';
    } elseif ( $lat_c > 0 && $client_code < $lat_c ) {
        $status = 'soft_update';
    }

    $store_url = ( $platform === 'ios' )
        ? 'https://apps.apple.com/app/1mz-for-artists/id0000000000'
        : ( function_exists( 'rmbz_app_get_install_url' )
            ? rmbz_app_get_install_url()
            : 'https://play.google.com/store/apps/details?id=com.onemzrecords.artists' );

    return [
        'status'              => $status,
        'latest_version'      => $lat_v,
        'latest_version_code' => $lat_c,
        'min_version'         => $min_v,
        'min_version_code'    => $min_c,
        'emergency'           => (bool) $emerg,
        'store_url'           => $store_url,
        'force_message'       => $force_msg ?: 'Esta versão da app já não é suportada. Por favor instala a versão mais recente.',
        'changelog'           => $changelog,
    ];
}

function rmbz_app_version_should_block_auth( $client_code ) {
    $emerg = (int) get_option( RMBZ_APP_OPT_EMERGENCY_BLOCK, 0 );
    if ( ! $emerg ) return false;
    $min_c = (int) get_option( RMBZ_APP_OPT_MIN_CODE, 0 );
    if ( $min_c <= 0 ) return false;
    if ( $client_code <= 0 ) return false;
    return $client_code < $min_c;
}
