<?php
/**
 * includes/distribution/distribution-store-requests.php
 *
 * ============================================================================
 * SISTEMA DE GESTÃO DE LOJAS (DSPs) — 1.5.144
 * ============================================================================
 *
 * Implementa duas funcionalidades novas relacionadas:
 *
 * 1. CATÁLOGO DE LOJAS GERIDO PELO ADMIN (rmbz_dsp_catalog)
 *    - Tabela BD com built-in (importadas no install) + custom (admin adiciona)
 *    - Toggle activo/inactivo para cada loja
 *    - Editar nome, tier, slug
 *    - Função rmbz_distro_dsp_catalog() refactorizada para fazer merge:
 *      lê primeiro da BD, cai no built-in se BD vazia (back-compat)
 *
 * 2. PEDIDOS DE ALTERAÇÃO DE LOJAS (rmbz_store_change_requests)
 *    - Artista pede para adicionar ou remover lojas duma release LIVE
 *    - 1ª alteração ao longo da vida do release = grátis (configurável)
 *    - Subsequentes = só ADIÇÕES contam para o cálculo de custo
 *      (remoções são sempre grátis)
 *    - Admin aprova/rejeita; aprovação debita saldo (se aplicável) e
 *      aplica via rmbz_release_override_dsps existente
 *    - Status: pending | approved | rejected | cancelled
 *
 * SETTINGS:
 *   rmbz_store_change_fee_subsequent  (int MT, default 100)  — custo após 1ª alteração
 *   rmbz_store_changes_enabled        (bool, default 1)      — toggle global
 */

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


// ============================================================================
// SCHEMA
// ============================================================================

function rmbz_store_changes_table() {
    global $wpdb;
    return $wpdb->prefix . 'rmbz_store_change_requests';
}

function rmbz_dsp_catalog_table() {
    global $wpdb;
    return $wpdb->prefix . 'rmbz_dsp_catalog';
}


/**
 * Instala/migra schema. Idempotente — só corre dbDelta se a versão de
 * schema gravada em option for diferente da actual.
 *
 * Hooked em admin_init/init para ser executada após plugin load.
 */
function rmbz_store_requests_maybe_install() {
    global $wpdb;
    /* [1.5.164] Bump schema 1.0 → 1.1: nova coluna logo_url
       (URL para imagem custom — Media Library ou URL externa).
       dbDelta detecta a coluna nova e faz ALTER TABLE automaticamente. */
    $current_v = '1.1';
    if ( get_option('rmbz_store_requests_schema_v') === $current_v ) return;

    $charset_collate = $wpdb->get_charset_collate();

    // Tabela 1: Catálogo de DSPs gerido (built-in + custom)
    $cat_table = rmbz_dsp_catalog_table();
    $sql1 = "CREATE TABLE $cat_table (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        slug varchar(64) NOT NULL,
        name varchar(120) NOT NULL,
        tier tinyint(1) DEFAULT 2 NOT NULL,
        is_active tinyint(1) DEFAULT 1 NOT NULL,
        is_builtin tinyint(1) DEFAULT 0 NOT NULL,
        sort_order int(11) DEFAULT 0 NOT NULL,
        notes text DEFAULT NULL,
        logo_url varchar(500) DEFAULT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY  (id),
        UNIQUE KEY uk_slug (slug),
        KEY idx_active (is_active),
        KEY idx_tier (tier)
    ) ENGINE=InnoDB $charset_collate;";

    // Tabela 2: Pedidos de alteração de lojas
    $req_table = rmbz_store_changes_table();
    $sql2 = "CREATE TABLE $req_table (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        release_id bigint(20) NOT NULL,
        requester_telemovel varchar(20) NOT NULL,
        current_dsps_snapshot longtext NOT NULL,
        proposed_dsps longtext NOT NULL,
        added_dsps longtext NOT NULL,
        removed_dsps longtext NOT NULL,
        is_first_change tinyint(1) DEFAULT 0 NOT NULL,
        fee_amount decimal(10,2) DEFAULT 0.00 NOT NULL,
        fee_charged tinyint(1) DEFAULT 0 NOT NULL,
        status varchar(20) DEFAULT 'pending' NOT NULL,
        admin_note text DEFAULT NULL,
        admin_actor varchar(80) DEFAULT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        decided_at datetime DEFAULT NULL,
        PRIMARY KEY  (id),
        KEY idx_release (release_id),
        KEY idx_requester (requester_telemovel),
        KEY idx_status (status)
    ) ENGINE=InnoDB $charset_collate;";

    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql1 );
    dbDelta( $sql2 );

    // Importar built-in DSPs uma vez. Em re-installs, ON DUPLICATE preserva
    // toggles/edições do admin (slug é UNIQUE).
    rmbz_store_seed_builtin_catalog();

    update_option('rmbz_store_requests_schema_v', $current_v);
}
add_action('admin_init', 'rmbz_store_requests_maybe_install', 8);
add_action('init',        'rmbz_store_requests_maybe_install', 9);


/**
 * Importa o catálogo built-in (definido em distribution-platforms.php) para
 * a tabela rmbz_dsp_catalog. Usa INSERT IGNORE para não sobrescrever
 * personalizações que o admin tenha feito em re-installs.
 */
function rmbz_store_seed_builtin_catalog() {
    global $wpdb;
    if ( ! function_exists('rmbz_distro_dsp_catalog_builtin') ) {
        // Função renomeada — ver mais abaixo. Se ainda não existe, abortar.
        return;
    }
    $builtin = rmbz_distro_dsp_catalog_builtin();
    $table = rmbz_dsp_catalog_table();
    $sort = 0;
    foreach ( $builtin as $slug => $meta ) {
        $sort++;
        $wpdb->query( $wpdb->prepare(
            "INSERT IGNORE INTO $table (slug, name, tier, is_active, is_builtin, sort_order, created_at)
             VALUES (%s, %s, %d, 1, 1, %d, %s)",
            $slug, $meta['name'], (int) ($meta['tier'] ?? 2), $sort, current_time('mysql')
        ) );
    }
}


// ============================================================================
// CATÁLOGO — QUERIES E HELPERS
// ============================================================================

/**
 * Devolve catálogo combinado built-in + custom da BD.
 *
 * @param bool $only_active Se true, só lojas com is_active=1
 * @return array slug => ['name', 'tier']
 */
function rmbz_store_get_catalog( $only_active = true ) {
    global $wpdb;
    $table = rmbz_dsp_catalog_table();

    // Se a tabela ainda não existe (install pendente), cair no built-in
    if ( $wpdb->get_var("SHOW TABLES LIKE '$table'") !== $table ) {
        if ( function_exists('rmbz_distro_dsp_catalog_builtin') ) {
            return rmbz_distro_dsp_catalog_builtin();
        }
        return [];
    }

    $where = $only_active ? "WHERE is_active = 1" : "";
    $rows = $wpdb->get_results(
        "SELECT slug, name, tier, is_active, is_builtin, logo_url
         FROM $table
         $where
         ORDER BY tier ASC, sort_order ASC, name ASC"
    );

    $out = [];
    foreach ( $rows as $r ) {
        $out[ $r->slug ] = [
            'name'       => $r->name,
            'tier'       => (int) $r->tier,
            'is_active'  => (int) $r->is_active,
            'is_builtin' => (int) $r->is_builtin,
            /* [1.5.164] URL do logo custom (Media Library ou URL externa).
               Vazio para os built-in (que usam SVG inline da função rmbz_service_logo). */
            'logo_url'   => $r->logo_url ?? '',
        ];
    }
    return $out;
}


/**
 * Adicionar uma DSP custom. Usado pela aba admin "Catálogo de Lojas".
 *
 * [1.5.164] $logo_url adicionado: URL absoluta para imagem do logo.
 *           Vazio = sem logo (vai usar fallback genérico ao renderizar).
 */
function rmbz_store_add_dsp( $slug, $name, $tier = 2, $logo_url = '' ) {
    global $wpdb;
    $slug = sanitize_key( $slug );
    if ( empty($slug) || empty($name) ) return false;
    $table = rmbz_dsp_catalog_table();
    $max_sort = (int) $wpdb->get_var("SELECT COALESCE(MAX(sort_order), 0) FROM $table");
    return $wpdb->insert(
        $table,
        [
            'slug'       => $slug,
            'name'       => sanitize_text_field($name),
            'tier'       => max(1, min(3, (int) $tier)),
            'is_active'  => 1,
            'is_builtin' => 0,
            'sort_order' => $max_sort + 1,
            'logo_url'   => $logo_url ? esc_url_raw( $logo_url ) : null,
            'created_at' => current_time('mysql'),
        ],
        [ '%s', '%s', '%d', '%d', '%d', '%d', '%s', '%s' ]
    );
}


/**
 * Toggle activo/inactivo. Aplica-se a built-in E custom.
 */
function rmbz_store_toggle_dsp( $slug, $active ) {
    global $wpdb;
    return $wpdb->update(
        rmbz_dsp_catalog_table(),
        [ 'is_active' => $active ? 1 : 0 ],
        [ 'slug' => sanitize_key($slug) ],
        [ '%d' ],
        [ '%s' ]
    );
}


/**
 * Apagar DSP custom. Built-in não pode ser apagada (só desactivada).
 */
function rmbz_store_delete_dsp( $slug ) {
    global $wpdb;
    return $wpdb->delete(
        rmbz_dsp_catalog_table(),
        [ 'slug' => sanitize_key($slug), 'is_builtin' => 0 ],
        [ '%s', '%d' ]
    );
}


// ============================================================================
// PEDIDOS DE ALTERAÇÃO — QUERIES E HELPERS
// ============================================================================

/**
 * Conta quantos pedidos APROVADOS este utilizador já tem para esta release.
 * Usado para determinar se a próxima alteração é a "1ª grátis" ou paga.
 */
function rmbz_store_count_approved_changes( $release_id, $user_tel ) {
    global $wpdb;
    $table = rmbz_store_changes_table();
    if ( $wpdb->get_var("SHOW TABLES LIKE '$table'") !== $table ) return 0;
    return (int) $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT(*) FROM $table
         WHERE release_id = %d
           AND requester_telemovel = %s
           AND status = 'approved'",
        $release_id, $user_tel
    ) );
}


/**
 * Calcula o custo de um pedido baseado nas regras:
 *   - Se for o 1º pedido aprovado deste user para esta release → 0
 *   - Senão, cobra rmbz_store_change_fee_subsequent SE houver adições;
 *     se só houver remoções, é grátis
 */
function rmbz_store_calculate_fee( $release_id, $user_tel, $added_dsps ) {
    $approved_count = rmbz_store_count_approved_changes( $release_id, $user_tel );
    $is_first = ( $approved_count === 0 );
    $has_additions = ! empty( $added_dsps );

    if ( $is_first || ! $has_additions ) {
        return [ 'amount' => 0.0, 'is_first' => $is_first, 'reason' => $is_first ? 'first_change_free' : 'only_removals_free' ];
    }
    $fee = (float) get_option( 'rmbz_store_change_fee_subsequent', 100 );
    return [ 'amount' => $fee, 'is_first' => false, 'reason' => 'standard_fee' ];
}


/**
 * Cria um pedido de alteração. Pre-condições:
 *   - release existe e pertence ao utilizador
 *   - status do release é live/distribuido
 *   - não há pedido pending pré-existente para a mesma release
 *   - se vai cobrar, utilizador tem saldo suficiente (validação do caller)
 *
 * @return int|WP_Error  ID do pedido criado, ou WP_Error
 */
function rmbz_store_create_request( $release_id, $user_tel, $proposed_dsps ) {
    global $wpdb;
    $release_id = (int) $release_id;
    $user_tel = sanitize_text_field( $user_tel );

    // Snapshot das DSPs actuais
    $current = function_exists('rmbz_distro_get_platforms')
        ? rmbz_distro_get_platforms( $release_id )
        : null;
    if ( ! $current ) {
        return new WP_Error( 'no_release', 'Release não encontrada.' );
    }
    $current_dsps = (array) ($current->_dsp_list ?? []);

    // Validar slugs propostos contra catálogo activo
    $catalog = rmbz_store_get_catalog( true );
    $valid_slugs = array_keys( $catalog );
    $proposed_dsps = array_values( array_unique( array_filter( (array) $proposed_dsps, function($s) use ($valid_slugs) {
        return in_array( $s, $valid_slugs, true );
    } ) ) );

    // Calcular diff
    $added   = array_values( array_diff( $proposed_dsps, $current_dsps ) );
    $removed = array_values( array_diff( $current_dsps, $proposed_dsps ) );

    if ( empty($added) && empty($removed) ) {
        return new WP_Error( 'no_change', 'Não há alterações para aplicar.' );
    }

    // [1.5.233] REGRA DOS 5 MESES — não se pode pedir REMOÇÃO de lojas
    // antes de o release estar 150 dias (5 meses) nas lojas. Protege
    // o artista de cortar receita logo após o lançamento e evita
    // inconsistências com os DSPs.
    if ( ! empty($removed) ) {
        $t_history = $wpdb->prefix . 'rmbz_distro_status_history';
        $live_since = $wpdb->get_var( $wpdb->prepare(
            "SELECT MIN(created_at) FROM $t_history
             WHERE release_id = %d AND to_status IN ('live','distribuido')",
            $release_id
        ) );
        if ( $live_since ) {
            $days_live = floor( ( time() - strtotime($live_since) ) / DAY_IN_SECONDS );
            if ( $days_live < 150 ) {
                $days_left = 150 - $days_live;
                return new WP_Error(
                    'too_early_to_remove',
                    'Ainda não é possível remover este lançamento das lojas. '
                    . 'A remoção só fica disponível 5 meses (150 dias) após a '
                    . 'publicação — faltam ' . (int) $days_left . ' dias. '
                    . 'Esta regra protege os teus ganhos e evita problemas '
                    . 'com as plataformas.'
                );
            }
        }
    }

    // Verificar se já existe pedido pending para esta release
    $table = rmbz_store_changes_table();
    $existing = (int) $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT(*) FROM $table WHERE release_id = %d AND status = 'pending'",
        $release_id
    ) );
    if ( $existing > 0 ) {
        return new WP_Error( 'pending_exists', 'Já existe um pedido pendente para esta release. Aguarda a decisão do admin.' );
    }

    // Calcular custo
    $fee = rmbz_store_calculate_fee( $release_id, $user_tel, $added );

    $inserted = $wpdb->insert(
        $table,
        [
            'release_id'             => $release_id,
            'requester_telemovel'    => $user_tel,
            'current_dsps_snapshot'  => wp_json_encode( $current_dsps ),
            'proposed_dsps'          => wp_json_encode( $proposed_dsps ),
            'added_dsps'             => wp_json_encode( $added ),
            'removed_dsps'           => wp_json_encode( $removed ),
            'is_first_change'        => $fee['is_first'] ? 1 : 0,
            'fee_amount'             => $fee['amount'],
            'fee_charged'            => 0,
            'status'                 => 'pending',
            'created_at'             => current_time('mysql'),
        ],
        [ '%d','%s','%s','%s','%s','%s','%d','%f','%d','%s','%s' ]
    );

    if ( ! $inserted ) {
        return new WP_Error( 'db_error', 'Erro ao criar o pedido.' );
    }
    return (int) $wpdb->insert_id;
}


/**
 * Aprova um pedido. Acções:
 *   1. Se fee_amount > 0, debita o saldo do utilizador (cria entrada no extrato).
 *   2. Aplica os proposed_dsps na tabela rmbz_distro_release_platforms via
 *      update directo (não chama o handler admin que requer POST).
 *   3. Marca status=approved, decided_at=now, fee_charged=1 (se aplicável).
 *   4. Notifica utilizador via Centro de Notificações (se disponível).
 */
function rmbz_store_approve_request( $request_id, $admin_actor = '' ) {
    global $wpdb;
    $table = rmbz_store_changes_table();
    $req = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id = %d", (int) $request_id ) );
    if ( ! $req ) return new WP_Error( 'not_found', 'Pedido não encontrado.' );
    if ( $req->status !== 'pending' ) return new WP_Error( 'bad_status', 'Pedido já foi processado.' );

    $proposed = (array) json_decode( $req->proposed_dsps, true );

    // 1. Debitar saldo se há custo
    $fee = (float) $req->fee_amount;
    if ( $fee > 0 ) {
        $balance = (float) $wpdb->get_var( $wpdb->prepare(
            "SELECT balance FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s",
            $req->requester_telemovel
        ) );
        if ( $balance < $fee ) {
            return new WP_Error( 'insufficient_balance',
                sprintf( 'Utilizador tem saldo insuficiente (%.2f MT) para a taxa de %.2f MT.', $balance, $fee ) );
        }
        $debit_details = sprintf(
            'Pedido de alteração de lojas — Release #%d (Pedido #%d)',
            (int) $req->release_id, (int) $req->id
        );
        $debit_result = function_exists('rmbz_update_wallet_balance')
            ? rmbz_update_wallet_balance( $req->requester_telemovel, -$fee, 'store_change_fee', null, $debit_details )
            : null;
        if ( ! $debit_result || ! $debit_result['success'] ) {
            return new WP_Error( 'debit_failed', 'Falha ao debitar saldo.' );
        }
    }

    // 2. Aplicar nas plataformas — update directo na tabela
    $platforms_table = $wpdb->prefix . 'rmbz_distro_release_platforms';
    $wpdb->update(
        $platforms_table,
        [ 'dsp_list' => wp_json_encode( array_values( $proposed ) ) ],
        [ 'release_id' => (int) $req->release_id ],
        [ '%s' ],
        [ '%d' ]
    );

    // 3. Marcar pedido como aprovado
    $wpdb->update(
        $table,
        [
            'status'      => 'approved',
            'decided_at'  => current_time('mysql'),
            'fee_charged' => $fee > 0 ? 1 : 0,
            'admin_actor' => $admin_actor ?: wp_get_current_user()->user_login,
        ],
        [ 'id' => (int) $req->id ],
        [ '%s', '%s', '%d', '%s' ],
        [ '%d' ]
    );

    // 4. Notificar (gracioso — falha silenciosa se Centro não disponível)
    if ( function_exists('rmbz_notify') ) {
        $added   = (array) json_decode( $req->added_dsps,   true );
        $removed = (array) json_decode( $req->removed_dsps, true );
        $catalog = rmbz_store_get_catalog( false );
        $names_added   = array_map( function($s) use ($catalog) { return $catalog[$s]['name'] ?? $s; }, $added );
        $names_removed = array_map( function($s) use ($catalog) { return $catalog[$s]['name'] ?? $s; }, $removed );
        $msg_diff = '';
        if ( $names_added )   $msg_diff .= 'Adicionadas: ' . implode(', ', $names_added) . ".\n";
        if ( $names_removed ) $msg_diff .= 'Removidas: ' . implode(', ', $names_removed) . ".\n";

        rmbz_notify( $req->requester_telemovel, 'store_change_approved', [
            'release_id'  => (int) $req->release_id,
            'diff'        => $msg_diff,
            'fee'         => number_format( $fee, 2, ',', '.' ) . ' MT',
        ]);
    }

    return true;
}


/**
 * Rejeita um pedido. Não cobra nada. Notifica utilizador.
 */
function rmbz_store_reject_request( $request_id, $admin_note = '', $admin_actor = '' ) {
    global $wpdb;
    $table = rmbz_store_changes_table();
    $req = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE id = %d", (int) $request_id ) );
    if ( ! $req ) return new WP_Error( 'not_found', 'Pedido não encontrado.' );
    if ( $req->status !== 'pending' ) return new WP_Error( 'bad_status', 'Pedido já foi processado.' );

    $wpdb->update(
        $table,
        [
            'status'      => 'rejected',
            'decided_at'  => current_time('mysql'),
            'admin_note'  => sanitize_textarea_field( $admin_note ),
            'admin_actor' => $admin_actor ?: wp_get_current_user()->user_login,
        ],
        [ 'id' => (int) $req->id ],
        [ '%s', '%s', '%s', '%s' ],
        [ '%d' ]
    );

    if ( function_exists('rmbz_notify') ) {
        rmbz_notify( $req->requester_telemovel, 'store_change_rejected', [
            'release_id' => (int) $req->release_id,
            'motivo'     => $admin_note ?: 'Sem detalhes adicionais.',
        ]);
    }

    return true;
}


// ============================================================================
// FRONTEND — MODAL E HANDLER DE SUBMIT
// ============================================================================

/**
 * Renderiza a modal "Adicionar / Remover Lojas".
 *
 * Renderizada uma única vez por página single-view (na rota distribution).
 * Hook: `rmbz_distro_after_single_view` (disparado pelo single-view).
 *
 * Mostra:
 *   - Lista de DSPs do catálogo activo, agrupadas por tier, com checkbox
 *   - DSPs actualmente activas pré-marcadas
 *   - Resumo dinâmico: "X serão adicionadas, Y removidas"
 *   - Custo calculado e aviso de débito (se aplicável)
 */
function rmbz_store_render_modal( $release_id, $user_tel ) {
    if ( ! $release_id || ! $user_tel ) return;
    if ( ! get_option('rmbz_store_changes_enabled', 1) ) return;
    if ( ! function_exists('rmbz_distro_get_platforms') ) return;

    $platforms = rmbz_distro_get_platforms( $release_id );
    if ( ! $platforms ) return;
    $current_dsps = (array) ($platforms->_dsp_list ?? []);

    $catalog = rmbz_store_get_catalog( true );
    if ( empty($catalog) ) return;

    // Estado para mostrar regra de cobrança
    $approved_count = rmbz_store_count_approved_changes( $release_id, $user_tel );
    $is_first = ( $approved_count === 0 );
    $fee_subsequent = (float) get_option( 'rmbz_store_change_fee_subsequent', 100 );

    // Já existe pedido pendente?
    global $wpdb;
    $pending = (int) $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT(*) FROM " . rmbz_store_changes_table() . " WHERE release_id = %d AND status = 'pending'",
        $release_id
    ) );

    // Saldo do utilizador
    $balance = (float) $wpdb->get_var( $wpdb->prepare(
        "SELECT balance FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s",
        $user_tel
    ) );

    // Agrupar por tier
    $by_tier = [1 => [], 2 => [], 3 => []];
    foreach ( $catalog as $slug => $meta ) {
        $tier = max(1, min(3, (int) ($meta['tier'] ?? 2)));
        $by_tier[$tier][$slug] = $meta;
    }
    $tier_names = [
        1 => 'Tier 1 — Essenciais',
        2 => 'Tier 2 — África & Lusófono',
        3 => 'Tier 3 — Ásia & Audiófilo',
    ];

    ?>
    <div id="rmbz-stores-modal" class="rmbz-stores-fe-modal" aria-hidden="true" style="display:none;">
        <div class="rmbz-stores-fe-backdrop" onclick="rmbzCloseStoresModal()"></div>
        <div class="rmbz-stores-fe-dialog" role="dialog" aria-labelledby="rmbz-stores-fe-title">

            <div class="rmbz-stores-fe-header">
                <h3 id="rmbz-stores-fe-title">🏪 Adicionar / Remover Lojas</h3>
                <button class="rmbz-stores-fe-close" onclick="rmbzCloseStoresModal()" aria-label="Fechar">×</button>
            </div>

            <div class="rmbz-stores-fe-body">

                <?php if ( $pending > 0 ): ?>
                    <div class="rmbz-stores-fe-notice warning">
                        ⏳ Já tens um pedido pendente para esta release. Aguarda a decisão do admin antes de submeter outro.
                    </div>
                <?php else: ?>
                    <div class="rmbz-stores-fe-info">
                        <strong>Como funciona:</strong>
                        <ul style="margin:6px 0 0 18px;padding:0;font-size:13px;line-height:1.5;">
                            <li>Marca as lojas onde queres que o teu release esteja disponível.</li>
                            <li>O admin valida e aplica a alteração nas lojas reais.</li>
                            <li>
                                <?php if ( $is_first ): ?>
                                    Esta é a tua <strong>1ª alteração</strong> para este release — <strong>grátis</strong>.
                                <?php else: ?>
                                    Já fizeste <?php echo (int) $approved_count; ?> alteração(ões). Adicionar lojas custa
                                    <strong><?php echo number_format( $fee_subsequent, 0, ',', '.' ); ?> MT</strong>
                                    (debitado quando o admin aprovar). Remover lojas é sempre grátis.
                                <?php endif; ?>
                            </li>
                            <li>O teu saldo actual: <strong><?php echo number_format( $balance, 2, ',', '.' ); ?> MT</strong>.</li>
                        </ul>
                    </div>

                    <form id="rmbz-stores-fe-form" method="post" action="<?php echo esc_url( admin_url('admin-post.php') ); ?>">
                        <?php wp_nonce_field( 'rmbz_store_change_' . (int) $release_id, 'rmbz_store_change_nonce' ); ?>
                        <input type="hidden" name="action" value="rmbz_store_change_submit">
                        <input type="hidden" name="release_id" value="<?php echo (int) $release_id; ?>">

                        <?php foreach ( [1, 2, 3] as $tier ):
                            if ( empty($by_tier[$tier]) ) continue;
                        ?>
                            <div class="rmbz-stores-fe-tier">
                                <h4><?php echo esc_html( $tier_names[$tier] ); ?></h4>
                                <div class="rmbz-stores-fe-grid">
                                    <?php foreach ( $by_tier[$tier] as $slug => $meta ):
                                        $checked = in_array( $slug, $current_dsps, true );
                                    ?>
                                        <label class="rmbz-stores-fe-cell <?php echo $checked ? 'sel' : ''; ?>" data-slug="<?php echo esc_attr($slug); ?>" data-was-checked="<?php echo $checked ? '1' : '0'; ?>">
                                            <input type="checkbox" name="dsps[]" value="<?php echo esc_attr($slug); ?>" <?php checked($checked); ?>>
                                            <?php /* [1.5.163] Logo SVG do DSP — fallback para nada se módulo não carregar */ ?>
                                            <?php if ( function_exists('rmbz_service_logo') ): ?>
                                                <span class="rmbz-stores-fe-logo"><?php echo rmbz_service_logo($slug, ['size' => 22]); ?></span>
                                            <?php endif; ?>
                                            <span class="rmbz-stores-fe-name"><?php echo esc_html($meta['name']); ?></span>
                                        </label>
                                    <?php endforeach; ?>
                                </div>
                            </div>
                        <?php endforeach; ?>

                        <div class="rmbz-stores-fe-summary" id="rmbz-stores-fe-summary">
                            <span id="rmbz-stores-fe-summary-text">Sem alterações.</span>
                            <span id="rmbz-stores-fe-summary-fee" class="rmbz-stores-fe-fee" style="float:right;font-weight:var(--rmbz-weight-semibold, 600);"></span>
                        </div>

                        <div class="rmbz-stores-fe-actions">
                            <button type="button" class="rmbz-sv2-btn secondary" onclick="rmbzCloseStoresModal()">Cancelar</button>
                            <button type="submit" class="rmbz-sv2-btn primary" id="rmbz-stores-fe-submit" disabled>Submeter Pedido</button>
                        </div>
                    </form>
                <?php endif; ?>

            </div>
        </div>
    </div>

    <style>
    /* [1.5.146] Backdrop e dialog ambos position:fixed — antes o backdrop
       era position:absolute o que falhava se algum ancestral tinha
       transform/filter/will-change (cria novo containing block).
       Usando fixed em ambos garantimos sempre cobertura total da viewport.
       [1.5.152] Refactor visual: usar design tokens (--rmbz-*) em vez de
       cores hardcoded. Mantém o comportamento e estrutura. */
    .rmbz-stores-fe-modal { position:fixed; inset:0; z-index:99999; }
    .rmbz-stores-fe-backdrop { position:fixed; inset:0; background:rgba(0,0,0,0.7); z-index:99999; }
    .rmbz-stores-fe-dialog {
        position:fixed; top:50%; left:50%; transform:translate(-50%, -50%);
        width:calc(100% - 32px); max-width:800px; max-height:90vh;
        background:var(--rmbz-bg-surface, #131316);
        color:var(--rmbz-text-primary, #fafafa);
        border:1px solid var(--rmbz-border-subtle, #26262b);
        border-radius:var(--rmbz-radius-md, 12px);
        box-shadow:var(--rmbz-shadow-xl, 0 24px 60px rgba(0,0,0,0.65));
        display:flex; flex-direction:column; z-index:100000;
        font-family:var(--rmbz-font-body, system-ui, sans-serif);
    }
    .rmbz-stores-fe-header {
        padding:var(--rmbz-space-4, 16px) var(--rmbz-space-5, 20px);
        border-bottom:1px solid var(--rmbz-border-subtle, #26262b);
        display:flex; justify-content:space-between; align-items:center; flex-shrink:0;
    }
    .rmbz-stores-fe-header h3 {
        margin:0;
        font-size:var(--rmbz-text-lg, 18px);
        font-weight:var(--rmbz-weight-semibold, 600);
        color:var(--rmbz-brand, #f0b90b);
        font-family:var(--rmbz-font-display, system-ui, sans-serif);
    }
    .rmbz-stores-fe-close {
        background:transparent;
        color:var(--rmbz-text-muted, #8a8a92);
        border:none; font-size:28px; cursor:pointer; padding:0 8px; line-height:1;
    }
    .rmbz-stores-fe-close:hover { color:var(--rmbz-text-primary, #fafafa); }
    .rmbz-stores-fe-body {
        padding:var(--rmbz-space-5, 20px) var(--rmbz-space-5, 20px);
        overflow-y:auto; flex:1;
    }
    .rmbz-stores-fe-info {
        background:var(--rmbz-info-soft, rgba(59,130,246,0.12));
        border:1px solid var(--rmbz-info, #3b82f6);
        border-radius:var(--rmbz-radius-sm, 8px);
        padding:var(--rmbz-space-3, 12px) var(--rmbz-space-4, 14px);
        margin-bottom:var(--rmbz-space-4, 18px);
        font-size:var(--rmbz-text-sm, 13px);
        color:var(--rmbz-text-secondary, #c4c4cc);
    }
    .rmbz-stores-fe-info strong { color:var(--rmbz-text-primary, #fafafa); }
    .rmbz-stores-fe-notice.warning {
        background:var(--rmbz-warning-soft, rgba(245,158,11,0.12));
        border:1px solid var(--rmbz-warning, #f59e0b);
        border-radius:var(--rmbz-radius-sm, 8px);
        padding:var(--rmbz-space-4, 14px);
        color:var(--rmbz-warning, #f59e0b);
        font-size:var(--rmbz-text-sm, 14px);
        text-align:center;
    }
    .rmbz-stores-fe-tier h4 {
        margin:var(--rmbz-space-4, 18px) 0 var(--rmbz-space-2, 8px);
        font-size:var(--rmbz-text-xs, 12px);
        color:var(--rmbz-text-muted, #8a8a92);
        text-transform:uppercase;
        letter-spacing:0.05em;
        font-weight:var(--rmbz-weight-semibold, 600);
    }
    .rmbz-stores-fe-grid {
        display:grid;
        grid-template-columns:repeat(auto-fill, minmax(160px, 1fr));
        gap:var(--rmbz-space-2, 8px);
    }
    .rmbz-stores-fe-cell {
        display:flex; align-items:center; gap:var(--rmbz-space-2, 8px);
        padding:var(--rmbz-space-3, 10px) var(--rmbz-space-3, 12px);
        background:var(--rmbz-bg-elevated, #1c1c20);
        border:1px solid var(--rmbz-border-subtle, #26262b);
        border-radius:var(--rmbz-radius-xs, 6px);
        cursor:pointer;
        transition:background 0.15s, border-color 0.15s;
        user-select:none;
        font-size:var(--rmbz-text-sm, 13px);
        color:var(--rmbz-text-secondary, #c4c4cc);
    }
    .rmbz-stores-fe-cell:hover {
        background:var(--rmbz-bg-hover, #1f1f23);
        border-color:var(--rmbz-border-strong, #3a3a40);
    }
    .rmbz-stores-fe-cell.sel {
        background:var(--rmbz-success-soft, rgba(16,185,129,0.12));
        border-color:var(--rmbz-success, #10b981);
        color:var(--rmbz-text-primary, #fafafa);
    }
    .rmbz-stores-fe-cell input { margin:0; }
    /* [1.5.163] Logo do DSP dentro da cell */
    .rmbz-stores-fe-logo {
        display: inline-flex;
        align-items: center;
        flex-shrink: 0;
        width: 22px;
        height: 22px;
    }
    .rmbz-stores-fe-logo svg {
        display: block;
        width: 100%;
        height: 100%;
        border-radius: 4px;
    }
    .rmbz-stores-fe-cell.diff-add {
        background:var(--rmbz-success-soft, rgba(16,185,129,0.18));
        border-color:var(--rmbz-success-strong, #059669);
    }
    .rmbz-stores-fe-cell.diff-remove {
        background:var(--rmbz-danger-soft, rgba(239,68,68,0.15));
        border-color:var(--rmbz-danger, #ef4444);
        text-decoration:line-through;
        opacity:0.85;
    }
    .rmbz-stores-fe-summary {
        margin-top:var(--rmbz-space-4, 16px);
        padding:var(--rmbz-space-3, 12px);
        background:var(--rmbz-bg-elevated, #1c1c20);
        border:1px solid var(--rmbz-border-subtle, #26262b);
        border-radius:var(--rmbz-radius-xs, 6px);
        font-size:var(--rmbz-text-sm, 13px);
        color:var(--rmbz-text-secondary, #c4c4cc);
    }
    .rmbz-stores-fe-actions {
        margin-top:var(--rmbz-space-4, 16px);
        display:flex; justify-content:flex-end; gap:var(--rmbz-space-3, 10px);
        flex-shrink:0;
        padding:var(--rmbz-space-4, 14px) var(--rmbz-space-5, 22px);
        border-top:1px solid var(--rmbz-border-subtle, #26262b);
    }
    /* Body scroll lock quando modal aberta */
    body.rmbz-stores-modal-open { overflow:hidden; }
    </style>

    <script>
    /* [1.5.144/146] Modal de gestão de lojas. Padrão idêntico à modal de splits.
       - rmbzOpenStoresModal() / rmbzCloseStoresModal() expostos globais
       - calcula diff em tempo real consoante checkboxes
       - desactiva submit se não há alterações
       - aplica classes diff-add/diff-remove para feedback visual
       Sem `&&` no script (segue padrão 1.5.119/126).
       [1.5.146] Move a modal para o <body> no load para escapar a
       containing blocks criados por transform/filter de ancestrais
       (causa comum de position:fixed comportar-se como absolute).
       Também lock do scroll do body quando aberta. */
    (function(){
        // Mover modal para body imediatamente para garantir que position:fixed
        // funciona correctamente (escapa qualquer ancestral com transform)
        var modalEl = document.getElementById('rmbz-stores-modal');
        if (modalEl) {
            if (modalEl.parentNode !== document.body) {
                document.body.appendChild(modalEl);
            }
        }

        window.rmbzOpenStoresModal = function() {
            var m = document.getElementById('rmbz-stores-modal');
            if (!m) return;
            m.style.display = 'block';
            m.setAttribute('aria-hidden', 'false');
            document.body.classList.add('rmbz-stores-modal-open');
        };
        window.rmbzCloseStoresModal = function() {
            var m = document.getElementById('rmbz-stores-modal');
            if (!m) return;
            m.style.display = 'none';
            m.setAttribute('aria-hidden', 'true');
            document.body.classList.remove('rmbz-stores-modal-open');
        };

        var feeSubseq = <?php echo (int) $fee_subsequent; ?>;
        var isFirst = <?php echo $is_first ? 'true' : 'false'; ?>;

        function recalc() {
            var cells = document.querySelectorAll('.rmbz-stores-fe-cell');
            var nAdd = 0, nRemove = 0;
            cells.forEach(function(cell) {
                var was = cell.getAttribute('data-was-checked') === '1';
                var input = cell.querySelector('input[type=checkbox]');
                if (!input) return;
                var now = input.checked;
                cell.classList.remove('diff-add', 'diff-remove');
                if (now) { cell.classList.add('sel'); }
                else { cell.classList.remove('sel'); }
                if (now) { if (!was) { cell.classList.add('diff-add'); nAdd++; } }
                if (!now) { if (was) { cell.classList.add('diff-remove'); nRemove++; } }
            });

            var sumText = document.getElementById('rmbz-stores-fe-summary-text');
            var sumFee  = document.getElementById('rmbz-stores-fe-summary-fee');
            var btn     = document.getElementById('rmbz-stores-fe-submit');
            if (!sumText) return;
            if (!btn) return;

            if (nAdd === 0) { if (nRemove === 0) {
                sumText.textContent = 'Sem alterações.';
                if (sumFee) sumFee.textContent = '';
                btn.disabled = true;
                return;
            } }
            var parts = [];
            if (nAdd > 0)    parts.push(nAdd + (nAdd === 1 ? ' loja a adicionar' : ' lojas a adicionar'));
            if (nRemove > 0) parts.push(nRemove + (nRemove === 1 ? ' loja a remover' : ' lojas a remover'));
            sumText.textContent = parts.join(' · ');

            // Cobrança: 1ª = grátis, ou se só remoções = grátis
            var willCharge = false;
            if (!isFirst) { if (nAdd > 0) { willCharge = true; } }
            if (sumFee) {
                if (willCharge) {
                    sumFee.textContent = 'Custo: ' + feeSubseq + ' MT';
                    /* [1.5.152] Cor via CSS var (com fallback) — token --rmbz-warning */
                    sumFee.style.color = 'var(--rmbz-warning, #f59e0b)';
                } else {
                    sumFee.textContent = 'Grátis';
                    sumFee.style.color = 'var(--rmbz-success, #10b981)';
                }
            }
            btn.disabled = false;
        }

        // Bindings
        document.querySelectorAll('.rmbz-stores-fe-cell input[type=checkbox]').forEach(function(cb) {
            cb.addEventListener('change', recalc);
        });

        // Auto-abrir se URL contém #open-stores
        if (window.location.hash === '#open-stores') {
            setTimeout(function(){ rmbzOpenStoresModal(); recalc(); }, 100);
        }

        // Inicializar estado
        recalc();
    })();
    </script>
    <?php
}


/**
 * Hook que injecta a modal no final do single-view.
 */
function rmbz_store_inject_modal_in_single_view( $release_id, $user_tel ) {
    rmbz_store_render_modal( $release_id, $user_tel );
}
add_action( 'rmbz_distro_after_single_view', 'rmbz_store_inject_modal_in_single_view', 10, 2 );


/**
 * Handler do submit do form. Registado em admin-post.php para artistas
 * autenticados (logged in via sistema RMBZ).
 */
function rmbz_store_handle_submit() {
    if ( ! function_exists('rmbz_is_user_logged_in') || ! rmbz_is_user_logged_in() ) {
        wp_die( 'Sessão expirada.' );
    }
    $release_id = isset($_POST['release_id']) ? (int) $_POST['release_id'] : 0;
    if ( ! wp_verify_nonce( $_POST['rmbz_store_change_nonce'] ?? '', 'rmbz_store_change_' . $release_id ) ) {
        wp_die( 'Falha de segurança.' );
    }

    $user_tel = function_exists('rmbz_get_current_user_telemovel')
        ? rmbz_get_current_user_telemovel()
        : '';
    if ( empty($user_tel) ) wp_die( 'Utilizador não identificado.' );
    $access_error = function_exists( 'rmbz_distro_artist_access_error' )
        ? rmbz_distro_artist_access_error( $user_tel )
        : '';
    if ( $access_error !== '' ) wp_die( esc_html( $access_error ), 'Distribuição indisponível', [ 'response' => 403 ] );

    // Validar ownership da release
    if ( function_exists('rmbz_splits_user_owns_release') && ! rmbz_splits_user_owns_release($release_id) ) {
        wp_die( 'Não és o dono desta release.' );
    }

    $proposed = isset($_POST['dsps']) ? array_map( 'sanitize_key', (array) $_POST['dsps'] ) : [];
    $return_to = sanitize_key( $_POST['return_to'] ?? '' );
    $vip_url = add_query_arg(
        [ 'sect' => 'distro', 'dview' => 'release', 'rid' => $release_id ],
        home_url( '/minha-conta/' )
    );

    $result = rmbz_store_create_request( $release_id, $user_tel, $proposed );
    if ( is_wp_error($result) ) {
        if ( $return_to === 'vip' ) {
            wp_safe_redirect( add_query_arg( 'store_err', $result->get_error_message(), $vip_url ) );
            exit;
        }
        $msg = urlencode( $result->get_error_message() );
        wp_redirect( admin_url('admin.php?page=rmbz-distribution&distro_action=view_release&id=' . $release_id . '&store_err=' . $msg) );
        exit;
    }
    if ( $return_to === 'vip' ) {
        wp_safe_redirect( add_query_arg( 'store_ok', '1', $vip_url ) );
        exit;
    }
    wp_redirect( admin_url('admin.php?page=rmbz-distribution&distro_action=view_release&id=' . $release_id . '&store_ok=1') );
    exit;
}
add_action( 'admin_post_rmbz_store_change_submit', 'rmbz_store_handle_submit' );
add_action( 'admin_post_nopriv_rmbz_store_change_submit', 'rmbz_store_handle_submit' );


// ============================================================================
// ADMIN — HANDLERS DE APROVAÇÃO/REJEIÇÃO
// ============================================================================

function rmbz_store_admin_approve_handler() {
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');
    $req_id = isset($_GET['req_id']) ? (int) $_GET['req_id'] : 0;
    if ( ! wp_verify_nonce( $_GET['_wpnonce'] ?? '', 'rmbz_store_approve_' . $req_id ) ) wp_die('Falha de segurança.');

    $r = rmbz_store_approve_request( $req_id, wp_get_current_user()->user_login );
    if ( is_wp_error($r) ) {
        wp_redirect( admin_url('admin.php?page=rmbz-admin-distribution&tab=store_requests&err=' . urlencode($r->get_error_message())) );
    } else {
        wp_redirect( admin_url('admin.php?page=rmbz-admin-distribution&tab=store_requests&ok=approved') );
    }
    exit;
}
add_action( 'admin_post_rmbz_store_approve', 'rmbz_store_admin_approve_handler' );

function rmbz_store_admin_reject_handler() {
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');
    $req_id = isset($_POST['req_id']) ? (int) $_POST['req_id'] : (int) ($_GET['req_id'] ?? 0);
    $nonce = $_POST['_wpnonce'] ?? $_GET['_wpnonce'] ?? '';
    if ( ! wp_verify_nonce( $nonce, 'rmbz_store_reject_' . $req_id ) ) wp_die('Falha de segurança.');

    $note = sanitize_textarea_field( $_POST['admin_note'] ?? '' );
    $r = rmbz_store_reject_request( $req_id, $note, wp_get_current_user()->user_login );
    if ( is_wp_error($r) ) {
        wp_redirect( admin_url('admin.php?page=rmbz-admin-distribution&tab=store_requests&err=' . urlencode($r->get_error_message())) );
    } else {
        wp_redirect( admin_url('admin.php?page=rmbz-admin-distribution&tab=store_requests&ok=rejected') );
    }
    exit;
}
add_action( 'admin_post_rmbz_store_reject', 'rmbz_store_admin_reject_handler' );


// ============================================================================
// ADMIN — RENDERERS DE PÁGINAS (chamados pelo controller)
// ============================================================================

function rmbz_store_admin_requests_page() {
    global $wpdb;
    $table = rmbz_store_changes_table();

    if ( ! empty($_GET['ok']) ) {
        $msg = $_GET['ok'] === 'approved' ? 'Pedido aprovado e aplicado.' : 'Pedido rejeitado.';
        echo '<div class="notice notice-success is-dismissible" style="margin:12px 0;"><p>' . esc_html($msg) . '</p></div>';
    }
    if ( ! empty($_GET['err']) ) {
        echo '<div class="notice notice-error is-dismissible" style="margin:12px 0;"><p>' . esc_html( wp_unslash($_GET['err']) ) . '</p></div>';
    }

    /* [1.5.155] Lemos `store_tab` (não `tab`) porque `tab` já é usado pelo
       controller principal para identificar a sub-página dentro de
       rmbz-admin-distribution. Validamos contra whitelist explícita
       em vez de confiar em $wpdb->prepare — defesa em profundidade. */
    $allowed_tabs = ['pending', 'approved', 'rejected'];
    $tab_raw = $_GET['store_tab'] ?? 'pending';
    $tab = in_array( $tab_raw, $allowed_tabs, true ) ? $tab_raw : 'pending';

    // [1.5.233] Query enriquecida — o admin precisa de ver QUAL a
    // música, QUEM é o artista e SE já tem ganhos, antes de decidir.
    $t_releases = $wpdb->prefix . 'rmbz_distro_releases';
    $t_wallets  = $wpdb->prefix . 'rmbz_wallets';
    $t_daily    = $wpdb->prefix . 'rmbz_distro_daily_stats';
    $t_history  = $wpdb->prefix . 'rmbz_distro_status_history';

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT scr.*,
                rel.title          AS release_title,
                rel.status         AS release_status,
                rel.release_date   AS release_date,
                w.user_nome        AS artist_name,
                ( SELECT COALESCE(SUM(d.sales),0)
                  FROM $t_daily d
                  WHERE d.release_id = scr.release_id ) AS release_earnings,
                ( SELECT COALESCE(SUM(d.streams),0)
                  FROM $t_daily d
                  WHERE d.release_id = scr.release_id ) AS release_streams,
                ( SELECT MIN(h.created_at)
                  FROM $t_history h
                  WHERE h.release_id = scr.release_id
                    AND h.to_status IN ('live','distribuido') ) AS live_since
         FROM $table scr
         LEFT JOIN $t_releases rel ON rel.id = scr.release_id
         LEFT JOIN $t_wallets  w   ON w.user_telemovel = scr.requester_telemovel
         WHERE scr.status = %s
         ORDER BY scr.created_at DESC
         LIMIT 200",
        $tab
    ) );

    $counts = [];
    foreach ( ['pending','approved','rejected'] as $st ) {
        $counts[$st] = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE status = %s", $st ) );
    }
    $catalog = rmbz_store_get_catalog( false );

    ?>
    <div class="wrap">
        <h1>🏪 Pedidos de Alteração de Lojas</h1>

        <h2 class="nav-tab-wrapper" style="margin-top:14px;">
            <?php foreach ( [
                'pending'  => '⏳ Pendentes',
                'approved' => '✓ Aprovados',
                'rejected' => '✗ Rejeitados',
            ] as $st => $label ): ?>
                <?php /* [1.5.155] Bug fix: link apontava para
                    ?page=rmbz-distribution (página do artista) o que
                    causava "Sem permissão" ao admin clicar nas tabs.
                    Corrigido para ?page=rmbz-admin-distribution&tab=...
                    que é o slug + query string corretos da página admin. */ ?>
                <a href="?page=rmbz-admin-distribution&tab=store_requests&store_tab=<?php echo esc_attr($st); ?>"
                   class="nav-tab <?php echo $tab === $st ? 'nav-tab-active' : ''; ?>">
                    <?php echo esc_html($label); ?> (<?php echo (int) $counts[$st]; ?>)
                </a>
            <?php endforeach; ?>
        </h2>

        <?php if ( empty($rows) ): ?>
            <p style="margin-top:20px;color:var(--rmbz-text-muted, #888);">Sem pedidos nesta categoria.</p>
        <?php else: ?>
            <!-- [1.5.234] Cards modernos — substituem a tabela apertada. -->
            <div style="display:flex; flex-direction:column; gap:14px; margin-top:18px;
                        max-width:920px;">
                <?php foreach ( $rows as $r ):
                    $added   = (array) json_decode( $r->added_dsps, true );
                    $removed = (array) json_decode( $r->removed_dsps, true );

                    // [1.5.233] REGRA DOS 5 MESES
                    $blocked_5m  = false;
                    $days_live   = null;
                    $days_left   = null;
                    if ( ! empty($removed) && ! empty($r->live_since) ) {
                        $days_live = floor(
                            ( time() - strtotime($r->live_since) ) / DAY_IN_SECONDS
                        );
                        if ( $days_live < 150 ) {
                            $blocked_5m = true;
                            $days_left  = 150 - $days_live;
                        }
                    }
                    $has_earnings = ( (float) $r->release_earnings ) > 0;

                    // Helper inline para render dos chips de loja
                    $render_chips = function( $slugs, $is_remove ) use ( $catalog ) {
                        echo '<div style="display:flex; flex-wrap:wrap; gap:6px;">';
                        foreach ( $slugs as $s ) {
                            $name = $catalog[$s]['name'] ?? $s;
                            $logo = function_exists('rmbz_service_logo')
                                ? rmbz_service_logo($s, ['size' => 16]) : '';
                            $style = $is_remove
                                ? 'background:#fdecec; border:1px solid #f5c2c2; opacity:.85; text-decoration:line-through;'
                                : 'background:#e7f6ee; border:1px solid #b6e2c8;';
                            echo '<span style="display:inline-flex; align-items:center; gap:5px; '
                               . 'border-radius:6px; padding:4px 9px; font-size:12px; ' . $style . '" '
                               . 'title="' . esc_attr($name) . '">'
                               . $logo . esc_html($name) . '</span>';
                        }
                        echo '</div>';
                    };
                ?>
                    <div style="background:#fff; border:1px solid #e2e4e7;
                                border-left:4px solid <?php echo $blocked_5m ? '#d63638' : ( ! empty($added) && empty($removed) ? '#00a32a' : '#dba617' ); ?>;
                                border-radius:10px; padding:0; overflow:hidden;
                                box-shadow:0 1px 3px rgba(0,0,0,0.04);">

                        <!-- Cabeçalho do card -->
                        <div style="display:flex; align-items:flex-start; gap:14px;
                                    padding:16px 18px; border-bottom:1px solid #f0f0f1;">
                            <div style="width:44px; height:44px; border-radius:9px;
                                        background:#f0f0f1; display:flex; align-items:center;
                                        justify-content:center; font-size:22px; flex-shrink:0;">🎵</div>
                            <div style="flex:1; min-width:0;">
                                <div style="font-size:15px; font-weight:600; color:#1d2327;">
                                    <?php echo esc_html( $r->release_title ?: 'Release sem título' ); ?>
                                </div>
                                <div style="font-size:13px; color:#50575e; margin-top:2px;">
                                    <?php echo esc_html( $r->artist_name ?: '—' ); ?>
                                    <span style="color:#a7aaad;">·</span>
                                    <span style="font-family:monospace; font-size:12px;">
                                        <?php echo esc_html( $r->requester_telemovel ); ?>
                                    </span>
                                </div>
                            </div>
                            <div style="text-align:right; flex-shrink:0;">
                                <div style="font-size:12px; color:#787c82;">
                                    Pedido #<?php echo (int) $r->id; ?> · Rel #<?php echo (int) $r->release_id; ?>
                                </div>
                                <div style="font-size:12px; color:#a7aaad; margin-top:2px;">
                                    <?php echo esc_html( date_i18n('d/m/Y H:i', strtotime($r->created_at)) ); ?>
                                </div>
                            </div>
                        </div>

                        <!-- Corpo: saúde do release + diff -->
                        <div style="padding:16px 18px; display:flex; gap:20px; flex-wrap:wrap;">

                            <!-- Saúde do release -->
                            <div style="flex:1; min-width:200px;">
                                <div style="font-size:11px; text-transform:uppercase;
                                            letter-spacing:.5px; color:#787c82; margin-bottom:8px;">
                                    Estado do lançamento
                                </div>
                                <?php if ( $has_earnings ): ?>
                                    <div style="display:inline-flex; align-items:center; gap:6px;
                                                background:#fff8e5; border:1px solid #f0d68a;
                                                border-radius:6px; padding:6px 10px; font-size:12px;
                                                color:#7a5c00; font-weight:600; margin-bottom:6px;">
                                        💰 $<?php echo number_format( (float) $r->release_earnings, 2 ); ?>
                                        gerados · <?php echo number_format( (int) $r->release_streams ); ?> streams
                                    </div>
                                <?php else: ?>
                                    <div style="font-size:12px; color:#787c82; margin-bottom:6px;">
                                        Sem ganhos registados ainda
                                    </div>
                                <?php endif; ?>
                                <?php if ( $r->live_since ): ?>
                                    <div style="font-size:12px; color:#50575e;">
                                        📅 Nas lojas há <strong><?php echo (int) $days_live; ?> dias</strong>
                                        <span style="color:#a7aaad;">
                                            (desde <?php echo esc_html( date_i18n('d/m/Y', strtotime($r->live_since)) ); ?>)
                                        </span>
                                    </div>
                                <?php endif; ?>
                            </div>

                            <!-- Diff das lojas -->
                            <div style="flex:1; min-width:220px;">
                                <div style="font-size:11px; text-transform:uppercase;
                                            letter-spacing:.5px; color:#787c82; margin-bottom:8px;">
                                    Alteração pedida
                                </div>
                                <?php if ( ! empty($added) ): ?>
                                    <div style="margin-bottom:8px;">
                                        <div style="font-size:12px; color:#00a32a; font-weight:600; margin-bottom:4px;">
                                            + Adicionar
                                        </div>
                                        <?php $render_chips( $added, false ); ?>
                                    </div>
                                <?php endif; ?>
                                <?php if ( ! empty($removed) ): ?>
                                    <div>
                                        <div style="font-size:12px; color:#d63638; font-weight:600; margin-bottom:4px;">
                                            − Remover
                                        </div>
                                        <?php $render_chips( $removed, true ); ?>
                                    </div>
                                <?php endif; ?>
                            </div>
                        </div>

                        <?php if ( $r->admin_note ): ?>
                            <div style="padding:0 18px 12px; font-size:12px; color:#787c82;">
                                <strong>Nota admin:</strong> <?php echo esc_html($r->admin_note); ?>
                            </div>
                        <?php endif; ?>

                        <?php
                        // [1.5.233] AVISO da regra dos 5 meses
                        if ( $blocked_5m ): ?>
                            <div style="margin:0 18px 14px; padding:11px 13px;
                                        background:#fcebec; border:1px solid #f0b5b7;
                                        border-radius:7px; font-size:12px; color:#9b1c1c;
                                        line-height:1.5;">
                                ⚠️ <strong>Regra dos 5 meses não cumprida.</strong>
                                Este release só está nas lojas há
                                <strong><?php echo (int) $days_live; ?> dias</strong> —
                                a remoção só é recomendada a partir dos 150 dias
                                (faltam <strong><?php echo (int) $days_left; ?> dias</strong>).
                                <?php if ( $has_earnings ): ?>
                                    Como o release já gera ganhos, remover agora
                                    <strong>interrompe a receita do artista</strong>.
                                <?php endif; ?>
                            </div>
                        <?php endif; ?>

                        <!-- Rodapé: custo + ações -->
                        <div style="display:flex; align-items:center; justify-content:space-between;
                                    gap:14px; padding:12px 18px; background:#f6f7f7;
                                    border-top:1px solid #f0f0f1; flex-wrap:wrap;">
                            <div style="font-size:13px;">
                                <span style="color:#787c82;">Custo:</span>
                                <?php if ( $r->is_first_change ): ?>
                                    <strong style="color:#00a32a;">1ª alteração grátis</strong>
                                <?php elseif ( (float) $r->fee_amount > 0 ): ?>
                                    <strong><?php echo number_format( (float) $r->fee_amount, 0, ',', '.' ); ?> MT</strong>
                                    <?php if ( $r->fee_charged ): ?>
                                        <span style="color:#00a32a; font-size:12px;">✓ Cobrado</span>
                                    <?php endif; ?>
                                <?php else: ?>
                                    <strong style="color:#787c82;">Grátis</strong>
                                <?php endif; ?>
                            </div>

                            <div style="display:flex; gap:8px; align-items:center;">
                                <?php if ( $r->status === 'pending' ): ?>
                                    <?php
                                    if ( $blocked_5m ) {
                                        $confirm_msg = 'ATENCAO: este pedido VIOLA a regra dos 5 meses '
                                            . '(release nas lojas ha apenas ' . (int) $days_live . ' dias). '
                                            . ( $has_earnings ? 'O release JA GERA GANHOS. ' : '' )
                                            . 'Tens a certeza de que queres aprovar mesmo assim?';
                                    } else {
                                        $confirm_msg = 'Aprovar este pedido? Vai aplicar a alteracao '
                                            . 'nas lojas e cobrar a taxa (se houver).';
                                    }
                                    ?>
                                    <button type="button" class="button"
                                            onclick="document.getElementById('reject-form-<?php echo (int) $r->id; ?>').style.display='block';">
                                        ✗ Rejeitar
                                    </button>
                                    <a href="<?php echo esc_url( wp_nonce_url( admin_url('admin-post.php?action=rmbz_store_approve&req_id=' . (int) $r->id), 'rmbz_store_approve_' . (int) $r->id ) ); ?>"
                                       class="button button-primary"
                                       <?php if ( $blocked_5m ): ?>style="background:#d63638;border-color:#d63638;"<?php endif; ?>
                                       onclick="return confirm('<?php echo esc_js( $confirm_msg ); ?>');">
                                        <?php echo $blocked_5m ? '⚠ Aprovar mesmo assim' : '✓ Aprovar'; ?>
                                    </a>
                                <?php else: ?>
                                    <small style="color:#787c82;">
                                        <?php echo esc_html( $r->admin_actor
                                            ? 'Por ' . $r->admin_actor . ' em ' . date_i18n('d/m/y H:i', strtotime($r->decided_at))
                                            : '—' ); ?>
                                    </small>
                                <?php endif; ?>
                            </div>
                        </div>

                        <?php if ( $r->status === 'pending' ): ?>
                            <form id="reject-form-<?php echo (int) $r->id; ?>" method="post"
                                  action="<?php echo esc_url( admin_url('admin-post.php') ); ?>"
                                  style="display:none; margin:0 18px 14px; padding:12px;
                                         background:#fff; border:1px solid #e2e4e7; border-radius:7px;">
                                <input type="hidden" name="action" value="rmbz_store_reject">
                                <input type="hidden" name="req_id" value="<?php echo (int) $r->id; ?>">
                                <?php wp_nonce_field( 'rmbz_store_reject_' . (int) $r->id ); ?>
                                <textarea name="admin_note" rows="2"
                                          placeholder="Motivo da rejeição (opcional)"
                                          style="width:100%; font-size:13px;"></textarea>
                                <button type="submit" class="button button-small" style="margin-top:6px;">
                                    Confirmar Rejeição
                                </button>
                            </form>
                        <?php endif; ?>
                    </div>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>
    </div>
    <?php
}


function rmbz_store_admin_catalog_page() {
    global $wpdb;

    if ( ! empty($_POST['rmbz_add_dsp']) && check_admin_referer('rmbz_dsp_catalog_add') ) {
        $slug = sanitize_key( $_POST['new_slug'] ?? '' );
        $name = sanitize_text_field( $_POST['new_name'] ?? '' );
        $tier = (int) ($_POST['new_tier'] ?? 2);

        /* [1.5.164] Validação do logo_url:
           - Aceita URL absoluto (https://...)
           - Aceita URL relativo da Media Library do WP (esc_url_raw devolve trim)
           - Validação max 500 chars (= varchar coluna)
           - esc_url_raw remove caracteres perigosos */
        $logo_url = '';
        if ( ! empty( $_POST['new_logo_url'] ) ) {
            $raw = trim( (string) $_POST['new_logo_url'] );
            // Limita a 500 chars (= varchar coluna)
            if ( strlen( $raw ) > 500 ) {
                echo '<div class="notice notice-error is-dismissible"><p>URL do logo demasiado longo (máx. 500 caracteres).</p></div>';
            } else {
                $logo_url = esc_url_raw( $raw );
                // Defesa: se esc_url_raw rejeita (ex: javascript:), $logo_url fica vazio
                if ( empty( $logo_url ) && ! empty( $raw ) ) {
                    echo '<div class="notice notice-warning is-dismissible"><p>URL do logo inválido — loja foi adicionada sem logo.</p></div>';
                }
            }
        }

        if ( $slug && $name ) {
            $ok = rmbz_store_add_dsp( $slug, $name, $tier, $logo_url );
            echo '<div class="notice notice-' . ($ok ? 'success' : 'error') . ' is-dismissible"><p>'
                 . esc_html( $ok ? 'Loja adicionada.' : 'Erro: slug pode já existir.' ) . '</p></div>';
        }
    }

    if ( ! empty($_GET['toggle_slug']) && wp_verify_nonce( $_GET['_wpnonce'] ?? '', 'rmbz_dsp_toggle_' . $_GET['toggle_slug'] ) ) {
        $slug = sanitize_key( $_GET['toggle_slug'] );
        $current = (int) $wpdb->get_var( $wpdb->prepare(
            "SELECT is_active FROM " . rmbz_dsp_catalog_table() . " WHERE slug = %s",
            $slug
        ) );
        rmbz_store_toggle_dsp( $slug, ! $current );
        wp_redirect( admin_url('admin.php?page=rmbz-admin-distribution&tab=stores_catalog&toggled=1') );
        exit;
    }

    if ( ! empty($_GET['delete_slug']) && wp_verify_nonce( $_GET['_wpnonce'] ?? '', 'rmbz_dsp_delete_' . $_GET['delete_slug'] ) ) {
        rmbz_store_delete_dsp( sanitize_key( $_GET['delete_slug'] ) );
        wp_redirect( admin_url('admin.php?page=rmbz-admin-distribution&tab=stores_catalog&deleted=1') );
        exit;
    }

    if ( ! empty($_GET['toggled']) ) echo '<div class="notice notice-success is-dismissible"><p>Estado da loja actualizado.</p></div>';
    if ( ! empty($_GET['deleted']) ) echo '<div class="notice notice-success is-dismissible"><p>Loja apagada.</p></div>';

    // [1.5.144] Save da setting de custo
    if ( ! empty($_POST['rmbz_save_store_settings']) && check_admin_referer('rmbz_store_settings_save') ) {
        $fee = max(0, (int) ($_POST['rmbz_store_change_fee_subsequent'] ?? 0));
        $enabled = ! empty($_POST['rmbz_store_changes_enabled']) ? 1 : 0;
        update_option('rmbz_store_change_fee_subsequent', $fee);
        update_option('rmbz_store_changes_enabled', $enabled);
        echo '<div class="notice notice-success is-dismissible"><p>Definições guardadas.</p></div>';
    }

    $catalog = rmbz_store_get_catalog( false );
    $current_fee = (int) get_option('rmbz_store_change_fee_subsequent', 100);
    $is_enabled = (int) get_option('rmbz_store_changes_enabled', 1);
    ?>
    <div class="wrap">
        <h1>🏪 Catálogo de Lojas (DSPs)</h1>
        <p>Gere as lojas onde os artistas podem distribuir. Lojas <strong>built-in</strong> não podem ser apagadas (só desactivadas). Custom podem ser apagadas.</p>

        <h2 style="margin-top:24px;">⚙ Definições de Pedidos de Alteração</h2>
        <form method="post" style="background:#fff;padding:14px;border:1px solid #ddd;border-radius:4px;max-width:680px;">
            <?php wp_nonce_field('rmbz_store_settings_save'); ?>
            <table>
                <tr>
                    <td><label>Sistema activo</label></td>
                    <td>
                        <label>
                            <input type="checkbox" name="rmbz_store_changes_enabled" value="1" <?php checked($is_enabled, 1); ?>>
                            Permitir aos artistas pedir alterações de lojas
                        </label>
                    </td>
                </tr>
                <tr>
                    <td><label>Custo após 1ª alteração (MT)</label></td>
                    <td>
                        <input type="number" name="rmbz_store_change_fee_subsequent" value="<?php echo (int) $current_fee; ?>" min="0" style="width:120px;">
                        <span style="color:var(--rmbz-text-muted, #888);font-size:12px;margin-left:8px;">A 1ª alteração é sempre grátis. Remoções são sempre grátis.</span>
                    </td>
                </tr>
            </table>
            <button type="submit" name="rmbz_save_store_settings" value="1" class="button button-primary" style="margin-top:8px;">Salvar Definições</button>
        </form>

        <h2 style="margin-top:30px;">➕ Adicionar Loja Custom</h2>
        <form method="post" style="background:#fff;padding:14px;border:1px solid #ddd;border-radius:4px;max-width:680px;">
            <?php wp_nonce_field('rmbz_dsp_catalog_add'); ?>
            <table>
                <tr>
                    <td><label>Slug (sem espaços, ex: <code>my_store</code>)</label></td>
                    <td><input type="text" name="new_slug" required style="width:200px;" pattern="[a-z0-9_]+"></td>
                </tr>
                <tr>
                    <td><label>Nome a mostrar</label></td>
                    <td><input type="text" name="new_name" required style="width:300px;"></td>
                </tr>
                <tr>
                    <td><label>Tier</label></td>
                    <td>
                        <select name="new_tier">
                            <option value="1">Tier 1 — Essencial</option>
                            <option value="2" selected>Tier 2 — Regional</option>
                            <option value="3">Tier 3 — Nicho</option>
                        </select>
                    </td>
                </tr>
                <?php /* [1.5.164] Campo logo: upload (Media Library) OR URL externa */ ?>
                <tr>
                    <td style="vertical-align:top; padding-top:8px;"><label>Logo (opcional)</label></td>
                    <td>
                        <div class="rmbz-logo-uploader" style="display:flex; flex-direction:column; gap:8px;">
                            <!-- Preview da imagem actual -->
                            <div class="rmbz-logo-preview" style="display:flex; align-items:center; gap:10px;">
                                <img id="rmbz-new-logo-preview" src="" alt=""
                                     style="width:48px; height:48px; border-radius:50%; border:1px dashed #ccc; object-fit:cover; background:#f8f8f8; display:none;" />
                                <span id="rmbz-new-logo-empty" style="font-style:italic; color:#888; font-size:12px;">Sem logo (vai usar fallback genérico)</span>
                            </div>

                            <!-- Hidden field que guarda o valor final -->
                            <input type="hidden" name="new_logo_url" id="rmbz-new-logo-url" value="">

                            <!-- Opção 1: Media Library (upload) -->
                            <div>
                                <button type="button" class="button" id="rmbz-new-logo-pick">
                                    📂 Escolher da Biblioteca
                                </button>
                                <button type="button" class="button" id="rmbz-new-logo-clear" style="display:none;">
                                    🗑 Remover
                                </button>
                                <span style="margin-left:10px; color:#666;">ou</span>
                            </div>

                            <!-- Opção 2: URL externa -->
                            <div>
                                <input type="url" id="rmbz-new-logo-external"
                                       placeholder="https://exemplo.com/logo.png"
                                       style="width:100%;">
                                <p class="description" style="margin-top:4px;">
                                    URL de imagem externa (PNG, JPG, SVG, WebP). PRO TIP: usa Media Library para evitar links que podem desaparecer.
                                </p>
                            </div>
                        </div>
                    </td>
                </tr>
            </table>
            <button type="submit" name="rmbz_add_dsp" value="1" class="button button-primary" style="margin-top:8px;">Adicionar Loja</button>
        </form>

        <?php /* [1.5.164] JS para integrar Media Library + sincronizar URL externa */
              /* Nota: este script é admin (não shortcode), logo `&&` não é
                 escapado pelo WordPress. Seguro usar normalmente. */ ?>
        <script>
        jQuery(document).ready(function($) {
            var $hidden  = $('#rmbz-new-logo-url');
            var $preview = $('#rmbz-new-logo-preview');
            var $empty   = $('#rmbz-new-logo-empty');
            var $clear   = $('#rmbz-new-logo-clear');
            var $external = $('#rmbz-new-logo-external');
            var mediaFrame = null;

            function setLogo(url) {
                $hidden.val(url);
                if (url) {
                    $preview.attr('src', url).show();
                    $empty.hide();
                    $clear.show();
                } else {
                    $preview.attr('src', '').hide();
                    $empty.show();
                    $clear.hide();
                }
            }

            // Botão Media Library
            $('#rmbz-new-logo-pick').on('click', function(e) {
                e.preventDefault();
                if (mediaFrame) { mediaFrame.open(); return; }
                mediaFrame = wp.media({
                    title: 'Escolher logo da loja',
                    button: { text: 'Usar este logo' },
                    library: { type: 'image' },
                    multiple: false
                });
                mediaFrame.on('select', function() {
                    var attachment = mediaFrame.state().get('selection').first().toJSON();
                    setLogo(attachment.url);
                    $external.val(''); // limpar campo URL externa para evitar confusão
                });
                mediaFrame.open();
            });

            // Botão Remover
            $clear.on('click', function(e) {
                e.preventDefault();
                setLogo('');
                $external.val('');
            });

            // Sincronizar URL externa para hidden
            $external.on('input change blur', function() {
                var url = $.trim($(this).val());
                if (url && /^https?:\/\//i.test(url)) {
                    setLogo(url);
                } else if (!url) {
                    // Só limpa se foi escrito e apagado, não se MediaLib seleccionou
                    if ($hidden.val() === $external.data('lastval')) {
                        setLogo('');
                    }
                }
                $external.data('lastval', url);
            });
        });
        </script>

        <?php /* [1.5.164] Garantir que o WP carrega scripts da Media Library nesta página */ ?>
        <?php wp_enqueue_media(); ?>

        <h2 style="margin-top:30px;">📋 Catálogo Actual (<?php echo count($catalog); ?>)</h2>
        <table class="wp-list-table widefat fixed striped">
            <thead><tr>
                <th width="60">Tier</th>
                <th width="180">Slug</th>
                <th>Nome</th>
                <th width="100">Tipo</th>
                <th width="100">Estado</th>
                <th width="180">Acções</th>
            </tr></thead>
            <tbody>
            <?php foreach ( $catalog as $slug => $meta ): ?>
                <tr>
                    <td><strong>T<?php echo (int) $meta['tier']; ?></strong></td>
                    <td><code><?php echo esc_html($slug); ?></code></td>
                    <td>
                        <?php /* [1.5.163] Logo do DSP lado-a-lado com nome */ ?>
                        <div style="display:flex; align-items:center; gap:8px;">
                            <?php if ( function_exists('rmbz_service_logo') ): ?>
                                <span style="display:inline-flex; align-items:center; flex-shrink:0;"><?php echo rmbz_service_logo($slug, ['size' => 24]); ?></span>
                            <?php endif; ?>
                            <span><?php echo esc_html($meta['name']); ?></span>
                        </div>
                    </td>
                    <td>
                        <?php if ( ! empty($meta['is_builtin']) ): ?>
                            <span style="color:var(--rmbz-info-strong, #2563eb);">Built-in</span>
                        <?php else: ?>
                            <span style="color:var(--rmbz-accent, #a855f7);">Custom</span>
                        <?php endif; ?>
                    </td>
                    <td>
                        <?php if ( ! empty($meta['is_active']) ): ?>
                            <span style="color:var(--rmbz-success, #10b981);font-weight:600;">✓ Activa</span>
                        <?php else: ?>
                            <span style="color:var(--rmbz-text-muted, #888);">Inactiva</span>
                        <?php endif; ?>
                    </td>
                    <td>
                        <a href="<?php echo esc_url( wp_nonce_url( admin_url('admin.php?page=rmbz-admin-distribution&tab=stores_catalog&toggle_slug=' . $slug), 'rmbz_dsp_toggle_' . $slug ) ); ?>"
                           class="button button-small">
                            <?php echo $meta['is_active'] ? 'Desactivar' : 'Activar'; ?>
                        </a>
                        <?php if ( empty($meta['is_builtin']) ): ?>
                            <a href="<?php echo esc_url( wp_nonce_url( admin_url('admin.php?page=rmbz-admin-distribution&tab=stores_catalog&delete_slug=' . $slug), 'rmbz_dsp_delete_' . $slug ) ); ?>"
                               class="button button-small"
                               onclick="return confirm('Apagar esta loja? Releases que a usem vão deixar de a ter na lista após o próximo update.');"
                               style="color:var(--rmbz-danger-strong, #dc2626);">Apagar</a>
                        <?php endif; ?>
                    </td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <?php
}
