<?php
// includes/admin/admin-handlers.php

/**
 * Handlers de Ações Administrativas do 1MZ For Artists.
 * Processa aprovações, gestão de contratos, financeiro e utilizadores.
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Acesso direto bloqueado
}

/**
 * ==========================================================================
 * 0. FUNÇÃO DE SEGURANÇA: Destruir Ficheiros Físicos do Servidor
 * ==========================================================================
 */
function rmbz_destruir_ficheiro_fisico( $file_url ) {
    if ( empty( $file_url ) || strpos( $file_url, 'http' ) === false ) return;

    // [1.5.406] Ficheiro no S3? Apaga o objeto do bucket.
    if ( function_exists( 'rmbz_s3_key_from_url' ) ) {
        $s3_key = rmbz_s3_key_from_url( $file_url );
        if ( $s3_key !== '' ) {
            rmbz_s3_delete( $s3_key );
            return;
        }
    }

    // Descobrir a pasta de uploads do WordPress no servidor
    $upload_dir = wp_upload_dir();
    $base_url   = $upload_dir['baseurl'];
    $base_dir   = $upload_dir['basedir'];

    // Se o ficheiro estiver no nosso servidor, converte o Link num Caminho de Pasta
    if ( strpos( $file_url, $base_url ) !== false ) {
        $file_path = str_replace( $base_url, $base_dir, $file_url );

        // Se o ficheiro existir no disco rígido, destrói-o definitivamente!
        if ( file_exists( $file_path ) && is_file( $file_path ) ) {
            unlink( $file_path );
        }
    }
}

/**
 * ==========================================================================
 * 1. GESTÃO DE AUDIÇÕES (SUBMISSÕES - SITE / SOCIAL)
 * ==========================================================================
 */

/**
 * [1.6.118.128] Offset do fuso do site em segundos (deriva do WordPress).
 * Guard function_exists para não colidir com a mesma função noutro ficheiro.
 */
if ( ! function_exists( 'rmbz_pub_site_offset_secs' ) ) {
    function rmbz_pub_site_offset_secs() {
        $local = (int) current_time( 'timestamp' );
        $utc   = (int) current_time( 'timestamp', true );
        $diff  = $local - $utc;
        if ( $diff >= -12 * HOUR_IN_SECONDS && $diff <= 14 * HOUR_IN_SECONDS ) {
            return $diff;
        }
        $gmt = get_option( 'gmt_offset', '' );
        if ( $gmt !== '' && $gmt !== null ) {
            return (int) round( (float) $gmt * HOUR_IN_SECONDS );
        }
        return 0;
    }
}

/**
 * APROVAR MÚSICA RÁPIDO (Ação na Lista)
 */
function onemz_approve_submission() {
    if ( ! isset( $_GET['sub_id'], $_GET['_wpnonce'] ) ) return;
    $sub_id = absint( $_GET['sub_id'] );
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'onemz_approve_nonce_' . $sub_id ) ) wp_die( 'Falha de segurança.' );
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );

    global $wpdb;
    $table_subs = $wpdb->prefix . 'rmbz_artist_submissions';
    $submission = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table_subs} WHERE id = %d", $sub_id ) );
    if ( ! $submission ) wp_die( 'Submissão não encontrada.' );

    $wpdb->update( $table_subs, ['review_status' => 'aprovado'], ['id' => $sub_id], ['%s'], ['%d'] );

    // Notificar Artista
    $artist_email = $wpdb->get_var($wpdb->prepare("SELECT user_email FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s", $submission->artist_telemovel));
    if ($artist_email && strpos($artist_email, '@1mz.local') === false) {
        $tpl = get_option('rmbz_email_templates');
        $sender_name = !empty($tpl['sender_name']) ? $tpl['sender_name'] : get_bloginfo('name');
        $sender_email = is_email($tpl['sender_email'] ?? '') ? $tpl['sender_email'] : get_option('admin_email');

        $subject = "✅ Aprovado: " . $submission->music_title;
        $body = "Olá,\n\nTemos boas notícias! A sua submissão para a obra '{$submission->music_title}' foi avaliada e APROVADA pela nossa equipa.\n\nIremos processar a publicação em breve. Acompanhe o estado final (e os links) diretamente na sua conta.\n\nUm abraço,\nEquipa " . $sender_name;
        
        if (function_exists('rmbz_build_html_email')) {
            $html_body = rmbz_build_html_email($body, $sender_name);
            $headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $sender_name . ' <' . $sender_email . '>'];
            $sent = wp_mail($artist_email, $subject, $html_body, $headers);
            if (function_exists('rmbz_log_sent_email')) rmbz_log_sent_email($artist_email, $subject, $html_body, $sent ? 'sucesso' : 'falha');
        }
    }
    wp_redirect( admin_url( 'admin.php?page=rmbz-envios-site&status=submission_approved' ) ); exit;
}
add_action( 'admin_post_onemz_approve_submission', 'onemz_approve_submission' );

/**
 * REJEITAR MÚSICA RÁPIDO
 */
function onemz_reject_submission() {
    if ( ! isset( $_GET['sub_id'], $_GET['_wpnonce'] ) ) return;
    $sub_id = absint( $_GET['sub_id'] );
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'onemz_reject_nonce_' . $sub_id ) ) wp_die( 'Falha de segurança.' );
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );

    $reason = isset($_GET['reason']) ? sanitize_textarea_field( urldecode($_GET['reason']) ) : 'Sem motivo especificado.';

    global $wpdb;
    $wpdb->update( $wpdb->prefix . 'rmbz_artist_submissions', ['review_status' => 'rejeitado', 'rejection_reason' => $reason], ['id' => $sub_id], ['%s', '%s'], ['%d'] );
    if ( function_exists('onemz_send_rejection_notification') ) onemz_send_rejection_notification( $sub_id, $reason );

    wp_redirect( admin_url( 'admin.php?page=rmbz-envios-site&status=submission_rejected' ) ); exit;
}
add_action( 'admin_post_onemz_reject_submission', 'onemz_reject_submission' );

/**
 * APAGAR MÚSICA E DESTRUIR FICHEIROS FÍSICOS
 */
function onemz_delete_submission() {
    if ( ! isset( $_GET['sub_id'], $_GET['_wpnonce'] ) ) return;
    $sub_id = absint( $_GET['sub_id'] );
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'onemz_delete_nonce_' . $sub_id ) ) wp_die( 'Falha de segurança.' );
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );

    global $wpdb;
    $table_subs = $wpdb->prefix . 'rmbz_artist_submissions';

    $submission = $wpdb->get_row( $wpdb->prepare( "SELECT music_file_url, extra_data FROM {$table_subs} WHERE id = %d", $sub_id ) );

    if ( $submission ) {
        rmbz_destruir_ficheiro_fisico( $submission->music_file_url );
        if ( ! empty( $submission->extra_data ) ) {
            $extra = json_decode( $submission->extra_data, true );
            if ( is_array( $extra ) ) {
                if ( ! empty( $extra['cover_image'] ) ) rmbz_destruir_ficheiro_fisico( $extra['cover_image'] );
                if ( ! empty( $extra['thumbnail_url'] ) ) rmbz_destruir_ficheiro_fisico( $extra['thumbnail_url'] );
                
                if ( ! empty( $extra['album_tracks'] ) && is_array( $extra['album_tracks'] ) ) {
                    foreach ( $extra['album_tracks'] as $track ) {
                        if ( ! empty( $track['url'] ) ) rmbz_destruir_ficheiro_fisico( $track['url'] );
                    }
                }
            }
        }
    }

    $deleted = $wpdb->delete( $table_subs, ['id' => $sub_id], ['%d'] );
    if ( $deleted ) { wp_redirect( admin_url( 'admin.php?page=rmbz-envios-site&status=submission_deleted' ) ); exit; } 
    else { wp_die('Erro ao apagar a submissão.'); }
}
add_action( 'admin_post_onemz_delete_submission', 'onemz_delete_submission' );

/**
 * ==========================================================================
 * ATUALIZAR ESTADO E ORÇAMENTO DE UM PROJETO (VIDEOCLIPE)
 * ==========================================================================
 */
function onemz_admin_update_project() {
    if ( ! isset( $_POST['project_id'], $_POST['_wpnonce'] ) ) wp_die( 'Dados em falta.' );
    $project_id = absint( $_POST['project_id'] );
    if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'onemz_update_project_nonce' ) ) wp_die( 'Falha de segurança.' );
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );

   $is_paid = isset($_POST['is_paid']) ? absint($_POST['is_paid']) : 0;
    $production_status = isset($_POST['production_status']) ? sanitize_text_field($_POST['production_status']) : 'pre-production';
    $script_content = isset($_POST['script_content']) ? wp_kses_post($_POST['script_content']) : '';
    $final_video_url = isset($_POST['final_video_url']) ? esc_url_raw($_POST['final_video_url']) : '';
    $youtube_url     = isset($_POST['youtube_url']) ? esc_url_raw($_POST['youtube_url']) : '';
    $release_date    = isset($_POST['release_date']) ? sanitize_text_field($_POST['release_date']) : '';

    global $wpdb;
    $table_projects = $wpdb->prefix . 'rmbz_artist_projects';
    
    // Obter dados antigos incluindo o 'extra_data' para guardar os múltiplos orçamentos
    // [1.5.373] Inclui campos extra para registar alterações no histórico.
    $old_project = $wpdb->get_row($wpdb->prepare("SELECT extra_data, production_status, scenario_type, project_title, artist_telemovel, artist_feedback, final_video_url, youtube_url, release_date, script_content, is_paid FROM {$table_projects} WHERE id = %d", $project_id));
    $old_status = $old_project ? $old_project->production_status : '';
    $extra_proj = $old_project && !empty($old_project->extra_data) ? json_decode($old_project->extra_data, true) : [];
    if ( ! is_array($extra_proj) ) $extra_proj = [];

    // [1.5.409] BLOQUEIO POR TAXA DE VISTORIA: com a filmagem terminada e a
    // vistoria por pagar, a equipa NÃO pode entregar — travamos a passagem a
    // "done" e a definição/alteração dos links de entrega (HD/YouTube).
    $rmbz_vistoria_notice = '';
    if ( function_exists('rmbz_team_vistoria_status') ) {
        $rmbz_vs_probe = rmbz_team_vistoria_status( $project_id );
        $rmbz_vs_target = in_array( $production_status, [ 'editing', 'done' ], true ) && $rmbz_vs_probe['left'] > 0;
        if ( $rmbz_vs_target ) {
            $rmbz_vs_valor = number_format( $rmbz_vs_probe['left'], 2, ',', '.' ) . ' MT';
            if ( $production_status === 'done' ) {
                $production_status = 'editing'; // não deixa concluir
                $rmbz_vistoria_notice = ' 🔒 O projeto NÃO pode ser concluído — taxa de vistoria por pagar (' . $rmbz_vs_valor . ').';
            }
            // Entrega bloqueada: mantém os links antigos, ignora os novos.
            if ( $final_video_url !== (string) ( $old_project->final_video_url ?? '' )
                 || $youtube_url !== (string) ( $old_project->youtube_url ?? '' ) ) {
                $final_video_url = (string) ( $old_project->final_video_url ?? '' );
                $youtube_url     = (string) ( $old_project->youtube_url ?? '' );
                $rmbz_vistoria_notice .= ' 🔒 Entrega bloqueada (links HD/YouTube não guardados) — vistoria por pagar (' . $rmbz_vs_valor . ').';
            }
        }
    }

    // [1.6.392] "Desativar Notificação" deste projeto — quando ligado, NENHUMA
    // mudança deste projeto notifica o artista (push/email/sino). Guardado em
    // extra_data['notifications_muted'].
    $extra_proj['notifications_muted'] = isset($_POST['notify_muted']) ? 1 : 0;
    $rmbz_notify_muted = ! empty($extra_proj['notifications_muted']);

    // [1.6.118.49] Defensivo — array para tracking de items recém criados
    // que precisam de decisão do artista. Pode ficar vazio (branch sem
    // budget_accepted_location não toca aqui).
    $newly_undecided_descs = [];

    // PROCESSAR MÚLTIPLOS ORÇAMENTOS (Se o artista ainda não tiver aceite nenhum)
    if (empty($extra_proj['budget_accepted_location'])) {
        $previous_proposed_budgets = isset( $extra_proj['proposed_budgets'] ) && is_array( $extra_proj['proposed_budgets'] )
            ? $extra_proj['proposed_budgets']
            : [];
        $locations = isset($_POST['budget_loc_name']) ? $_POST['budget_loc_name'] : [];
        $proposed_budgets = [];
        
        foreach ($locations as $idx => $loc_name) {
            $loc_name = sanitize_text_field($loc_name);
            if (empty($loc_name)) continue;
            
            $cats     = isset($_POST["budget_cat_{$idx}"])     ? $_POST["budget_cat_{$idx}"]     : [];
            $descs    = isset($_POST["budget_desc_{$idx}"])    ? $_POST["budget_desc_{$idx}"]    : [];
            $costs    = isset($_POST["budget_cost_{$idx}"])    ? $_POST["budget_cost_{$idx}"]    : [];
            // [1.6.118.42] Modo misto desde a proposta — admin pode marcar
            // Obrigatório/Negociável por item ANTES do artista escolher localização.
            // Default seguro: 1 (obrigatório) se não vier no POST.
            $mustpays = isset($_POST["budget_must_pay_{$idx}"]) ? $_POST["budget_must_pay_{$idx}"] : [];

            $items = []; $total_loc = 0;
            for ($i = 0; $i < count($descs); $i++) {
                if (!empty($descs[$i])) {
                    $c = (float)$costs[$i];
                    $cat = !empty($cats[$i]) ? sanitize_text_field($cats[$i]) : 'Geral';
                    $mp  = isset($mustpays[$i]) ? intval($mustpays[$i]) : 1;
                    $items[] = [
                        'cat'            => $cat,
                        'desc'           => sanitize_text_field($descs[$i]),
                        'cost'           => $c,
                        'must_pay'       => $mp,
                        'artist_assumes' => 0, // decisão do artista vem no contrato
                    ];
                    $total_loc += $c;
                }
            }
            if (!empty($items)) {
                $proposed_budgets[$loc_name] = ['items' => $items, 'total' => $total_loc];
            }
        }
        
        $budgets_changed = wp_json_encode( $previous_proposed_budgets ) !== wp_json_encode( $proposed_budgets );
        $location_timer_states = [ 'pre-production', 'kyc_review' ];

        if ( ! empty( $proposed_budgets ) && in_array( $production_status, $location_timer_states, true ) ) {
            // O relógio começa quando o admin envia opções válidas ao artista.
            // Alterar as opções reinicia os 2 dias para o artista avaliar.
            if ( $budgets_changed || empty( $extra_proj['location_choice_set_at'] ) ) {
                $extra_proj['location_choice_set_at'] = current_time( 'mysql', 1 );
                unset( $extra_proj['location_cancel_warned'] );
            }
        } else {
            // Opções vazias ou projecto ainda não aprovado não iniciam prazo.
            unset( $extra_proj['location_choice_set_at'], $extra_proj['location_cancel_warned'] );
        }

        $extra_proj['proposed_budgets'] = $proposed_budgets;
        // Limpa os campos antigos da BD enquanto ele não escolher para não dar conflito
        $details_json = wp_json_encode([]);
        $total_calc = 0;
    } else {
        // JÁ ESCOLHEU UM LOCAL: Administrador edita a tabela e os Vistos (Checklist)
        $cats     = isset($_POST['budget_cat']) ? $_POST['budget_cat'] : [];
        $descs    = isset($_POST['budget_desc']) ? $_POST['budget_desc'] : [];
        $costs    = isset($_POST['budget_cost']) ? $_POST['budget_cost'] : [];
        $checks   = isset($_POST['budget_checked']) ? $_POST['budget_checked'] : [];   // Vistos da equipa
        $mustpays = isset($_POST['budget_must_pay']) ? $_POST['budget_must_pay'] : []; // [1.6.118.34] Obrigatório?
        $assumes  = isset($_POST['budget_assumes']) ? $_POST['budget_assumes'] : [];   // Hidden snapshot

        // [1.6.118.43] Carregar items ACTUAIS da BD para preservar 'artist_assumes'
        // mesmo que o JS/POST falhe. Admin não deve poder mudar a decisão do
        // artista — só o artista pode (no momento de aceitar contrato).
        // Match por descrição+categoria (estável o suficiente para items existentes).
        $old_details_raw = $wpdb->get_var( $wpdb->prepare(
            "SELECT scenario_details FROM {$table_projects} WHERE id = %d",
            $project_id
        ) );
        $old_details = ! empty( $old_details_raw ) ? json_decode( $old_details_raw, true ) : [];
        if ( ! is_array( $old_details ) ) $old_details = [];
        // Index por chave estável: cat|desc
        $old_by_key = [];
        foreach ( $old_details as $od ) {
            $k = ( $od['cat'] ?? 'Geral' ) . '|' . ( $od['desc'] ?? '' );
            $old_by_key[ $k ] = $od;
        }

        // [1.6.118.44] Saber se contrato já foi aceite — se sim, items novos
        // negociáveis precisam de mini-prompt para artista decidir.
        $contract_already_accepted = ! empty( $extra_proj['contract_accepted'] );

        // [1.6.118.49] Contar items que ficam undecided NESTE save (novos +
        // re-classificações). Usado mais abaixo para gatilho FCM ao artista.
        $newly_undecided_descs = [];

        $items = []; $total_calc = 0;
        for ($i = 0; $i < count($descs); $i++) {
            if (!empty($descs[$i])) {
                $c = (float)$costs[$i];
                $cat = !empty($cats[$i]) ? sanitize_text_field($cats[$i]) : 'Geral';
                $desc_clean = sanitize_text_field($descs[$i]);
                $is_checked   = isset($checks[$i])   ? intval($checks[$i])   : 0;
                $is_must_pay  = isset($mustpays[$i]) ? intval($mustpays[$i]) : 1;
                $is_assumed   = isset($assumes[$i])  ? intval($assumes[$i])  : 0;

                // [1.6.118.43] Se item existe na BD (match cat|desc) E tinha
                // artist_assumes=1, PRESERVAR o valor da BD em vez de confiar
                // só no hidden do POST (que pode ter sido apagado por JS).
                $key = $cat . '|' . $desc_clean;
                $is_decided = 1; // default: items legados/novos pre-contrato assumem decididos
                $just_became_undecided = false; // [1.6.118.49]

                if ( isset( $old_by_key[ $key ] ) ) {
                    // ITEM EXISTENTE — preservar contexto
                    $old_item = $old_by_key[ $key ];
                    $old_assumed = ! empty( $old_item['artist_assumes'] ) ? 1 : 0;
                    $old_must_pay = isset( $old_item['must_pay'] ) ? (int) $old_item['must_pay'] : 1;
                    $old_decided = isset( $old_item['decided'] ) ? (int) $old_item['decided'] : 1; // legado = decidido

                    if ( $is_must_pay === 0 && $old_must_pay === 0 ) {
                        // Negociável → continua negociável: preservar decisão e flag
                        if ( $old_assumed === 1 ) $is_assumed = 1;
                        $is_decided = $old_decided;
                    } elseif ( $is_must_pay === 0 && $old_must_pay === 1 && $contract_already_accepted ) {
                        // ADMIN MUDOU obrigatório → negociável APÓS contrato aceite.
                        // Artista NUNCA decidiu sobre isto → marcar undecided.
                        $is_decided = 0;
                        $is_assumed = 0;
                        $just_became_undecided = true; // NOTIFICAR
                    } elseif ( $is_must_pay === 1 ) {
                        // Obrigatório: sempre decidido (não há decisão a tomar)
                        $is_decided = 1;
                    }
                } else {
                    // ITEM NOVO
                    if ( $is_must_pay === 0 && $contract_already_accepted ) {
                        // Negociável adicionado APÓS contrato → artista precisa decidir
                        $is_decided = 0;
                        $is_assumed = 0;
                        $just_became_undecided = true; // NOTIFICAR
                    } else {
                        // Negociável antes do contrato → vai aparecer no flow normal
                        // Obrigatório → não precisa decisão
                        $is_decided = 1;
                    }
                }

                if ( $just_became_undecided ) {
                    $newly_undecided_descs[] = $desc_clean;
                }

                // Coerência: se must_pay=1, artist_assumes força a 0
                if ( $is_must_pay === 1 ) $is_assumed = 0;

                $items[] = [
                    'cat'             => $cat,
                    'desc'            => $desc_clean,
                    'cost'            => $c,
                    'checked'         => $is_checked,
                    'must_pay'        => $is_must_pay,
                    'artist_assumes'  => $is_assumed,
                    'decided'         => $is_decided,
                ];
                $total_calc += $c;
            }
        }
        $details_json = wp_json_encode($items);
    }

// GUARDAR A DATA LIMITE DO CONTRATO
    if (isset($_POST['contract_deadline'])) {
        $deadline_val = sanitize_text_field($_POST['contract_deadline']);
        if (!empty($deadline_val)) {
            $extra_proj['contract_deadline'] = strtotime($deadline_val);
        } else {
            unset($extra_proj['contract_deadline']); // Remove se o admin apagar a data
        }
    }
    
    // [1.6.118.64] DATAS DE GRAVAÇÃO (intervalo início + fim)
    // Admin define; artista pode confirmar ou propor outra data (via app/site).
    // Guardadas em extra_data como ISO 'Y-m-d'. Quando o admin MUDA as datas,
    // o flag de confirmação do artista é resetado (tem de re-confirmar) e
    // qualquer contra-proposta pendente é limpa.
    // [1.6.118.65] Flags para disparar notificação multi-canal depois do
    // update da BD (só se as datas foram efectivamente definidas/mudadas).
    $filming_notify        = false;
    $filming_notify_start  = '';
    $filming_notify_end    = '';
    if ( isset( $_POST['filming_start'] ) || isset( $_POST['filming_end'] ) ) {
        $fs_raw = isset( $_POST['filming_start'] ) ? sanitize_text_field( $_POST['filming_start'] ) : '';
        $fe_raw = isset( $_POST['filming_end'] )   ? sanitize_text_field( $_POST['filming_end'] )   : '';

        // Validar formato Y-m-d (input type=date) e normalizar.
        $fs = ( $fs_raw !== '' && strtotime( $fs_raw ) ) ? date( 'Y-m-d', strtotime( $fs_raw ) ) : '';
        $fe = ( $fe_raw !== '' && strtotime( $fe_raw ) ) ? date( 'Y-m-d', strtotime( $fe_raw ) ) : '';

        // Coerência: se ambas definidas e fim < início, troca.
        if ( $fs !== '' && $fe !== '' && strtotime( $fe ) < strtotime( $fs ) ) {
            $tmp = $fs; $fs = $fe; $fe = $tmp;
        }

        $prev_fs = isset( $extra_proj['filming_start'] ) ? (string) $extra_proj['filming_start'] : '';
        $prev_fe = isset( $extra_proj['filming_end'] )   ? (string) $extra_proj['filming_end']   : '';
        $has_pending_proposal = ! empty( $extra_proj['filming_proposed_by_artist'] );
        // Guardar as mesmas datas enquanto existe contraproposta significa
        // que a equipa decidiu manter as datas originais. Reabre o prazo e
        // volta a notificar o artista.
        $datas_mudaram = ( $fs !== $prev_fs || $fe !== $prev_fe || $has_pending_proposal );

        if ( $fs === '' && $fe === '' ) {
            // Admin limpou ambas as datas → remove tudo relacionado
            unset(
                $extra_proj['filming_start'], $extra_proj['filming_end'],
                $extra_proj['filming_set_by'], $extra_proj['filming_set_at'],
                $extra_proj['filming_confirmed_by_artist'],
                $extra_proj['filming_proposed_by_artist'],
                $extra_proj['filming_cancel_warned']
            );
        } else {
            $extra_proj['filming_start']  = $fs;
            $extra_proj['filming_end']    = $fe;
            if ( $datas_mudaram ) {
                $extra_proj['filming_set_by'] = 'admin';
                $extra_proj['filming_set_at'] = current_time( 'mysql', 1 );
                // Reset confirmação + limpar contra-proposta pendente
                $extra_proj['filming_confirmed_by_artist'] = false;
                unset(
                    $extra_proj['filming_proposed_by_artist'],
                    $extra_proj['filming_cancel_warned']
                );

                // Marcar para notificar o artista (só quando há pelo menos
                // uma data E o projecto está aprovado — verificado no disparo)
                $filming_notify       = true;
                $filming_notify_start = $fs;
                $filming_notify_end   = $fe;
            }
        }
    }

    // Grava tudo na base de dados (incluindo o extra_data que guarda as localizações)
    // GESTÃO DO GUIÃO
        if (isset($_POST['script_url'])) { $extra_proj['script_url'] = esc_url_raw($_POST['script_url']); }

        // [1.5.407] EQUIPA DO VIDEOCLIPE — as checkboxes nunca eram GUARDADAS
        // (o bloco existia no form mas o handler ignorava team_member_ids;
        // além disso o form aninhado da taxa partia o HTML e deixava a secção
        // fora do form principal). O marcador team_section_present distingue
        // "secção presente com zero marcados" (limpar equipa) de um POST
        // antigo sem a secção.
        if ( isset( $_POST['team_section_present'] ) ) {
            $rmbz_team_ids = ( isset( $_POST['team_member_ids'] ) && is_array( $_POST['team_member_ids'] ) )
                ? array_values( array_unique( array_filter( array_map( 'intval', $_POST['team_member_ids'] ) ) ) )
                : [];
            $extra_proj['team_member_ids'] = $rmbz_team_ids;

            $rmbz_team_req = [];
            if ( isset( $_POST['team_requested_by'] ) && is_array( $_POST['team_requested_by'] ) ) {
                foreach ( $_POST['team_requested_by'] as $rmbz_tm_id => $rmbz_tm_who ) {
                    $rmbz_team_req[ (int) $rmbz_tm_id ] = ( $rmbz_tm_who === 'artist' ) ? 'artist' : 'mz';
                }
            }
            $extra_proj['team_requested_by'] = $rmbz_team_req;

            // [1.5.408] QUAIS cargos cada pessoa exerce NESTE projeto
            // (member_id => [cargos]) — o artista vê só estes. Só guarda
            // para membros selecionados.
            $rmbz_team_proles = [];
            if ( isset( $_POST['team_project_roles'] ) && is_array( $_POST['team_project_roles'] ) ) {
                foreach ( $_POST['team_project_roles'] as $rmbz_tm_id => $rmbz_tm_roles ) {
                    $rmbz_tm_id = (int) $rmbz_tm_id;
                    if ( ! in_array( $rmbz_tm_id, $rmbz_team_ids, true ) || ! is_array( $rmbz_tm_roles ) ) continue;
                    $clean = array_values( array_filter( array_map(
                        fn( $r ) => sanitize_text_field( wp_unslash( (string) $r ) ),
                        $rmbz_tm_roles
                    ) ) );
                    if ( ! empty( $clean ) ) $rmbz_team_proles[ $rmbz_tm_id ] = $clean;
                }
            }
            $extra_proj['team_project_roles'] = $rmbz_team_proles;
        }
        // [1.6.118.36] Fase 3 — removido o tratamento de 'allow_self_fund' aqui.
        // Já não há checkbox no form admin (modo misto per-item substituiu).
        // O valor antigo em $extra_proj fica preservado mas inerte.

        // [1.6.118.30] Pitch da Produção — texto inspirador para o artista
        // ler no app/site. Limpo com sanitize_textarea_field (preserva quebras
        // de linha mas remove tags). Em branco = remove.
        // [1.6.118.40] Capturar flag para disparar FCM push depois do save.
        $pitch_changed     = false; // texto mudou (1ª vez OU edição)
        $pitch_is_new      = false; // antes não existia, agora sim
        $pitch_new_preview = '';
        if ( isset( $_POST['production_pitch'] ) ) {
            $pitch_raw = sanitize_textarea_field( wp_unslash( $_POST['production_pitch'] ) );
            $prev_pitch = isset( $extra_proj['production_pitch'] ) ? (string) $extra_proj['production_pitch'] : '';
            if ( $pitch_raw === '' ) {
                unset( $extra_proj['production_pitch'], $extra_proj['production_pitch_updated_at'] );
                // Apagar pitch NÃO dispara push (não queremos avisar de "deleção")
            } else {
                if ( $prev_pitch !== $pitch_raw ) {
                    $extra_proj['production_pitch_updated_at'] = current_time( 'mysql', 1 );
                    $pitch_changed     = true;
                    $pitch_is_new      = ( $prev_pitch === '' );
                    $pitch_new_preview = $pitch_raw;
                }
                $extra_proj['production_pitch'] = $pitch_raw;
            }
        }
        
    // [1.6.118.55] Marcar approved_at quando o projeto SAI de pending_review
    // para um estado aprovado (qualquer um excepto rejected/correction).
    // Só marca da 1ª vez (não sobrescreve se admin re-aprovar depois de correction).
    if ( $old_status === 'pending_review'
         && ! in_array( $production_status, [ 'pending_review', 'rejected', 'correction' ], true )
         && empty( $extra_proj['approved_at'] ) ) {
        $extra_proj['approved_at'] = current_time( 'mysql', 1 );
    }

    $wpdb->update(
        $table_projects,
        [
            'scenario_cost'     => $total_calc,
            'scenario_details'  => $details_json,
            'is_paid'           => $is_paid,
            'production_status' => $production_status,
            'script_content'    => $script_content,
            'final_video_url'   => $final_video_url,
            'youtube_url'       => $youtube_url,
            'release_date'      => $release_date,
            'extra_data'        => wp_json_encode($extra_proj) // <- Adicionado aqui
        ],
        ['id' => $project_id],
        ['%f', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s'], ['%d'] // Adicionado um '%s'
    );

    // =====================================================================
    // GATILHO 1: REJEIÇÃO E PEDIDO DE CORREÇÃO (COM MENSAGEM MOTIVACIONAL)
    // =====================================================================
    $rejection_reason = isset($_POST['rejection_reason']) ? sanitize_textarea_field($_POST['rejection_reason']) : '';

    if (in_array($production_status, ['rejected', 'correction']) && $old_status !== $production_status && !empty($rejection_reason)) {
        
        // 1. Gravar a rejeição no Chat do Projeto para o artista ver na plataforma
        $timestamp = current_time('d/m/Y H:i');
        $prefix = ($production_status === 'rejected') ? '❌ PROJETO REJEITADO' : '🔄 CORREÇÃO SOLICITADA';
        $chat_entry = "[$timestamp] <strong>Equipa 1MZ (Aviso de Sistema):</strong> {$prefix} - {$rejection_reason}";
        
        $current_feedback = $old_project->artist_feedback;
        $updated_feedback = empty($current_feedback) ? $chat_entry : $current_feedback . "\n\n" . $chat_entry;
        
        $wpdb->update($table_projects, ['artist_feedback' => $updated_feedback], ['id' => $project_id], ['%s'], ['%d']);
        
        // 2. Disparar o E-mail Motivaconal
        $artista = $wpdb->get_row($wpdb->prepare("SELECT user_email, user_nome FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s", $old_project->artist_telemovel));
        
        if ($artista && !empty($artista->user_email) && strpos($artista->user_email, '@1mz.local') === false) {
            $nome = $artista->user_nome;
            $musica = $old_project->project_title;
            
            if ($production_status === 'rejected') {
                $subject = "Aviso sobre a candidatura do videoclipe: {$musica}";
                $body = "Olá <strong>{$nome}</strong>,<br><br>
                Agradecemos imenso o envio da tua música <strong>'{$musica}'</strong> para gravação de videoclipe.<br><br>
                A nossa equipa de curadoria analisou a tua proposta com muito cuidado, mas infelizmente <strong>não avançaremos com a produção neste momento</strong>.<br><br>
                <strong>Motivo da Decisão:</strong><br>
                <div style='background:#ffebee; border-left:4px solid #c0392b; padding:15px; margin:10px 0; color:#c0392b; font-style:italic;'>
                {$rejection_reason}
                </div>
                <br>
                <strong style='color:#f0b90b; font-size:18px;'>Não desanimes! 💪</strong><br>
                A indústria da música é feita de persistência e evolução constante. Continua a trabalhar no teu talento, a criar coisas novas e a melhorar a tua arte. O facto de não ter sido aceite agora, não define o teu futuro.<br><br>
                Assim que tiveres uma nova obra preparada, não hesites em submeter uma nova candidatura!<br><br>
                Estamos sempre aqui para apoiar o teu crescimento.<br><br>
                Um abraço de força,<br><strong>Equipa 1MusicMoz</strong>";
            } else {
                $subject = "Ação Necessária: Candidatura de Videoclipe ({$musica})";
                $body = "Olá <strong>{$nome}</strong>,<br><br>
                A tua candidatura para a música <strong>'{$musica}'</strong> está em análise, mas precisamos que faças uma pequena correção ou nos envies novos ficheiros para podermos aprovar o projeto.<br><br>
                <strong>O que precisamos de ti:</strong><br>
                <div style='background:#fdf3e8; border-left:4px solid #e67e22; padding:15px; margin:10px 0; color:#d35400;'>
                {$rejection_reason}
                </div>
                <br>
                Por favor, acede ao teu painel <strong>1MZ For Artists</strong>, vai à aba <strong>'Produção Vídeos 🏆'</strong> e responde à nossa equipa diretamente pelo chat da candidatura (podes colar links de novos áudios lá).<br><br>
                Ficamos a aguardar a tua resposta rápida!<br><br>
                <strong>Equipa 1MusicMoz</strong>";
            }
            
            $tpl = get_option('rmbz_email_templates');
            $sender_name = !empty($tpl['sender_name']) ? $tpl['sender_name'] : '1MZ For Artists';
            $sender_email = is_email($tpl['sender_email'] ?? '') ? $tpl['sender_email'] : get_option('admin_email');
            
            if (function_exists('rmbz_build_html_email')) { $html_body = rmbz_build_html_email($body, $sender_name); } else { $html_body = $body; }
            $headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $sender_name . ' <' . $sender_email . '>'];
            if ( ! $rmbz_notify_muted ) { wp_mail($artista->user_email, $subject, $html_body, $headers); }
        }
    }

    // =====================================================================
    // GATILHO 2: APROVAÇÃO E MENSAGEM ESPECIAL POR CENÁRIO (1, 2 ou 3)
    // =====================================================================
    if ($old_status === 'pending_review' && $production_status === 'pre-production' && ! $rmbz_notify_muted) {
        $artista = $wpdb->get_row($wpdb->prepare("SELECT user_email, user_nome FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s", $old_project->artist_telemovel));
        
        if ($artista && !empty($artista->user_email) && strpos($artista->user_email, '@1mz.local') === false) {
            $cenario = strtolower($old_project->scenario_type);
            $nome = $artista->user_nome;
            $musica = $old_project->project_title;
            
            $subject = "🎉 PARABÉNS! O teu videoclipe foi APROVADO!";
            
            if (strpos($cenario, '1') !== false || strpos($cenario, 'um') !== false) {
                $msg_especial = "Acreditamos muito no teu talento! Foste selecionado para o <strong>Cenário 1</strong>. Esta é a rampa de lançamento perfeita para dar vida à tua música com a qualidade que mereces. Prepara-te para brilhar!";
            } elseif (strpos($cenario, '2') !== false || strpos($cenario, 'dois') !== false) {
                $msg_especial = "Incrível! A tua música tem uma 'vibe' fantástica. Foste aprovado para o <strong>Cenário 2</strong>, onde vamos elevar a fasquia visual e criar algo que os teus fãs vão adorar partilhar.";
            } elseif (strpos($cenario, '3') !== false || strpos($cenario, 'tres') !== false || strpos($cenario, 'três') !== false || strpos($cenario, 'premium') !== false) {
                $msg_especial = "EXCECIONAL! 🏆 É com enorme orgulho que te anunciamos que a tua obra-prima foi escolhida para o nosso cobiçado <strong>Cenário 3 (Premium)</strong>. Vamos entregar uma superprodução de alto nível e criar uma verdadeira obra cinematográfica para a tua carreira!";
            } else {
                $msg_especial = "A tua música foi escolhida e o teu projeto de videoclipe foi aprovado pela nossa comissão de curadoria! Parabéns pelo teu trabalho árduo.";
            }
            
            $body = "Olá <strong>{$nome}</strong>,<br><br>
            É oficial! Temos novidades incríveis sobre a tua música <strong>'{$musica}'</strong>.<br><br>
            <div style='background:#fdfcf0; border-left:4px solid #f0b90b; padding:15px; margin:20px 0; font-size:15px; color:#333; line-height:1.6;'>
            {$msg_especial}
            </div>
            Acede agora mesmo à tua conta <strong>1MZ For Artists</strong>. Na aba <strong>'Produção Vídeos 🏆'</strong>, a nossa equipa já disponibilizou o teu Orçamento detalhado para darmos início aos preparativos.<br><br>
            O teu momento é agora. Vamos fazer história juntos!<br><br>
            Um abraço,<br><strong>Equipa 1MusicMoz</strong>";
            
            $tpl = get_option('rmbz_email_templates');
            $sender_name = !empty($tpl['sender_name']) ? $tpl['sender_name'] : '1MZ For Artists';
            $sender_email = is_email($tpl['sender_email'] ?? '') ? $tpl['sender_email'] : get_option('admin_email');
            
            if (function_exists('rmbz_build_html_email')) { $html_body = rmbz_build_html_email($body, $sender_name); } else { $html_body = $body; }
            $headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $sender_name . ' <' . $sender_email . '>'];
            wp_mail($artista->user_email, $subject, $html_body, $headers);
        }
    }

    // =====================================================================
    // [1.6.118.25] GATILHO 3: NOTIFICAÇÃO PUSH AO ARTISTA (FCM)
    // Sempre que o estado da produção mudar, envia push para a app.
    // O sistema FCM já existe (rmbz_fcm_send_to_user em fcm-sender.php).
    // =====================================================================
    if ( $old_status !== $production_status && ! $rmbz_notify_muted && function_exists( 'rmbz_fcm_send_to_user' ) ) {

        $musica = $old_project ? $old_project->project_title : '';
        $nome   = '';
        $artista_row = $wpdb->get_row( $wpdb->prepare(
            "SELECT user_nome FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s LIMIT 1",
            $old_project->artist_telemovel
        ) );
        if ( $artista_row ) $nome = $artista_row->user_nome;

        // Labels human-readable por estado (sincronizado com UI admin)
        $state_labels = [
            'pending_review' => 'Em Avaliação',
            'pre-production' => 'Aprovado',
            'kyc_review'     => 'KYC Pendente',
            'filming'        => 'Filmagem',
            'editing'        => 'Edição',
            'done'           => 'Concluído',
            'correction'     => 'Correção',
            'rejected'       => 'Rejeitado',
        ];

        // Titles + bodies por novo estado (orientado ao artista)
        $push_meta = [
            'pre-production' => [
                'title' => '✅ Projeto Aprovado!',
                'body'  => sprintf( 'O teu projeto "%s" foi APROVADO e está desbloqueado. Abre a app para avançar.', $musica ),
            ],
            'rejected' => [
                'title' => '❌ Projeto Não Avançou',
                'body'  => sprintf( '"%s" não pôde avançar. Abre a app para ver o motivo.', $musica ),
            ],
            'correction' => [
                'title' => '⚠️ Pedimos Correções',
                'body'  => sprintf( '"%s" precisa de uma correção da tua parte. Abre a app para ver detalhes.', $musica ),
            ],
            'kyc_review' => [
                'title' => '🪪 Documento Necessário',
                'body'  => sprintf( 'Para avançar com "%s" precisamos do teu documento de identificação.', $musica ),
            ],
            'filming' => [
                'title' => '🎥 Filmagem Iniciou!',
                'body'  => sprintf( '"%s" entrou em fase de filmagem. Em breve teremos novidades!', $musica ),
            ],
            'editing' => [
                'title' => '✂ Em Edição',
                'body'  => sprintf( '"%s" está em edição. A nossa equipa está a finalizar o teu vídeo.', $musica ),
            ],
            'done' => [
                'title' => '🚀 Projeto Concluído!',
                'body'  => sprintf( 'Parabéns! "%s" está pronto. Abre a app para ver os links.', $musica ),
            ],
            'pending_review' => [
                'title' => '⏱ Em Re-Avaliação',
                'body'  => sprintf( '"%s" voltou para análise pela nossa equipa.', $musica ),
            ],
        ];

        // Só envia se temos meta definida para o novo estado
        if ( isset( $push_meta[ $production_status ] ) ) {
            $meta = $push_meta[ $production_status ];

            $push_data = [
                'type'         => 'project_status_change',
                'project_id'   => (string) $project_id,
                'old_status'   => (string) $old_status,
                'new_status'   => (string) $production_status,
                'old_label'    => $state_labels[ $old_status ] ?? $old_status,
                'new_label'    => $state_labels[ $production_status ] ?? $production_status,
                'project_title' => (string) $musica,
                'deeplink'     => 'onemz://projects/' . $project_id,
            ];

            $sent = rmbz_fcm_send_to_user(
                $old_project->artist_telemovel,
                $meta['title'],
                $meta['body'],
                $push_data
            );

            error_log( sprintf(
                '[1MZ push project_status] proj=%d artist=%s %s→%s sent=%s',
                $project_id,
                $old_project->artist_telemovel,
                $old_status,
                $production_status,
                $sent ? 'OK' : 'NO_TOKEN/FAIL'
            ) );
        }
    }

    // =====================================================================
    // [1.6.118.40] GATILHO 4: NOTIFICAÇÃO PUSH AO ARTISTA — PITCH DA PRODUÇÃO
    // Quando admin escreve ou actualiza o pitch E o projeto já está em estado
    // visível ao artista (>= pre-production), enviar push.
    // =====================================================================
    if ( $pitch_changed && ! $rmbz_notify_muted && function_exists( 'rmbz_fcm_send_to_user' ) ) {
        // Estados onde o artista PODE ver o pitch (sincronizado com a API)
        $pitch_visible_states = [ 'pre-production', 'kyc_review', 'filming', 'editing', 'done' ];

        if ( in_array( $production_status, $pitch_visible_states, true ) ) {
            $musica = $old_project ? $old_project->project_title : '';

            // Preview do pitch (primeiras ~80 chars, em linha única)
            $preview = preg_replace( '/\s+/', ' ', trim( $pitch_new_preview ) );
            if ( mb_strlen( $preview ) > 80 ) {
                $preview = mb_substr( $preview, 0, 77 ) . '…';
            }

            if ( $pitch_is_new ) {
                $title = '💡 Nova Mensagem da Equipa';
                $body  = sprintf( 'Tens um novo Pitch da Produção em "%s". Toca para ler.', $musica );
            } else {
                $title = '💡 Pitch da Produção atualizado';
                $body  = sprintf( 'A equipa atualizou o pitch de "%s". Toca para ler.', $musica );
            }

            $push_data = [
                'type'           => 'production_pitch_updated',
                'project_id'     => (string) $project_id,
                'project_title'  => (string) $musica,
                'pitch_preview'  => (string) $preview,
                'is_new'         => $pitch_is_new ? '1' : '0',
                'deeplink'       => 'onemz://projects/' . $project_id,
            ];

            $sent = rmbz_fcm_send_to_user(
                $old_project->artist_telemovel,
                $title,
                $body,
                $push_data
            );

            error_log( sprintf(
                '[1MZ push production_pitch] proj=%d artist=%s new=%s sent=%s',
                $project_id,
                $old_project->artist_telemovel,
                $pitch_is_new ? '1' : '0',
                $sent ? 'OK' : 'NO_TOKEN/FAIL'
            ) );
        } else {
            // Estado ainda não desbloqueia pitch — não notifica para evitar
            // confusão ("recebi push mas não vejo nada na app")
            error_log( sprintf(
                '[1MZ push production_pitch] proj=%d skipped (estado=%s não visível ao artista)',
                $project_id,
                $production_status
            ) );
        }
    }

    // =====================================================================
    // [1.6.118.65] GATILHO 6: NOTIFICAÇÃO AO ARTISTA — DATAS DE GRAVAÇÃO
    // Quando o admin define/altera as datas E o projecto está em estado
    // aprovado, notificar o artista por 3 canais:
    //   1) FCM push (sino do telemóvel + abre o app no projecto)
    //   2) Email
    //   3) Sino in-app (via rmbz_notify, que grava em rmbz_email_logs)
    // =====================================================================
    if ( $filming_notify && ! $rmbz_notify_muted ) {
        $filming_visible_states = [ 'pre-production', 'kyc_review', 'filming', 'editing', 'done' ];

        if ( in_array( $production_status, $filming_visible_states, true ) ) {
            $musica = $old_project ? $old_project->project_title : '';
            $tel    = $old_project ? $old_project->artist_telemovel : '';

            // Datas formatadas para humanos (dd/mm/Y)
            $ds = $filming_notify_start !== '' ? date_i18n( 'd/m/Y', strtotime( $filming_notify_start ) ) : '—';
            $de = $filming_notify_end   !== '' ? date_i18n( 'd/m/Y', strtotime( $filming_notify_end ) )   : '—';

            // 1) FCM push
            if ( function_exists( 'rmbz_fcm_send_to_user' ) && ! empty( $tel ) ) {
                $push_data = [
                    'type'          => 'filming_dates_set',
                    'project_id'    => (string) $project_id,
                    'project_title' => (string) $musica,
                    'filming_start' => (string) $filming_notify_start,
                    'filming_end'   => (string) $filming_notify_end,
                    'deeplink'      => 'onemz://projects/' . $project_id,
                ];
                $sent = rmbz_fcm_send_to_user(
                    $tel,
                    '📅 Datas de gravação definidas',
                    sprintf( 'A gravação de "%s" está marcada: %s a %s. Confirma na app.', $musica, $ds, $de ),
                    $push_data
                );
                error_log( sprintf(
                    '[1MZ push filming_dates] proj=%d artist=%s sent=%s',
                    $project_id, $tel, $sent ? 'OK' : 'NO_TOKEN/FAIL'
                ) );
            }

            // 2) + 3) Email + sino in-app (via rmbz_notify)
            if ( function_exists( 'rmbz_notify' ) && ! empty( $tel ) ) {
                rmbz_notify( $tel, 'filming_dates_set', [
                    'musica'      => $musica,
                    'data_inicio' => $ds,
                    'data_fim'    => $de,
                ] );
            }
        } else {
            error_log( sprintf(
                '[1MZ filming_dates] proj=%d skipped (estado=%s não visível ao artista)',
                $project_id, $production_status
            ) );
        }
    }

    // =====================================================================
    // [1.6.118.49] GATILHO 5: NOTIFICAÇÃO PUSH AO ARTISTA — NOVOS ITEMS A DECIDIR
    // Quando admin adiciona items negociáveis pós-contrato (ou muda obrigatório
    // para negociável), o artista precisa de saber para abrir o app e decidir.
    // =====================================================================
    if ( ! empty( $newly_undecided_descs ) && ! $rmbz_notify_muted && function_exists( 'rmbz_fcm_send_to_user' ) ) {
        $musica = $old_project ? $old_project->project_title : '';
        $count  = count( $newly_undecided_descs );

        if ( $count === 1 ) {
            $title = '🆕 Novo item para decidir';
            $body  = sprintf(
                'A equipa adicionou "%s" ao orçamento de "%s". Abre a app para escolher quem trata.',
                $newly_undecided_descs[0],
                $musica
            );
        } else {
            $title = '🆕 Novos items para decidir';
            $body  = sprintf(
                'A equipa adicionou %d items ao orçamento de "%s". Abre a app para decidir.',
                $count,
                $musica
            );
        }

        $push_data = [
            'type'          => 'pending_items_added',
            'project_id'    => (string) $project_id,
            'project_title' => (string) $musica,
            'count'         => (string) $count,
            'deeplink'      => 'onemz://projects/' . $project_id,
        ];

        $sent = rmbz_fcm_send_to_user(
            $old_project->artist_telemovel,
            $title,
            $body,
            $push_data
        );

        error_log( sprintf(
            '[1MZ push pending_items] proj=%d artist=%s count=%d sent=%s',
            $project_id,
            $old_project->artist_telemovel,
            $count,
            $sent ? 'OK' : 'NO_TOKEN/FAIL'
        ) );
    }

    // =====================================================================
    // [1.5.373] HISTÓRICO DE ALTERAÇÕES — registar o que o admin mudou neste
    // save (estado, vídeo final, YouTube, data de lançamento, guião, pago).
    // Cada alteração vira uma entrada no change_log do projeto.
    // =====================================================================
    if ( function_exists( 'rmbz_pchat_append_change_log' ) && $old_project ) {
        $who = function_exists( 'rmbz_pchat_current_admin_who' ) ? rmbz_pchat_current_admin_who() : 'admin';
        $status_labels = [
            'pending_review' => 'Em Avaliação',
            'pre-production' => 'Aprovado',
            'kyc_review'     => 'KYC Pendente',
            'filming'        => 'Filmagem',
            'editing'        => 'Edição',
            'done'           => 'Concluído',
            'correction'     => 'Correção',
            'rejected'       => 'Rejeitado',
        ];
        $lbl = function ( $s ) use ( $status_labels ) {
            return $status_labels[ $s ] ?? ( $s ?: '(vazio)' );
        };

        // Estado de produção
        if ( $old_status !== $production_status ) {
            rmbz_pchat_append_change_log( $project_id, [
                'who'  => $who,
                'what' => '🔄 Alterou o estado da produção',
                'from' => $lbl( $old_status ),
                'to'   => $lbl( $production_status ),
            ] );
        }

        // Campos de entrega/links/datas — só regista quando muda mesmo.
        // [1.5.404] Datas normalizadas antes de comparar — a BD devolve
        // '0000-00-00 00:00:00' quando está vazio (e junta a hora à data),
        // o que fazia registar "📅 Atualizou a data de lançamento:
        // 0000-00-00 00:00:00 → (vazio)" em TODOS os guardares, mesmo sem
        // mexer em nada.
        $norm_date = function ( $v ) {
            $v = trim( (string) $v );
            if ( $v === '' || strpos( $v, '0000-00-00' ) === 0 ) return '';
            return substr( $v, 0, 10 ); // só a data (o campo do form é type=date)
        };
        $field_changes = [
            'final_video_url' => [ '🎬 Atualizou o link do vídeo final', (string) ( $old_project->final_video_url ?? '' ), $final_video_url ],
            'youtube_url'     => [ '▶️ Atualizou o link do YouTube',      (string) ( $old_project->youtube_url ?? '' ),     $youtube_url ],
            'release_date'    => [ '📅 Atualizou a data de lançamento',   $norm_date( $old_project->release_date ?? '' ),   $norm_date( $release_date ) ],
        ];
        foreach ( $field_changes as $info ) {
            list( $what, $old_v, $new_v ) = $info;
            if ( trim( (string) $old_v ) !== trim( (string) $new_v ) ) {
                rmbz_pchat_append_change_log( $project_id, [
                    'who'  => $who,
                    'what' => $what,
                    'from' => $old_v !== '' ? $old_v : '(vazio)',
                    'to'   => $new_v !== '' ? $new_v : '(vazio)',
                ] );
            }
        }

        // Guião (sem mostrar o conteúdo todo — só que mudou).
        if ( trim( (string) ( $old_project->script_content ?? '' ) ) !== trim( (string) $script_content ) ) {
            rmbz_pchat_append_change_log( $project_id, [
                'who'  => $who,
                'what' => '📝 Atualizou o guião do projeto',
            ] );
        }

        // Marcação de pago.
        $old_paid = (int) ( $old_project->is_paid ?? 0 );
        if ( $old_paid !== (int) $is_paid ) {
            rmbz_pchat_append_change_log( $project_id, [
                'who'  => $who,
                'what' => $is_paid ? '✅ Marcou o projeto como PAGO' : '↩️ Marcou o projeto como NÃO pago',
            ] );
        }
    }

    // [1.5.409] Mudança manual Filmagem→Edição com vistoria por pagar →
    // bloqueia e avisa o artista em todos os canais (idem ao fluxo automático).
    if ( $old_status === 'filming' && $production_status === 'editing'
         && function_exists( 'rmbz_team_vistoria_notify_if_blocked' ) ) {
        rmbz_team_vistoria_notify_if_blocked( $project_id );
    }

    $rmbz_redirect_url = admin_url( 'admin.php?page=rmbz-projetos&action=edit&id=' . $project_id . '&updated=true' );
    if ( $rmbz_vistoria_notice !== '' ) {
        $rmbz_redirect_url = add_query_arg( 'msg', rawurlencode( trim( $rmbz_vistoria_notice ) ), $rmbz_redirect_url );
    }
    wp_redirect( $rmbz_redirect_url ); exit;
}
add_action( 'admin_post_onemz_admin_update_project', 'onemz_admin_update_project' );

function onemz_delete_project() {
    if ( !isset($_GET['project_id'], $_GET['_wpnonce']) ) return;
    $proj_id = absint($_GET['project_id']);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'onemz_delete_project_' . $proj_id ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão!');

    global $wpdb;
    $table_projects = $wpdb->prefix . 'rmbz_artist_projects';

    $project = $wpdb->get_row( $wpdb->prepare( "SELECT demo_mp3_url, artist_telemovel FROM {$table_projects} WHERE id = %d", $proj_id ) );
    if ( ! $project ) {
        wp_redirect( admin_url('admin.php?page=rmbz-projetos') ); exit;
    }

    // [1.5.239] Revalidação do telemóvel confirmado no servidor.
    // O modal do admin exige escrever o nº do artista; aqui confirma-se
    // de novo (defesa caso o JS seja contornado). Compara só dígitos e
    // aceita match pelos últimos 9 (tolera +258 vs sem indicativo).
    $confirm_tel = isset( $_GET['confirm_tel'] )
        ? preg_replace( '/\D/', '', (string) $_GET['confirm_tel'] )
        : '';
    $real_tel = preg_replace( '/\D/', '', (string) $project->artist_telemovel );
    $tel_ok = ( $confirm_tel !== '' && $real_tel !== '' ) && (
        $confirm_tel === $real_tel ||
        ( strlen( $confirm_tel ) >= 9 && strlen( $real_tel ) >= 9 &&
          substr( $confirm_tel, -9 ) === substr( $real_tel, -9 ) )
    );
    if ( ! $tel_ok ) {
        wp_die( 'Confirmação inválida: o número de telemóvel não corresponde ao artista deste projeto. A eliminação foi cancelada.' );
    }

    if ( $project->demo_mp3_url ) { rmbz_destruir_ficheiro_fisico( $project->demo_mp3_url ); }

    $wpdb->delete( $table_projects, ['id' => $proj_id], ['%d'] );
    wp_redirect( admin_url('admin.php?page=rmbz-projetos&status=deleted') ); exit;
}
add_action('admin_post_onemz_delete_project', 'onemz_delete_project');
/**
 * ==========================================================================
 * 3. GESTÃO FINANCEIRA: DEPÓSITOS
 * ==========================================================================
 */
function rmbz_processar_aprovacao_deposito() {
    if ( !isset($_GET['dep_id'], $_GET['_wpnonce']) ) return;
    $dep_id = absint($_GET['dep_id']);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_approve_deposit_nonce_' . $dep_id ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Não tem permissão!');

    global $wpdb;
    $table_deposits = $wpdb->prefix . 'rmbz_deposit_requests';
    $deposit = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table_deposits} WHERE id = %d AND status = 'pendente'", $dep_id ) );

    if ($deposit) {
        $details = 'Depósito Aprovado (' . $deposit->payment_method . ' - ID: ' . $deposit->transaction_id . ')';
        $credit_result = rmbz_update_wallet_balance( $deposit->user_telemovel, (float)$deposit->amount, 'deposit', null, $details );

        if ( $credit_result['success'] ) {
            $wpdb->update($table_deposits, ['status' => 'aprovado', 'processed_date' => current_time('mysql', 1)], ['id' => $dep_id], ['%s', '%s'], ['%d']);

            // [1.5.131] Disparar hook rmbz_first_deposit_approved se este foi
            // o PRIMEIRO depósito aprovado deste utilizador. Sistema de
            // referrals usa para validar 1ª das 2 condições da comissão.
            $approved_count = (int) $wpdb->get_var( $wpdb->prepare(
                "SELECT COUNT(*) FROM $table_deposits
                 WHERE user_telemovel = %s AND status = 'aprovado'",
                $deposit->user_telemovel
            ) );
            if ( $approved_count === 1 ) { // o que acabámos de aprovar é o único
                do_action( 'rmbz_first_deposit_approved', $deposit->user_telemovel, (float) $deposit->amount );
            }

            // [1.5.138] Notificação ao utilizador via Centro (email + sino).
            // Antes: rmbz_send_user_deposit_status() — só email, texto hardcoded.
            // Agora: rmbz_notify() — template editável em Settings → E-mails 📧
            // (key: deposit_approved). Inclui sino automático.
            if ( function_exists('rmbz_notify') ) {
                rmbz_notify( $deposit->user_telemovel, 'deposit_approved', [
                    'valor' => number_format( (float) $deposit->amount, 0, ',', '.' ),
                ]);
            }

            wp_redirect( admin_url('admin.php?page=rmbz-depositos&status=deposito_aprovado') ); exit;
        } else { wp_die('Erro ao atualizar a carteira: ' . $credit_result['error']); }
    } else { wp_die('Depósito não encontrado.'); }
}
add_action('admin_post_rmbz_approve_deposit', 'rmbz_processar_aprovacao_deposito');

function rmbz_processar_rejeicao_deposito() {
    if ( !isset($_GET['dep_id'], $_GET['_wpnonce']) ) return;
    $dep_id = absint($_GET['dep_id']);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_reject_deposit_nonce_' . $dep_id ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Não tem permissão!');

    global $wpdb;
    $table_deposits = $wpdb->prefix . 'rmbz_deposit_requests';
    $table_wallets = $wpdb->prefix . 'rmbz_wallets';
    // [BUG-DP1] Só rejeitar se ainda for pendente. Antes era possível rejeitar
    // um depósito já aprovado, deixando o saldo do utilizador inflacionado.
    // [1.6.118.9] Buscar também is_first_deposit para decidir se apagamos
    // conta inteira ao rejeitar.
    $deposito = $wpdb->get_row($wpdb->prepare(
        "SELECT user_telemovel, amount, status, is_first_deposit FROM {$table_deposits} WHERE id = %d",
        $dep_id
    ));
    if (!$deposito) wp_die('Depósito não encontrado.');
    if ( $deposito->status !== 'pendente' ) {
        wp_redirect( admin_url('admin.php?page=rmbz-depositos&status=already_processed') );
        exit;
    }

    $reject_reason = isset( $_REQUEST['reason'] )
        ? sanitize_textarea_field( wp_unslash( $_REQUEST['reason'] ) )
        : '';
    if ( $reject_reason === '' ) {
        $reject_reason = 'O comprovativo enviado é inválido ou o valor não deu entrada.';
    }

    $wpdb->update(
        $table_deposits,
        ['status' => 'rejeitado', 'processed_date' => current_time('mysql', 1), 'admin_notes' => $reject_reason],
        ['id' => $dep_id, 'status' => 'pendente'],
        ['%s', '%s', '%s'], ['%d', '%s']
    );

    // [1.5.138] Notificação via Centro (email + sino). Template editável.
    // Motivo padrão pode ser sobrescrito por POST ou GET no futuro.
    if ( function_exists('rmbz_notify') ) {
        rmbz_notify( $deposito->user_telemovel, 'deposit_rejected', [
            'valor'  => number_format( (float) $deposito->amount, 0, ',', '.' ),
            'motivo' => $reject_reason,
        ]);
    }

    // [1.6.118.9] Se for primeiro depósito (registo) → apagar conta inteira.
    // Regra: "user só existe se pagar". Rejeitar primeiro depósito = não pagou.
    // Apaga: wallet + outras tabelas relacionadas (sem releases/uploads porque
    // utilizador novo não teve tempo de criar nada).
    // Soft delete não se aplica aqui porque ainda é fase de registo
    // (sem conteúdo gerado).
    if ( ! empty( $deposito->is_first_deposit ) ) {
        $telemovel_first = $deposito->user_telemovel;

        // Log auditoria
        error_log( sprintf(
            '[1MZ reject_first_deposit] Admin %s rejeitou primeiro depósito #%d de %s — conta vai ser apagada.',
            wp_get_current_user()->user_login ?? 'unknown',
            $dep_id,
            $telemovel_first
        ) );

        // Apagar wallet
        $wpdb->delete( $table_wallets, ['user_telemovel' => $telemovel_first], ['%s'] );

        // Apagar tabelas relacionadas (safety — devem estar vazias mas defensivo)
        $extras = [
            'rmbz_wallet_transactions' => 'user_telemovel',
            'rmbz_notifications'       => 'user_telemovel',
            'rmbz_app_devices'         => 'user_telemovel',
            'rmbz_api_refresh_tokens'  => 'user_telemovel',
        ];
        foreach ( $extras as $tbl => $col ) {
            $full = $wpdb->prefix . $tbl;
            if ( $wpdb->get_var( "SHOW TABLES LIKE '$full'" ) === $full ) {
                $wpdb->delete( $full, [ $col => $telemovel_first ], [ '%s' ] );
            }
        }

        // Apagar wp_user se existir
        $user_obj = get_user_by( 'login', $telemovel_first );
        if ( $user_obj ) {
            if ( ! function_exists( 'wp_delete_user' ) ) {
                require_once ABSPATH . 'wp-admin/includes/user.php';
            }
            wp_delete_user( $user_obj->ID );
        }

        wp_redirect( admin_url('admin.php?page=rmbz-depositos&status=primeiro_rejeitado_conta_apagada') );
        exit;
    }

    // [Top-up rejeitado — comportamento original]
    $saldo = (float) $wpdb->get_var($wpdb->prepare("SELECT balance FROM {$table_wallets} WHERE user_telemovel = %s", $deposito->user_telemovel));
    $historico_count = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->prefix}rmbz_wallet_transactions WHERE user_telemovel = %s", $deposito->user_telemovel));
    if ( $saldo == 0 && $historico_count == 0 ) $wpdb->delete( $table_wallets, ['user_telemovel' => $deposito->user_telemovel], ['%s'] );

    wp_redirect( admin_url('admin.php?page=rmbz-depositos&status=deposito_rejeitado') ); exit;
}
add_action('admin_post_rmbz_reject_deposit', 'rmbz_processar_rejeicao_deposito');


/**
 * [1.6.118.3] APAGAR DEFINITIVAMENTE depósito REJEITADO + ficheiro proof.
 *
 * Só funciona para depósitos com status='rejeitado'. Aprovados ficam
 * como histórico financeiro permanente — não podem ser apagados via UI.
 *
 * SAFEGUARDS:
 *   1. Nonce
 *   2. current_user_can('manage_options')
 *   3. status === 'rejeitado' (defesa profunda, não confia no front)
 *   4. Confirmação dupla no front (input "APAGAR")
 *   5. Log auditoria no error_log antes de apagar
 *
 * FICHEIRO PROOF:
 *   - URL local (mesmo domínio + dentro de wp-content/uploads): unlink
 *   - URL externo: tenta wp_remote_request DELETE (vai falhar para
 *     CDNs típicos, mas tentamos). Loga resultado.
 */
add_action( 'admin_post_rmbz_delete_rejected_deposit', 'rmbz_processar_delete_rejected_deposit' );

function rmbz_processar_delete_rejected_deposit() {
    if ( ! isset( $_GET['dep_id'], $_GET['_wpnonce'] ) ) wp_die( 'Parâmetros inválidos.' );

    $dep_id = absint( $_GET['dep_id'] );
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_delete_rejected_deposit_nonce_' . $dep_id ) ) {
        wp_die( 'Falha de segurança (nonce).' );
    }
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );

    global $wpdb;
    $tbl = $wpdb->prefix . 'rmbz_deposit_requests';

    $dep = $wpdb->get_row( $wpdb->prepare(
        "SELECT * FROM $tbl WHERE id = %d",
        $dep_id
    ) );

    if ( ! $dep ) {
        wp_redirect( wp_get_referer() ?: admin_url( 'admin.php?page=rmbz-deposits' ) );
        exit;
    }

    // SAFEGUARD: só rejeitados podem ser apagados
    if ( $dep->status !== 'rejeitado' ) {
        wp_die( 'Apenas depósitos REJEITADOS podem ser apagados. Este está como: ' . esc_html( $dep->status ) );
    }

    // ---- Apagar ficheiro proof_image ----
    $proof = trim( (string) ( $dep->proof_image ?? '' ) );
    $proof_log = 'sem_imagem';

    if ( ! empty( $proof ) ) {
        $proof_log = rmbz_deposit_delete_proof_file( $proof );
    }

    // ---- Log auditoria ANTES do DELETE ----
    $user = wp_get_current_user();
    error_log( sprintf(
        '[1MZ delete_rejected_deposit] admin_user=%s (id=%d) apagou dep_id=%d artista=%s valor=%.2f tx=%s proof_result=%s',
        $user->user_login ?? 'unknown',
        $user->ID,
        $dep_id,
        $dep->user_telemovel,
        (float) $dep->amount,
        $dep->transaction_id ?: 'sem_tx',
        $proof_log
    ) );

    // ---- DELETE row ----
    $wpdb->delete( $tbl, [ 'id' => $dep_id ], [ '%d' ] );

    // Redirect com notice
    $redirect = wp_get_referer() ?: admin_url( 'admin.php?page=rmbz-deposits' );
    $redirect = add_query_arg( [ 'rmbz_dep_deleted' => $dep_id ], $redirect );
    wp_safe_redirect( $redirect );
    exit;
}


/**
 * Apaga o ficheiro proof_image. Detecta se é local ou externo.
 *
 * @return string Descrição textual do resultado (para log).
 */
function rmbz_deposit_delete_proof_file( $proof ) {
    if ( empty( $proof ) ) return 'sem_proof';

    // [1.6.364] Caso 0: a prova é um ANEXO do WordPress (media library). Apagar
    // só o ficheiro deixava o anexo + miniaturas órfãos. Resolve a URL → anexo e
    // usa wp_delete_attachment (apaga post + ficheiro + todos os tamanhos).
    if ( function_exists( 'attachment_url_to_postid' ) ) {
        $att_id = attachment_url_to_postid( $proof );
        if ( $att_id ) {
            if ( ! function_exists( 'wp_delete_attachment' ) ) {
                require_once ABSPATH . 'wp-admin/includes/post.php';
            }
            wp_delete_attachment( $att_id, true );
            return 'apagado_anexo:' . $att_id;
        }
    }

    // Caso 1: caminho absoluto (raro, mas se vier de antigos uploads)
    if ( $proof[0] === '/' && file_exists( $proof ) ) {
        @unlink( $proof );
        return 'apagado_absoluto:' . basename( $proof );
    }

    // Caso 2: URL — tentar converter para caminho local
    $upload_dir = wp_upload_dir();
    $base_url   = trailingslashit( $upload_dir['baseurl'] );  // https://site/wp-content/uploads/
    $base_path  = trailingslashit( $upload_dir['basedir'] );  // /var/www/.../wp-content/uploads/

    // Também aceitar variantes http/https
    $variants = [
        $base_url,
        str_replace( 'https://', 'http://', $base_url ),
        str_replace( 'http://', 'https://', $base_url ),
    ];

    foreach ( $variants as $variant ) {
        if ( strpos( $proof, $variant ) === 0 ) {
            $relative = substr( $proof, strlen( $variant ) );
            $abs_path = $base_path . $relative;
            if ( file_exists( $abs_path ) ) {
                if ( @unlink( $abs_path ) ) {
                    return 'apagado_local:' . $relative;
                }
                return 'erro_unlink_local:' . $relative;
            }
            return 'nao_existe_local:' . $relative;
        }
    }

    // Caso 3: URL externo (S3, Cloudinary, etc.) — tentar DELETE HTTP
    // (vai falhar na maioria dos casos — CDNs não aceitam DELETE público
    // mas tentamos para o caso de ser um endpoint nosso noutro servidor)
    if ( filter_var( $proof, FILTER_VALIDATE_URL ) ) {
        $response = wp_remote_request( $proof, [
            'method'  => 'DELETE',
            'timeout' => 5,
        ] );
        if ( is_wp_error( $response ) ) {
            return 'externo_erro:' . substr( $response->get_error_message(), 0, 60 );
        }
        $code = wp_remote_retrieve_response_code( $response );
        if ( $code >= 200 && $code < 300 ) {
            return 'apagado_externo_http_' . $code;
        }
        return 'externo_recusado_http_' . $code;
    }

    return 'formato_desconhecido';
}

/**
 * ==========================================================================
 * 4. GESTÃO FINANCEIRA: LEVANTAMENTOS
 * ==========================================================================
 */
function rmbz_processar_aprovacao_levantamento() {
    if ( !isset($_GET['req_id'], $_GET['_wpnonce']) ) return;
    $req_id = absint($_GET['req_id']);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_approve_withdrawal_nonce_' . $req_id ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Não tem permissão!');

    global $wpdb;
    $tbl = $wpdb->prefix . 'rmbz_withdrawal_requests';

    // [BUG-WD1] Garantir idempotência: só aprovar se ainda estiver pendente.
    // Sem isto, repetir o link já processado dispararia a lógica de novo.
    $current = $wpdb->get_row( $wpdb->prepare("SELECT id, status FROM {$tbl} WHERE id = %d", $req_id) );
    if ( ! $current ) wp_die('Pedido não encontrado.');
    if ( $current->status !== 'pendente' ) {
        wp_redirect( admin_url('admin.php?page=rmbz-pedidos-levantamento&status=already_processed') );
        exit;
    }

    $wpdb->update($tbl,
        ['status' => 'aprovado', 'processed_date' => current_time('mysql', 1), 'admin_notes' => 'Pagamento processado e enviado.'],
        ['id' => $req_id, 'status' => 'pendente'], // condicional
        ['%s', '%s', '%s'], ['%d', '%s']
    );

    // [1.5.184] Hook para sistema de notificações inline.
    // Buscar info para o banner: telemovel + valor.
    $req_data = $wpdb->get_row( $wpdb->prepare(
        "SELECT user_telemovel, amount FROM {$tbl} WHERE id = %d",
        $req_id
    ));
    if ( $req_data && ! empty( $req_data->user_telemovel ) ) {
        do_action(
            'rmbz_withdrawal_approved',
            (int) $req_id,
            (string) $req_data->user_telemovel,
            (float) $req_data->amount
        );
    }

    wp_redirect( admin_url('admin.php?page=rmbz-pedidos-levantamento&status=aprovado') ); exit;
}
add_action('admin_post_rmbz_approve_withdrawal', 'rmbz_processar_aprovacao_levantamento');

function rmbz_processar_rejeicao_levantamento() {
    if ( !isset($_GET['req_id'], $_GET['_wpnonce']) ) return;
    $req_id = absint($_GET['req_id']);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_reject_withdrawal_nonce_' . $req_id ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Não tem permissão!');

    global $wpdb;
    $table_name = $wpdb->prefix . 'rmbz_withdrawal_requests';

    // [BUG-WD2] Antes a query já filtrava por status='pendente' no SELECT mas
    // o UPDATE seguinte não tinha condição de status — em corrida, dois admins
    // a clicar simultaneamente reembolsavam duas vezes. Agora o UPDATE também
    // é condicional em status='pendente' e validamos o nº de linhas afectadas.
    $request = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table_name} WHERE id = %d AND status = 'pendente'", $req_id ) );

    if ($request) {
        // [FEAT 1.5.35] Decisão da rota de reembolso baseada na fonte original.
        $source        = isset($request->source) ? $request->source : 'wallet';
        $exchange_rate = isset($request->exchange_rate) ? (float) $request->exchange_rate : 0.0;
        $amount_mt     = (float) $request->amount;
        $is_royalties_with_rate = ( $source === 'royalties' && $exchange_rate > 0 );

        $admin_note = $is_royalties_with_rate
            ? 'Pedido rejeitado. Valor revertido para Royalties (USD) ao câmbio do pedido original.'
            : 'Pedido rejeitado. Valor reembolsado na carteira (MT).';

        // Marcar como rejeitado primeiro, condicional, para reservar a operação.
        $rows = $wpdb->update(
            $table_name,
            ['status' => 'rejeitado', 'processed_date' => current_time('mysql', 1), 'admin_notes' => $admin_note],
            ['id' => $req_id, 'status' => 'pendente'],
            ['%s', '%s', '%s'], ['%d', '%s']
        );
        if ( $rows !== 1 ) {
            // Outro admin já rejeitou — não reembolsar de novo.
            wp_redirect( admin_url('admin.php?page=rmbz-pedidos-levantamento&status=already_processed') ); exit;
        }

        if ( $is_royalties_with_rate && function_exists('rmbz_secure_revert_mt_to_usd') ) {
            // -----------------------------------------------------------------
            // ROTA NOVA: pedido veio de Royalties (USD).
            // Devolver USD aos royalties, sem creditar MT na carteira.
            // O MT que foi originalmente debitado na criação do pedido continua
            // "fora" da carteira — não voltamos a creditar, porque o equivalente
            // em USD é que volta para os royalties. Resultado líquido: utilizador
            // fica exactamente como estava antes do pedido.
            // -----------------------------------------------------------------
            $revert = rmbz_secure_revert_mt_to_usd( $request->user_telemovel, $amount_mt, $exchange_rate, $req_id );

            if ( ! $revert['success'] ) {
                // Reverter para pendente e avisar — não deixa dinheiro perdido.
                $wpdb->update($table_name, ['status' => 'pendente', 'processed_date' => null, 'admin_notes' => 'Erro ao reverter para Royalties: ' . $revert['message']], ['id' => $req_id], ['%s', '%s', '%s'], ['%d']);
                wp_die('ERRO CRÍTICO: Falha ao reverter para Royalties (' . esc_html($revert['message']) . '). Estado revertido para pendente. Refazer manualmente.');
            }

            // Audit log do Guarda-Costas
            if ( function_exists('rmbz_wg_log') ) {
                rmbz_wg_log( $req_id, 'rejected_reverted_royalties',
                    sprintf('Pedido rejeitado. %.2f MT revertidos para %s ($%.4f USD ao câmbio %.4f).',
                        $amount_mt, 'Royalties', $revert['amount_usd'], $exchange_rate
                    )
                );
            }

            // [1.5.184] Hook para sistema de notificações inline.
            do_action(
                'rmbz_withdrawal_rejected',
                (int) $req_id,
                (string) $request->user_telemovel,
                (float) $amount_mt,
                ''  // sem motivo explícito — admin pode adicionar à mão se quiser
            );

            wp_redirect( admin_url('admin.php?page=rmbz-pedidos-levantamento&status=rejeitado') ); exit;

        } else {
            // -----------------------------------------------------------------
            // ROTA CLÁSSICA: pedido veio da Carteira (MT) ou pedido antigo
            // sem câmbio gravado. Creditar MT de volta na carteira como antes.
            // -----------------------------------------------------------------
            // [DS 1.5.106] Enriquecer detalhes para ficar rastreável no extracto
            $req_dt_fmt = $request->request_date
                ? date_i18n('d/m/Y H:i', strtotime((string) $request->request_date))
                : '?';
            $details = sprintf(
                'Reembolso de pedido de levantamento rejeitado — Pedido #%d (%s) · Método: %s · Destino: %s',
                $req_id,
                $req_dt_fmt,
                (string) $request->payment_method,
                (string) $request->payment_details
            );
            $refund_result = rmbz_update_wallet_balance( $request->user_telemovel, $amount_mt, 'withdrawal_rejected', null, $details );
            if ( ! $refund_result['success'] ) {
                // Reverter para pendente para não deixar dinheiro perdido.
                $wpdb->update($table_name, ['status' => 'pendente', 'processed_date' => null, 'admin_notes' => 'Erro no reembolso — reverter manualmente.'], ['id' => $req_id], ['%s', '%s', '%s'], ['%d']);
                wp_die('ERRO CRÍTICO: Falha ao reembolsar. Estado revertido para pendente.');
            }

            // [1.5.184] Hook para sistema de notificações inline.
            do_action(
                'rmbz_withdrawal_rejected',
                (int) $req_id,
                (string) $request->user_telemovel,
                (float) $amount_mt,
                ''
            );

            wp_redirect( admin_url('admin.php?page=rmbz-pedidos-levantamento&status=rejeitado') ); exit;
        }
    } else { wp_die('Pedido não encontrado ou já processado.'); }
}
add_action('admin_post_rmbz_reject_withdrawal', 'rmbz_processar_rejeicao_levantamento');

/**
 * ==========================================================================
 * 5. FERRAMENTAS DE GESTÃO DE UTILIZADOR
 * ==========================================================================
 */
function rmbz_processar_ajuste_saldo() {
    if ( ! isset( $_POST['user_telemovel'], $_POST['_rmbz_ajuste_nonce'] ) ) wp_die('Dados em falta.');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');

    $telemovel = sanitize_text_field($_POST['user_telemovel']);
    $phone_for_nonce = str_replace('+', '', $telemovel);
    if ( ! wp_verify_nonce( $_POST['_rmbz_ajuste_nonce'], 'rmbz_executar_ajuste_saldo_nonce_' . $phone_for_nonce ) ) wp_die('Nonce inválido.');

    $amount_raw = isset($_POST['amount']) ? sanitize_text_field($_POST['amount']) : '';
    $amount = (float) str_replace(',', '.', $amount_raw);
    $reason = isset($_POST['reason']) ? sanitize_text_field($_POST['reason']) : '';

    if ( $amount == 0 || empty($reason) ) wp_die('Dados inválidos.');
    $type = $amount > 0 ? 'admin_bonus' : 'admin_debit';
    $details = 'Ajuste Admin: ' . $reason;
    $result = rmbz_update_wallet_balance( $telemovel, $amount, $type, null, $details );

    if ( $result['success'] ) { wp_redirect( admin_url('admin.php?page=rmbz-utilizadores&status=balance_updated') ); } else { wp_die( 'Erro: ' . $result['error'] ); }
    exit;
}
add_action('admin_post_rmbz_processar_ajuste_saldo', 'rmbz_processar_ajuste_saldo');

function rmbz_processar_delete_single_transaction() {
    if ( !isset($_GET['trans_id'], $_GET['_wpnonce']) ) wp_die('Dados em falta.');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');

    $trans_id = absint($_GET['trans_id']);
    $user_phone = isset($_GET['user_phone']) ? sanitize_text_field(rawurldecode($_GET['user_phone'])) : '';
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_delete_trans_' . $trans_id ) ) wp_die('Nonce inválido.');

    global $wpdb;
    $trans_table = $wpdb->prefix . 'rmbz_wallet_transactions';
    $transaction = $wpdb->get_row( $wpdb->prepare( "SELECT amount, user_telemovel FROM {$trans_table} WHERE transaction_id = %d", $trans_id ) );

    if ( $transaction ) {
        $reverse_amount = (float)$transaction->amount * -1;
        $details = 'Reversão Admin da Transação ID: ' . $trans_id;
        $result = rmbz_update_wallet_balance( $transaction->user_telemovel, $reverse_amount, 'admin_reversal', null, $details );

        if ( $result['success'] ) {
            $wpdb->delete( $trans_table, ['transaction_id' => $trans_id], ['%d'] );
            wp_redirect( admin_url('admin.php?page=rmbz-historico-utilizador&user_phone=' . rawurlencode($user_phone) . '&status=trans_deleted_ok') ); exit;
        }
    }
    wp_die('Erro ao processar reversão.');
}
add_action('admin_post_rmbz_delete_single_transaction', 'rmbz_processar_delete_single_transaction');

function rmbz_processar_edicao_bloqueio() {
    if ( !isset($_POST['trans_id']) ) wp_die('Dados em falta.');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');

    $trans_id = absint($_POST['trans_id']);
    $user_phone = isset($_POST['user_phone']) ? sanitize_text_field(rawurldecode($_POST['user_phone'])) : '';
    if ( ! wp_verify_nonce( $_POST['_rmbz_edit_lock_nonce'], 'rmbz_edit_lock_' . $trans_id ) ) wp_die('Nonce inválido.');

    $is_unlocked = isset($_POST['lock_status']) ? absint($_POST['lock_status']) : 0;
    $unlock_date = isset($_POST['unlock_date']) ? sanitize_text_field($_POST['unlock_date']) : '';
    $data_update = [ 'is_unlocked' => $is_unlocked ];
    
    if ($is_unlocked == 0 && !empty($unlock_date)) { $data_update['unlock_date'] = $unlock_date; } elseif ($is_unlocked == 1) { $data_update['unlock_date'] = current_time('mysql', 1); }

    global $wpdb;
    $wpdb->update( $wpdb->prefix . 'rmbz_wallet_transactions', $data_update, ['transaction_id' => $trans_id], ['%s', '%s'], ['%d'] );
    wp_redirect( admin_url('admin.php?page=rmbz-historico-utilizador&user_phone=' . rawurlencode($user_phone)) ); exit;
}
add_action('admin_post_rmbz_processar_edicao_bloqueio', 'rmbz_processar_edicao_bloqueio');

function rmbz_processar_redefinir_pin() {
    if ( ! isset( $_POST['user_telemovel'] ) ) wp_die('Dados em falta.');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');

    $telemovel = sanitize_text_field($_POST['user_telemovel']);
    $phone_for_nonce = str_replace('+', '', $telemovel);
    if ( ! wp_verify_nonce( $_POST['_rmbz_pin_nonce'], 'rmbz_executar_redefinir_pin_nonce_' . $phone_for_nonce ) ) wp_die('Nonce inválido.');

    $pin1 = isset($_POST['new_pin_1']) ? sanitize_text_field($_POST['new_pin_1']) : '';
    $pin2 = isset($_POST['new_pin_2']) ? sanitize_text_field($_POST['new_pin_2']) : '';
    if ( strlen($pin1) !== 4 || !is_numeric($pin1) ) wp_die('PIN deve ter 4 dígitos.');
    if ( $pin1 !== $pin2 ) wp_die('PINs não coincidem.');

    global $wpdb;
    $wpdb->update( $wpdb->prefix . 'rmbz_wallets', ['user_pin' => wp_hash_password($pin1)], ['user_telemovel' => $telemovel], ['%s'], ['%s'] );
    wp_redirect( admin_url('admin.php?page=rmbz-utilizadores&status=pin_updated') ); exit;
}
add_action('admin_post_rmbz_processar_redefinir_pin', 'rmbz_processar_redefinir_pin');

function rmbz_processar_banir_utilizador() {
    if ( ! isset( $_GET['user_phone'] ) ) wp_die('Dados em falta.');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');

    $user_phone = sanitize_text_field(rawurldecode($_GET['user_phone']));
    $new_status = isset($_GET['ban']) ? absint($_GET['ban']) : 0;
    $phone_for_nonce = str_replace('+', '', $user_phone);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_ban_nonce_' . $phone_for_nonce ) ) wp_die('Nonce inválido.');

    global $wpdb;
    $wpdb->update( $wpdb->prefix . 'rmbz_wallets', ['is_banned' => $new_status], ['user_telemovel' => $user_phone], ['%d'], ['%s'] );
    wp_redirect( admin_url('admin.php?page=rmbz-utilizadores&status=user_status_changed') ); exit;
}
add_action('admin_post_rmbz_banir_utilizador', 'rmbz_processar_banir_utilizador');

function rmbz_cancel_user_subscription() {
    if ( !isset($_GET['user_phone'], $_GET['_wpnonce']) ) return;
    $user_phone = sanitize_text_field(rawurldecode($_GET['user_phone']));
    $phone_nonce = str_replace('+', '', $user_phone);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_cancel_sub_' . $phone_nonce ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão!');

    global $wpdb;
    $yesterday = date('Y-m-d H:i:s', strtotime('-1 day'));
    $wpdb->update( $wpdb->prefix . 'rmbz_subscriptions', ['end_date' => $yesterday, 'status' => 'cancelled'], ['user_telemovel' => $user_phone], ['%s', '%s'], ['%s'] );
    wp_redirect( admin_url('admin.php?page=rmbz-utilizadores&status=sub_cancelled') ); exit;
}
add_action('admin_post_rmbz_cancel_user_subscription', 'rmbz_cancel_user_subscription');

function rmbz_processar_delete_user_transactions() {
    if ( !isset($_GET['user_phone']) ) return;
    $user_phone = sanitize_text_field(rawurldecode($_GET['user_phone']));
    $phone_for_nonce = str_replace('+', '', $user_phone);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_delete_trans_nonce_' . $phone_for_nonce ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Não tem permissão!');

    global $wpdb;
    $wpdb->delete( $wpdb->prefix . 'rmbz_wallet_transactions', ['user_telemovel' => $user_phone], ['%s'] );
    wp_redirect(admin_url('admin.php?page=rmbz-utilizadores&status=trans_deleted')); exit;
}
add_action('admin_post_rmbz_delete_user_transactions', 'rmbz_processar_delete_user_transactions');

/**
 * ==========================================================================
 * 6. ATUALIZAÇÕES MANUAIS / EDIÇÃO DE SUBMISSÕES (SITE/SOCIAL) E CHAT
 * ==========================================================================
 */
function onemz_admin_edit_chat() {
    if ( ! isset( $_POST['project_id'], $_POST['_wpnonce'] ) ) wp_die( 'Dados em falta.' );
    if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'onemz_edit_chat_nonce' ) ) wp_die( 'Segurança.' );
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );

    $project_id = absint($_POST['project_id']);
    $msg_index = absint($_POST['msg_index']);
    $new_text = sanitize_textarea_field($_POST['new_message']);
    
    global $wpdb;
    $table = $wpdb->prefix . 'rmbz_artist_projects';
    // [SQL-1] Usar prepare. Não era explorável (absint à frente) mas é boa prática.
    $project = $wpdb->get_row( $wpdb->prepare("SELECT artist_feedback FROM {$table} WHERE id = %d", $project_id) );
    
    if ($project && !empty($new_text)) {
        $lines = explode("\n\n", $project->artist_feedback);
        if (isset($lines[$msg_index])) {
            
            // 1. Extrair APENAS a data original da mensagem (ex: [23/03/2026 15:30])
            if (preg_match('/^\[(.*?)\]/', $lines[$msg_index], $date_matches)) {
                $data_str = $date_matches[0];
            } else {
                $data_str = '[' . current_time('d/m/Y H:i') . ']';
            }
            
            // 2. FORÇAR a criação de um prefixo limpo (Ignora os : acumulados do passado)
            $prefix = $data_str . ' <strong>Equipa 1MZ:</strong>';
            
            // 3. Limpar completamente o texto novo para não ter espaços ou : no início
            $clean_new_text = ltrim(trim($new_text), ': ');
            
            // 4. Montar a linha perfeita
            $lines[$msg_index] = $prefix . ' ' . $clean_new_text . ' <em style="font-size:9px; color:#ccc;">(Editado)</em>';
            
            $wpdb->update($table, ['artist_feedback' => implode("\n\n", $lines)], ['id' => $project_id]);
        }
    }
    wp_redirect(wp_get_referer()); exit;
}
add_action('admin_post_onemz_admin_edit_chat', 'onemz_admin_edit_chat');

function onemz_admin_update_site_submission() {
    if ( ! isset( $_POST['sub_id'], $_POST['_wpnonce'] ) ) wp_die('Dados em falta.');
    $sub_id = absint( $_POST['sub_id'] );
    if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'onemz_update_site_sub_nonce' ) ) wp_die('Falha de segurança.');
    if ( ! current_user_can( 'manage_options' ) ) wp_die('Sem permissão.');

    global $wpdb;
    $table_subs = $wpdb->prefix . 'rmbz_artist_submissions';
    $submission = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table_subs} WHERE id = %d", $sub_id ) );
    if ( ! $submission ) wp_die('Submissão não encontrada.');

    $new_status = isset($_POST['review_status']) ? sanitize_text_field($_POST['review_status']) : 'pendente';
    $rejection_reason = isset($_POST['rejection_reason']) ? sanitize_textarea_field($_POST['rejection_reason']) : '';

    $extra = !empty($submission->extra_data) ? json_decode($submission->extra_data, true) : [];
    if ( ! is_array( $extra ) ) $extra = [];
    $old_published_post_id = function_exists( 'rmbz_site_submission_post_id_from_extra' )
        ? rmbz_site_submission_post_id_from_extra( $extra )
        : absint( $extra['published_post_id'] ?? 0 );
    $publication_became_official = false;
    if ( (string) $submission->scenario_type === 'Publicação Site' ) {
        $published_post_id = absint( $_POST['published_post_id'] ?? 0 );
        if ( ! $published_post_id && $old_published_post_id ) {
            $published_post_id = $old_published_post_id;
        }
        if ( $published_post_id && $new_status === 'finalizado' && function_exists( 'rmbz_site_submission_link_publication' ) ) {
            $publication_result = rmbz_site_submission_link_publication( $submission, $published_post_id, $extra );
            if ( is_wp_error( $publication_result ) ) {
                $url = add_query_arg( [
                    'page'   => 'rmbz-envios-site',
                    'action' => 'edit',
                    'id'     => $sub_id,
                    'status' => 'publication_invalid',
                    'reason' => $publication_result->get_error_code(),
                ], admin_url( 'admin.php' ) );
                wp_safe_redirect( $url );
                exit;
            }
            $publication_became_official = $new_status === 'finalizado'
                && $old_published_post_id !== absint( $publication_result['post_id'] ?? 0 );
        } elseif ( $published_post_id ) {
            $candidate = get_post( $published_post_id );
            if ( ! $candidate || $candidate->post_type !== 'post' || $candidate->post_status !== 'publish' ) {
                wp_safe_redirect( add_query_arg( [
                    'page'   => 'rmbz-envios-site',
                    'action' => 'edit',
                    'id'     => $sub_id,
                    'status' => 'publication_invalid',
                ], admin_url( 'admin.php' ) ) );
                exit;
            }
            $extra['published_post_id'] = $published_post_id;
            unset( $extra['published_url'], $extra['published_linked_at'] );
        } elseif ( $new_status === 'finalizado' ) {
            wp_safe_redirect( add_query_arg( [
                'page'   => 'rmbz-envios-site',
                'action' => 'edit',
                'id'     => $sub_id,
                'status' => 'publication_required',
            ], admin_url( 'admin.php' ) ) );
            exit;
        }
    }
    if (isset($_POST['facebook_url'])) $extra['facebook_url'] = esc_url_raw($_POST['facebook_url']);
    if (isset($_POST['youtube_url'])) $extra['youtube_url'] = esc_url_raw($_POST['youtube_url']);

    // [1.6.118.128] Agendamento de publicação: o admin escolhe a hora local
    // (Maputo); guardamos em UTC. Vazio = remove o agendamento (publica já).
    if (isset($_POST['scheduled_publish_at'])) {
        $sched_in = trim((string) $_POST['scheduled_publish_at']);
        if ($sched_in === '') {
            unset($extra['scheduled_publish_at']);
        } else {
            $local_wall = strtotime(str_replace('T', ' ', $sched_in) . ' UTC');
            if ($local_wall !== false) {
                $off = function_exists('rmbz_pub_site_offset_secs') ? rmbz_pub_site_offset_secs() : 0;
                $extra['scheduled_publish_at'] = gmdate('Y-m-d H:i:s', $local_wall - $off); // UTC
            }
        }
    }
    
    // GUARDAR AS FLAGS DE CORREÇÃO
    if ($new_status === 'correcao') {
        $extra['needs_audio_fix'] = isset($_POST['needs_audio_fix']) ? 1 : 0;
        $extra['needs_cover_fix'] = isset($_POST['needs_cover_fix']) ? 1 : 0;
        $extra['needs_link_fix']  = isset($_POST['needs_link_fix']) ? 1 : 0;
    } else {
        // Limpar os pedidos de correção se for aprovado ou rejeitado definitivamente
        unset($extra['needs_audio_fix'], $extra['needs_cover_fix'], $extra['needs_link_fix']);
    }

    $extra_json = wp_json_encode($extra);
    $old_status = $submission->review_status;
    $scheduled_publish_ts = ! empty( $extra['scheduled_publish_at'] ) ? strtotime( (string) $extra['scheduled_publish_at'] . ' UTC' ) : 0;
    $publication_visible = ! $scheduled_publish_ts || $scheduled_publish_ts <= time();

    $wpdb->update(
        $table_subs,
        ['review_status' => $new_status, 'rejection_reason' => $rejection_reason, 'extra_data' => $extra_json],
        ['id' => $sub_id],
        ['%s', '%s', '%s'], ['%d']
    );

    // NOTIFICAR O ARTISTA SE O ESTADO MUDAR
    if ( $new_status !== $old_status || $publication_became_official ) {
        // Buscar o E-mail e Nome do Artista
        $artista = $wpdb->get_row($wpdb->prepare("SELECT user_email, user_nome FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s", $submission->artist_telemovel));
        
        $artist_email = $artista ? $artista->user_email : '';
        $artist_name = $artista ? $artista->user_nome : 'Artista';
        
        // [1.6.34] Criar notificação na BD (rmbz_notifications) — isto
        // dispara automaticamente o push notification pelo sistema
        // rmbz_fcm_auto_push (que escuta a action 'rmbz_notification_created').
        // O artista vê a notif no sino + recebe push no telemóvel.
        if ( function_exists('rmbz_notify_create') && $submission->artist_telemovel ) {
            $notif_severity = 'info';
            $notif_title    = '';
            $notif_body     = null;
            $action_url     = null;

            if ( $new_status === 'aprovado' ) {
                $notif_severity = 'success';
                $notif_title    = "Submissão aprovada: {$submission->music_title}";
                $notif_body     = "A tua submissão foi aprovada e vai ser publicada em breve.";
            } elseif ( $new_status === 'em_publicacao' ) {
                $notif_severity = 'info';
                $notif_title    = "Submissão em publicação: {$submission->music_title}";
                $notif_body     = "A equipa 1MZ está a publicar o teu conteúdo.";
            } elseif ( $new_status === 'finalizado' ) {
                $notif_severity = 'success';
                $notif_title    = $publication_visible ? "Publicado: {$submission->music_title}" : "Publicação agendada: {$submission->music_title}";
                $notif_body     = $publication_visible
                    ? "A tua música está oficialmente publicada. Toca para ver o link."
                    : "A publicação foi validada e o link ficará disponível automaticamente na data agendada.";
            } elseif ( $new_status === 'correcao' ) {
                $notif_severity = 'warning';
                $notif_title    = "Submissão precisa de correcção: {$submission->music_title}";
                $notif_body     = $rejection_reason
                    ? mb_substr( wp_strip_all_tags( $rejection_reason ), 0, 200 )
                    : "Abre a submissão para ver o que precisas de corrigir.";
            } elseif ( $new_status === 'rejeitado' ) {
                $notif_severity = 'danger';
                $notif_title    = "Submissão rejeitada: {$submission->music_title}";
                $notif_body     = $rejection_reason
                    ? mb_substr( wp_strip_all_tags( $rejection_reason ), 0, 200 )
                    : "Abre a submissão para ver o motivo.";
            }

            if ( $notif_title ) {
                rmbz_notify_create([
                    'type'             => 'submission_status_change',
                    'severity'         => $notif_severity,
                    'title'            => $notif_title,
                    'body'             => $notif_body,
                    'action_url'       => "1mzapp://submissions/{$sub_id}",
                    'action_label'     => 'Ver submissão',
                    'target_telemovel' => $submission->artist_telemovel,
                    'dedup_key'        => "subm_status_{$sub_id}_{$new_status}_" . absint( $extra['published_post_id'] ?? 0 ),
                    'metadata'         => [
                        'submission_id' => (int) $sub_id,
                        'new_status'    => $new_status,
                        'music_title'   => $submission->music_title,
                        'published_url' => (string) ( $extra['published_url'] ?? '' ),
                    ],
                    'created_by'       => 'admin_submission_update',
                ]);
            }
        }

        $tpl = get_option('rmbz_email_templates');
        $sender_name = !empty($tpl['sender_name']) ? $tpl['sender_name'] : get_bloginfo('name');
        $sender_email = is_email($tpl['sender_email'] ?? '') ? $tpl['sender_email'] : get_option('admin_email');

        // Só envia se tiver um email real (ignora os emails falsos do sistema @1mz.local)
        if ( $artist_email && strpos($artist_email, '@1mz.local') === false ) {
            
            if ( $new_status === 'aprovado' ) {
                $subject = "✅ Aprovado: " . $submission->music_title;
                $body = "Olá <strong>{$artist_name}</strong>,<br><br>A sua obra '<strong>{$submission->music_title}</strong>' foi avaliada e <strong>aprovada</strong> pela nossa equipa!<br><br>Iremos processar a sua publicação em breve. Acompanhe os links finais diretamente no seu painel da 1MZ.<br><br>Um abraço,<br>Equipa " . $sender_name;
                
                if (function_exists('rmbz_build_html_email')) {
                    $html_body = rmbz_build_html_email($body, $sender_name);
                    $headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $sender_name . ' <' . $sender_email . '>'];
                    $sent = wp_mail($artist_email, $subject, $html_body, $headers);
                    // Regista o log para aparecer no Sino de Notificações
                    if (function_exists('rmbz_log_sent_email')) rmbz_log_sent_email($artist_email, $subject, $html_body, $sent ? 'sucesso' : 'falha');
                }
            }
            elseif ( $new_status === 'finalizado' && ! empty( $extra['published_url'] ) ) {
                $subject = ( $publication_visible ? "Publicado no site: " : "Publicação agendada: " ) . $submission->music_title;
                $official_url = esc_url( (string) $extra['published_url'] );
                if ( $publication_visible ) {
                    $body = "Olá <strong>{$artist_name}</strong>,<br><br>A publicação de '<strong>{$submission->music_title}</strong>' foi validada pela equipa e já está disponível no site oficial da 1MZ.<br><br><a href='{$official_url}' style='display:inline-block;padding:12px 18px;border-radius:999px;background:#f5b900;color:#17130a;text-decoration:none;font-weight:700;'>Abrir publicação oficial</a><br><br>Também podes encontrar este link em Envios e no teu Art Track.<br><br>Equipa " . $sender_name;
                } else {
                    $publish_date = date_i18n( 'd/m/Y H:i', $scheduled_publish_ts + ( function_exists( 'rmbz_pub_site_offset_secs' ) ? rmbz_pub_site_offset_secs() : 0 ) );
                    $body = "Olá <strong>{$artist_name}</strong>,<br><br>A publicação de '<strong>{$submission->music_title}</strong>' foi validada e está agendada para <strong>{$publish_date}</strong>. O link aparecerá automaticamente no teu painel quando chegar a hora.<br><br>Equipa " . $sender_name;
                }

                if ( function_exists( 'rmbz_build_html_email' ) ) {
                    $html_body = rmbz_build_html_email( $body, $sender_name );
                    $headers = [ 'Content-Type: text/html; charset=UTF-8', 'From: ' . $sender_name . ' <' . $sender_email . '>' ];
                    $sent = wp_mail( $artist_email, $subject, $html_body, $headers );
                    if ( function_exists( 'rmbz_log_sent_email' ) ) rmbz_log_sent_email( $artist_email, $subject, $html_body, $sent ? 'sucesso' : 'falha' );
                }
            }
            elseif ( $new_status === 'correcao' ) {
                $subject = "⚠️ O seu envio precisa da sua atenção!";
                $body = "Olá <strong>{$artist_name}</strong>,<br><br>A sua submissão para '<strong>{$submission->music_title}</strong>' precisa de uma correção antes de podermos publicá-la.<br><br><div style='background:#f9f9f9; padding:15px; border-left:4px solid #e67e22; margin:15px 0;'><strong>Mensagem da equipa:</strong><br><em>\"{$rejection_reason}\"</em></div>Por favor, aceda à sua área de artista na plataforma 1MusicMoz, vá ao separador <strong>'Meus Envios'</strong> e clique no botão laranja <strong>'⚠️ Ver e Corrigir'</strong> para enviar os dados corretos.<br><br>Ficamos a aguardar,<br>Equipa " . $sender_name;
                
                if (function_exists('rmbz_build_html_email')) {
                    $html_body = rmbz_build_html_email($body, $sender_name);
                    $headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $sender_name . ' <' . $sender_email . '>'];
                    $sent = wp_mail($artist_email, $subject, $html_body, $headers);
                    // Regista o log para aparecer no Sino de Notificações
                    if (function_exists('rmbz_log_sent_email')) rmbz_log_sent_email($artist_email, $subject, $html_body, $sent ? 'sucesso' : 'falha');
                }
            } 
        }
        
        // Rejeição definitiva tem a sua própria função (que também já regista no log)
        if ( $new_status === 'rejeitado' ) {
            if ( function_exists('onemz_send_rejection_notification') ) { onemz_send_rejection_notification( $sub_id, $rejection_reason ); }
        }
    }

    $page = ($submission->scenario_type === 'Redes Sociais') ? 'rmbz-envios-social' : 'rmbz-envios-site';
    wp_redirect( admin_url("admin.php?page={$page}&action=edit&id={$sub_id}&status=updated") ); exit;
}
add_action('admin_post_onemz_admin_update_site_submission', 'onemz_admin_update_site_submission');

function onemz_admin_reply_chat() {
    if ( ! isset( $_POST['project_id'], $_POST['_wpnonce'] ) ) wp_die( 'Dados em falta.' );
    $project_id = absint( $_POST['project_id'] );
    if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'onemz_reply_chat_nonce' ) ) wp_die( 'Falha de segurança.' );
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );

    $message = isset($_POST['admin_message']) ? sanitize_textarea_field($_POST['admin_message']) : '';
    if ( empty($message) ) { wp_redirect( admin_url( "admin.php?page=rmbz-projetos&action=edit&id={$project_id}" ) ); exit; }

    global $wpdb;
    $table_projects = $wpdb->prefix . 'rmbz_artist_projects';
    $project = $wpdb->get_row( $wpdb->prepare("SELECT id, artist_feedback FROM {$table_projects} WHERE id = %d", $project_id) );
    if ( !$project ) wp_die('Projeto não encontrado.');

    $timestamp = current_time('d/m/Y H:i');
    $entry = "[$timestamp] <strong>Equipa 1MZ:</strong> $message";
    $updated_history = trim($project->artist_feedback);
    if ( ! empty($updated_history) ) { $updated_history .= "\n\n" . $entry; } else { $updated_history = $entry; }

    $wpdb->update( $table_projects, ['artist_feedback' => $updated_history], ['id' => $project_id], ['%s'], ['%d'] );
    wp_redirect( admin_url( "admin.php?page=rmbz-projetos&action=edit&id={$project_id}" ) ); exit;
}
add_action('admin_post_onemz_admin_reply_chat', 'onemz_admin_reply_chat');

function onemz_run_db_update() {
    if ( ! current_user_can( 'manage_options' ) ) return;
    if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'onemz_db_update_nonce' ) ) wp_die( 'Falha de segurança!' );

    if ( function_exists('rmbz_criar_tabelas') ) { rmbz_criar_tabelas(); }
    update_option( 'rmbz_db_version', RMBZ_DB_VERSION );

    wp_redirect( admin_url( 'admin.php?page=rmbz-dashboard&db_updated=true' ) ); exit;
}
add_action( 'admin_post_onemz_run_db_update', 'onemz_run_db_update' );

/**
 * ==========================================================================
 * 7. Processador para Gravar as Regras Cupom
 * ==========================================================================
 */
/**
 * Offset do fuso configurado no WordPress, em segundos.
 *
 * As datas de validade dos cupões são guardadas em UTC e apresentadas no
 * horário local do site.
 */
function rmbz_coupons_site_offset_secs() {
    $local = (int) current_time( 'timestamp' );
    $utc   = (int) current_time( 'timestamp', true );
    $diff  = $local - $utc;

    if ( $diff >= -12 * HOUR_IN_SECONDS && $diff <= 14 * HOUR_IN_SECONDS ) {
        return $diff;
    }

    $gmt = get_option( 'gmt_offset', '' );
    if ( $gmt !== '' && $gmt !== null ) {
        return (int) round( (float) $gmt * HOUR_IN_SECONDS );
    }

    return 0;
}

function rmbz_admin_add_coupon() {
    if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'rmbz_add_coupon_nonce' ) ) wp_die('Falha de segurança.');
    if ( ! current_user_can( 'manage_options' ) ) wp_die('Sem permissão.');

    global $wpdb;
    $table_coupons = $wpdb->prefix . 'rmbz_coupons';

    // Limpar e formatar os dados base
    $code = strtoupper(sanitize_text_field(str_replace(' ', '', $_POST['coupon_code'])));
    $type = sanitize_text_field($_POST['reward_type']);
    $value = (float) $_POST['reward_value'];
    $limit = absint($_POST['usage_limit']);
    
    // O admin escolhe a hora local do site; a base de dados recebe UTC.
    $expiry = null;
    if (!empty($_POST['expiry_date'])) {
        $local_wall = strtotime( str_replace('T', ' ', $_POST['expiry_date']) . ' UTC' );
        if ( $local_wall !== false ) {
            $expiry = gmdate( 'Y-m-d H:i:s', $local_wall - rmbz_coupons_site_offset_secs() );
        }
    }

    // Verificar se o código já existe
    $exists = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table_coupons} WHERE coupon_code = %s", $code));
    if ($exists > 0) wp_die('Erro: Já existe um cupão com este código exato. Volte atrás e escolha outro.');

    // PROCESSAR AS REGRAS E LIMITES (EXTRA DATA)
    $extra_data = [];
    if ($type === 'saldo') {
        $extra_data['is_withdrawable'] = isset($_POST['is_withdrawable']) ? absint($_POST['is_withdrawable']) : 0;
    } elseif ($type === 'vip_days') {
        $extra_data['vip_permissions'] = [
            'distribute' => isset($_POST['vip_perm_distribute']) ? 1 : 0,
            'site'       => isset($_POST['vip_perm_site']) ? 1 : 0,
            'social'     => isset($_POST['vip_perm_social']) ? 1 : 0,
            'video'      => isset($_POST['vip_perm_video']) ? 1 : 0,
            // [1.5.297] Add-on do Assistente de IA via cupão.
            'ai'         => isset($_POST['vip_perm_ai']) ? 1 : 0,
        ];
    }
    $extra_data_json = wp_json_encode($extra_data);

    // Inserir na base de dados
    $inserted = $wpdb->insert(
        $table_coupons,
        [
            'coupon_code'  => $code,
            'reward_type'  => $type,
            'reward_value' => $value,
            'usage_limit'  => $limit,
            'expiry_date'  => $expiry,
            'status'       => 'ativo',
            'extra_data'   => $extra_data_json
        ],
        ['%s', '%s', '%f', '%d', '%s', '%s', '%s']
    );

    // MENSAGEM DE ERRO DETALHADA SE FALHAR
    if ( $inserted === false ) {
        wp_die('Erro Crítico ao guardar o cupão na Base de Dados. Detalhe do Erro MySQL: <br><br><strong>' . $wpdb->last_error . '</strong><br><br>Isto geralmente significa que a tabela rmbz_coupons não foi criada. Volte ao painel e atualize a página para o sistema tentar recriá-la.');
    }

    wp_redirect( admin_url('admin.php?page=rmbz-cupoes&status=coupon_created') ); exit;
}
add_action('admin_post_rmbz_admin_add_coupon', 'rmbz_admin_add_coupon');

function rmbz_admin_delete_coupon() {
    if ( !isset($_GET['id'], $_GET['_wpnonce']) ) return;
    $id = absint($_GET['id']);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_del_coupon_' . $id ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão!');

    global $wpdb;
    $wpdb->delete( $wpdb->prefix . 'rmbz_coupons', ['id' => $id], ['%d'] );
    
    wp_redirect( admin_url('admin.php?page=rmbz-cupoes&status=coupon_deleted') ); exit;
}
add_action('admin_post_rmbz_admin_delete_coupon', 'rmbz_admin_delete_coupon');

/**
 * ==========================================================================
 * APROVAR OU REJEITAR IDENTIDADE DO ARTISTA (KYC)
 * ==========================================================================
 */
function onemz_admin_approve_kyc() {
    if ( ! current_user_can('manage_options') ) wp_die('Acesso negado.');
    $project_id = intval($_GET['project_id']);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'onemz_approve_kyc_' . $project_id ) ) wp_die('Segurança!');
    
    global $wpdb;
    $table_projects = $wpdb->prefix . 'rmbz_artist_projects';
    $project = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table_projects} WHERE id = %d", $project_id));
    
    if ($project) {
        $extra = !empty($project->extra_data) ? json_decode($project->extra_data, true) : [];
        $extra['kyc_status'] = 'approved';
        
        $wpdb->update($table_projects, [
            'extra_data' => wp_json_encode($extra),
            'production_status' => 'pre-production' // Desbloqueia e volta ao estado de Orçamento/Pagamento
        ], ['id' => $project_id]);

        // Avisar o artista que o pagamento está liberado
        $artista = $wpdb->get_row($wpdb->prepare("SELECT user_email FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s", $project->artist_telemovel));
        if ($artista && !empty($artista->user_email) && strpos($artista->user_email, '@1mz.local') === false) {
            wp_mail($artista->user_email, "✅ Identidade Validada - " . $project->project_title, "A sua foto do BI foi aprovada com sucesso! Já pode aceder à plataforma 1MZ e prosseguir com o pagamento da produção do videoclipe.");
        }
    }
    wp_redirect( wp_get_referer() ); exit;
}
add_action('admin_post_onemz_admin_approve_kyc', 'onemz_admin_approve_kyc');

function onemz_admin_reject_kyc() {
    if ( ! current_user_can('manage_options') ) wp_die('Acesso negado.');
    $project_id = intval($_POST['project_id']);
    $reason = sanitize_textarea_field($_POST['kyc_rejection_reason']);
    if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'onemz_reject_kyc_' . $project_id ) ) wp_die('Segurança!');

    global $wpdb;
    $table_projects = $wpdb->prefix . 'rmbz_artist_projects';
    $project = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table_projects} WHERE id = %d", $project_id));
    
    if ($project) {
        $extra = !empty($project->extra_data) ? json_decode($project->extra_data, true) : [];
        
        // Destruir foto rejeitada do servidor
        if (!empty($extra['contract_kyc_photo']) && function_exists('rmbz_destruir_ficheiro_fisico')) {
            rmbz_destruir_ficheiro_fisico($extra['contract_kyc_photo']);
        }

        // Resetar o contrato obrigando o artista a reenviar
        $extra['contract_accepted'] = false;
        unset($extra['contract_real_name']);
        unset($extra['contract_kyc_photo']);
        unset($extra['contract_accepted_date']);
        $extra['kyc_status'] = 'rejected';
        $extra['kyc_rejection_reason'] = $reason;

        $wpdb->update($table_projects, [
            'extra_data' => wp_json_encode($extra),
            'production_status' => 'pre-production'
        ], ['id' => $project_id]);

        $artista = $wpdb->get_row($wpdb->prepare("SELECT user_email FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s", $project->artist_telemovel));
        if ($artista && !empty($artista->user_email) && strpos($artista->user_email, '@1mz.local') === false) {
            wp_mail($artista->user_email, "❌ Problema com a sua Identidade - " . $project->project_title, "A sua foto do BI foi rejeitada pela equipa pelo seguinte motivo:\n\n{$reason}\n\nPor favor, aceda à plataforma e envie uma nova foto mais clara.");
        }
    }
    wp_redirect( wp_get_referer() ); exit;
}
add_action('admin_post_onemz_admin_reject_kyc', 'onemz_admin_reject_kyc');

/**
 * ==========================================================================
 * GESTÃO E COMPRA DE PACOTES VIP (NOVO SISTEMA)
 * ==========================================================================
 */
function rmbz_admin_save_package() {
    if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'rmbz_save_package_nonce' ) ) wp_die('Falha de segurança.');
    if ( ! current_user_can( 'manage_options' ) ) wp_die('Sem permissão.');

    $packages = get_option('rmbz_vip_packages', []);

    // [1.6.118.20] Suporte a editar pacote existente (se vier pkg_id)
    $editing_id = isset($_POST['pkg_id']) ? sanitize_text_field($_POST['pkg_id']) : '';

    // Validações
    $nome  = sanitize_text_field($_POST['pkg_nome']);
    $preco = (float)$_POST['pkg_preco'];
    $dias  = absint($_POST['pkg_dias']);
    if ( $nome === '' ) {
        wp_redirect( admin_url('admin.php?page=rmbz-pacotes&status=error&msg=' . urlencode('Nome do pacote obrigatório.')) ); exit;
    }
    if ( $preco <= 0 ) {
        wp_redirect( admin_url('admin.php?page=rmbz-pacotes&status=error&msg=' . urlencode('Preço deve ser maior que 0.')) ); exit;
    }
    if ( $dias < 1 ) {
        wp_redirect( admin_url('admin.php?page=rmbz-pacotes&status=error&msg=' . urlencode('Duração mínima 1 dia.')) ); exit;
    }

    // Channel segmentação (default 'both')
    $channel = isset($_POST['pkg_channel']) ? sanitize_text_field($_POST['pkg_channel']) : 'both';
    if ( ! in_array( $channel, [ 'both', 'app_only', 'site_only' ], true ) ) {
        $channel = 'both';
    }

    $pack_data = [
        'nome'  => $nome,
        'preco' => $preco,
        'dias'  => $dias,
        'perms' => [
            'site'       => isset($_POST['perm_site']) ? 1 : 0,
            'social'     => isset($_POST['perm_social']) ? 1 : 0,
            'distribute' => isset($_POST['perm_distribute']) ? 1 : 0,
            'video'      => isset($_POST['perm_video']) ? 1 : 0,
        ],
        'channel'  => $channel,
        'featured' => isset($_POST['pkg_featured']) ? 1 : 0,
        // [1.5.291] Add-on do Assistente de IA (opcional no checkout).
        'ai_offered' => isset($_POST['pkg_ai_offered']) ? 1 : 0,
        'ai_price'   => isset($_POST['pkg_ai_price']) ? max(0, (float) $_POST['pkg_ai_price']) : 0.0,
        // [1.5.372] Descontos por duração (2/3/4/12 meses), em percentagem.
        'duration_discounts' => [
            2  => isset($_POST['pkg_disc_2'])  ? max(0, min(90, (float) $_POST['pkg_disc_2']))  : 0,
            3  => isset($_POST['pkg_disc_3'])  ? max(0, min(90, (float) $_POST['pkg_disc_3']))  : 0,
            4  => isset($_POST['pkg_disc_4'])  ? max(0, min(90, (float) $_POST['pkg_disc_4']))  : 0,
            12 => isset($_POST['pkg_disc_12']) ? max(0, min(90, (float) $_POST['pkg_disc_12'])) : 0,
        ],
    ];

    if ( $editing_id !== '' ) {
        // EDIT: actualizar pacote existente
        $found = false;
        foreach ( $packages as &$pkg ) {
            if ( $pkg['id'] === $editing_id ) {
                $pkg = array_merge( $pkg, $pack_data ); // mantém id + created_at
                $found = true;
                break;
            }
        }
        unset( $pkg );
        if ( ! $found ) {
            wp_redirect( admin_url('admin.php?page=rmbz-pacotes&status=error&msg=' . urlencode('Pacote não encontrado.')) ); exit;
        }
        error_log( sprintf( '[1MZ vip_pkg edit] Admin %s editou pacote %s.', wp_get_current_user()->user_login ?? '?', $editing_id ) );
        update_option('rmbz_vip_packages', $packages);
        wp_redirect( admin_url('admin.php?page=rmbz-pacotes&status=updated') ); exit;
    } else {
        // CREATE: novo pacote
        $pack_data['id']         = uniqid('pkg_');
        $pack_data['created_at'] = current_time('mysql', 1);
        $packages[] = $pack_data;
        error_log( sprintf( '[1MZ vip_pkg create] Admin %s criou pacote %s (%s).', wp_get_current_user()->user_login ?? '?', $pack_data['id'], $nome ) );
        update_option('rmbz_vip_packages', $packages);
        wp_redirect( admin_url('admin.php?page=rmbz-pacotes&status=success') ); exit;
    }
}
add_action('admin_post_rmbz_admin_save_package', 'rmbz_admin_save_package');

/**
 * [1.6.118.20] Toggle featured (destacado) de um pacote.
 */
function rmbz_admin_toggle_featured_package() {
    if ( ! isset( $_GET['id'], $_GET['_wpnonce'] ) ) return;
    $id = sanitize_text_field( $_GET['id'] );
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_toggle_featured_' . $id ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can( 'manage_options' ) ) wp_die('Sem permissão!');

    $packages = get_option('rmbz_vip_packages', []);
    foreach ( $packages as &$pkg ) {
        if ( $pkg['id'] === $id ) {
            $pkg['featured'] = empty( $pkg['featured'] ) ? 1 : 0;
            break;
        }
    }
    unset( $pkg );
    update_option('rmbz_vip_packages', $packages);
    wp_redirect( admin_url('admin.php?page=rmbz-pacotes') ); exit;
}
add_action('admin_post_rmbz_admin_toggle_featured_package', 'rmbz_admin_toggle_featured_package');

function rmbz_admin_delete_package() {
    if ( !isset($_GET['id'], $_GET['_wpnonce']) ) return;
    $id = sanitize_text_field($_GET['id']);
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_del_pack_' . $id ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão!');

    $packages = get_option('rmbz_vip_packages', []);
    $new_packages = [];
    foreach($packages as $p) { if ($p['id'] !== $id) { $new_packages[] = $p; } }
    
    update_option('rmbz_vip_packages', $new_packages);
    wp_redirect( admin_url('admin.php?page=rmbz-pacotes&status=deleted') ); exit;
}
add_action('admin_post_rmbz_admin_delete_package', 'rmbz_admin_delete_package');

function rmbz_admin_move_package() {
    if ( !isset($_GET['id'], $_GET['direction'], $_GET['_wpnonce']) ) return;
    $id = sanitize_text_field($_GET['id']);
    $direction = sanitize_text_field($_GET['direction']);
    
    if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rmbz_move_pack_' . $id ) ) wp_die('Falha de segurança!');
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão!');

    $packages = get_option('rmbz_vip_packages', []);
    $index = false;
    
    foreach ($packages as $k => $p) {
        if ($p['id'] === $id) { $index = $k; break; }
    }

    if ($index !== false) {
        if ($direction === 'up' && $index > 0) {
            // Troca com o pacote de cima
            $temp = $packages[$index - 1];
            $packages[$index - 1] = $packages[$index];
            $packages[$index] = $temp;
        } elseif ($direction === 'down' && $index < count($packages) - 1) {
            // Troca com o pacote de baixo
            $temp = $packages[$index + 1];
            $packages[$index + 1] = $packages[$index];
            $packages[$index] = $temp;
        }
        update_option('rmbz_vip_packages', $packages);
    }
    
    wp_redirect( admin_url('admin.php?page=rmbz-pacotes&status=moved') ); exit;
}
add_action('admin_post_rmbz_admin_move_package', 'rmbz_admin_move_package');

function rmbz_process_buy_vip_package() {
    if ( !isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'rmbz_buy_package_nonce') ) wp_die('Falha de segurança.');
    if ( !function_exists('rmbz_is_user_logged_in') || !rmbz_is_user_logged_in() ) wp_die('Acesso negado.');

    $telemovel = rmbz_get_current_user_telemovel();
    $package_id = sanitize_text_field($_POST['package_id']);
    
    $pacotes = get_option('rmbz_vip_packages', []);
    $selected = null;
    foreach($pacotes as $p) { if ($p['id'] === $package_id) { $selected = $p; break; } }
    if (!$selected) wp_die('Pacote inválido ou inexistente.');

    $perms = isset( $selected['perms'] ) && is_array( $selected['perms'] ) ? $selected['perms'] : [];

    // [1.5.291] Add-on do Assistente de IA (opcional). Só conta se o pacote o oferece.
    $ai_offered = ! empty( $selected['ai_offered'] );
    $include_ai = $ai_offered && ! empty( $_POST['include_ai'] );

    // Uma renovação ou mudança de plano nunca pode retirar permissões já
    // contratadas. Esta validação no servidor protege também contra POSTs
    // manuais, páginas em cache e interfaces antigas.
    global $wpdb;
    $table_wallets = $wpdb->prefix . 'rmbz_wallets';
    $table_subs = $wpdb->prefix . 'rmbz_subscriptions';
    $wallet_state = $wpdb->get_row( $wpdb->prepare(
        "SELECT balance, extra_data FROM {$table_wallets} WHERE user_telemovel = %s",
        $telemovel
    ) );
    $current_extra = $wallet_state && ! empty( $wallet_state->extra_data ) ? json_decode( (string) $wallet_state->extra_data, true ) : [];
    if ( ! is_array( $current_extra ) ) $current_extra = [];
    $active_end = $wpdb->get_var( $wpdb->prepare(
        "SELECT end_date FROM {$table_subs} WHERE user_telemovel = %s AND status = 'active' ORDER BY id DESC LIMIT 1",
        $telemovel
    ) );
    if ( $active_end && strtotime( (string) $active_end ) > time() ) {
        $current_perms = isset( $current_extra['vip_permissions'] ) && is_array( $current_extra['vip_permissions'] ) ? $current_extra['vip_permissions'] : [];
        // Compatibilidade com subscrições antigas que guardavam apenas o nome.
        if ( empty( $current_perms ) && ! empty( $current_extra['active_plan_name'] ) ) {
            foreach ( $pacotes as $legacy_package ) {
                if ( sanitize_title( (string) ( $legacy_package['nome'] ?? '' ) ) === sanitize_title( (string) $current_extra['active_plan_name'] ) ) {
                    $current_perms = isset( $legacy_package['perms'] ) && is_array( $legacy_package['perms'] ) ? $legacy_package['perms'] : [];
                    break;
                }
            }
        }
        $would_downgrade = false;
        foreach ( [ 'site', 'social', 'distribute', 'video' ] as $permission_key ) {
            if ( ! empty( $current_perms[ $permission_key ] ) && empty( $perms[ $permission_key ] ) ) {
                $would_downgrade = true;
                break;
            }
        }
        if ( ! empty( $current_perms['ai'] ) && ( ! $ai_offered || ! $include_ai ) ) $would_downgrade = true;
        if ( $would_downgrade ) {
            $_SESSION['rmbz_withdrawal_notice'] = [ 'error', 'Este pacote é inferior ao teu plano atual e não pode ser ativado. Escolhe a renovação atual ou um upgrade que mantenha todos os serviços.' ];
            wp_safe_redirect( wp_get_referer() ?: home_url( '/' ) );
            exit;
        }
    }

    // [1.5.372] Duração em meses (1/2/3/4/12). Preço = base×meses + IA×meses,
    // com desconto por duração. Dias = dias_base×meses.
    $months = isset($_POST['months']) ? (int) $_POST['months'] : 1;
    if ( $months <= 0 ) $months = 1;
    $price  = function_exists('rmbz_vip_compute_price')
        ? rmbz_vip_compute_price( $selected, $months, $include_ai ) : null;
    if ( $price === null ) {
        $_SESSION['rmbz_withdrawal_notice'] = ['error', 'Duração inválida para este plano.'];
        wp_redirect( wp_get_referer() ); exit;
    }
    $preco = (float) $price['base_total'];
    $dias  = (int) $price['days'];
    $total = (float) $price['total'];

    $saldo_atual = (float) ( $wallet_state->balance ?? 0 );

    if ($saldo_atual < $total) {
        $_SESSION['rmbz_withdrawal_notice'] = ['error', 'Saldo insuficiente. Custa ' . number_format($total, 2) . ' MT. Por favor, faça um depósito.'];
        wp_redirect( wp_get_referer() ); exit;
    }

    // Descontar saldo do artista
    $details = "Compra do Plano: " . $selected['nome'] . " (" . $dias . " dias)" . ( $include_ai ? ' + Assistente IA' : '' );
    $result = rmbz_update_wallet_balance($telemovel, -$total, 'subscription_payment', null, $details);

    if ($result['success']) {
        // 1. Dar as Permissões e guardar o Nome do Plano escolhido
        $extra = $current_extra;
        // Garantir que o add-on de IA é gravado conforme a escolha desta compra.
        if ( ! is_array( $perms ) ) $perms = [];
        $perms['ai'] = $include_ai ? 1 : 0;
        $extra['vip_permissions'] = $perms;
        $extra['active_plan_name'] = $selected['nome']; // Guardamos o nome do plano aqui, de forma segura!
        
        $wpdb->update($table_wallets, ['extra_data' => wp_json_encode($extra)], ['user_telemovel' => $telemovel]);

        // [1.6.380] Congelar a multa por atraso ANTES de estender a subscrição.
        if ( function_exists( 'rmbz_project_freeze_pending_fine' ) ) {
            rmbz_project_freeze_pending_fine( $telemovel );
        }

        // 2. Dar os Dias de Subscrição (Sem tentar usar a coluna 'plan_type' que não existe na DB)
        $sub = $wpdb->get_row($wpdb->prepare("SELECT id, end_date FROM {$table_subs} WHERE user_telemovel = %s AND status='active'", $telemovel));
        
        if ($sub && strtotime($sub->end_date) > time()) {
            // Soma dias se a subscrição já estiver ativa
            $new_end_date = date('Y-m-d H:i:s', strtotime($sub->end_date . " + {$dias} days"));
            $wpdb->update($table_subs, ['end_date' => $new_end_date], ['id' => $sub->id]);
        } else {
            // Cria uma nova subscrição
            $new_end_date = date('Y-m-d H:i:s', strtotime("+ {$dias} days"));
            $wpdb->insert($table_subs, [
                'user_telemovel' => $telemovel, 
                'status' => 'active',
                'start_date' => current_time('mysql'), 
                'end_date' => $new_end_date
            ]);
        }
        
        $_SESSION['rmbz_withdrawal_notice'] = ['success', 'Parabéns! O ' . $selected['nome'] . ' foi ativado com sucesso. Já pode aceder aos recursos do plano.'];

        // [1.5.131] Disparar hook para sistema de referrals validar a 2ª das
        // 2 condições da comissão (1º depósito aprovado + compra de plano VIP).
        do_action( 'rmbz_vip_plan_purchased', $telemovel, ($selected['id'] ?? 'unknown'), (float) $preco );

        // [1.5.138] Notificação ao utilizador via Centro (email + sino).
        // Template editável: vip_purchased.
        if ( function_exists('rmbz_notify') ) {
            rmbz_notify( $telemovel, 'vip_purchased', [
                'plano'           => $selected['nome'],
                'dias'            => (int) $dias,
                'data_expiracao'  => date_i18n( 'd/m/Y', strtotime($new_end_date) ),
            ]);
        }
    } else {
        $_SESSION['rmbz_withdrawal_notice'] = ['error', 'Falha ao processar o pagamento.'];
    }
    
    wp_redirect( wp_get_referer() ); exit;
}
add_action('admin_post_rmbz_buy_vip_package', 'rmbz_process_buy_vip_package');
add_action('admin_post_nopriv_rmbz_buy_vip_package', 'rmbz_process_buy_vip_package');

/**
 * ==========================================================================
 * SISTEMA DE CANCELAMENTO MANUAL E AUTOMÁTICO (COM DATAS FLEXÍVEIS E EMAIL)
 * ==========================================================================
 */

// 1. AÇÃO MANUAL: CANCELAMENTO PELO ADMIN + EMAIL AO ARTISTA
if ( ! function_exists( 'onemz_admin_revoke_contract' ) ) {
    function onemz_admin_revoke_contract() {
        if ( ! isset( $_POST['project_id'], $_POST['_wpnonce'] ) ) wp_die( 'Dados em falta.' );
        if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'onemz_revoke_contract_nonce' ) ) wp_die( 'Segurança.' );
        if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );

        $project_id = absint($_POST['project_id']);
        global $wpdb;
        $table = $wpdb->prefix . 'rmbz_artist_projects';
        $table_email = $wpdb->prefix . 'rmbz_email_logs';
        $table_wallets = $wpdb->prefix . 'rmbz_wallets';
        
        $project = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $project_id));
        if ($project) {
            $extra = !empty($project->extra_data) ? json_decode($project->extra_data, true) : [];
            $extra['admin_rejection_reason'] = "O orçamento/contrato foi revogado e cancelado pela administração (Falta de resposta ou alteração de agenda).";
            
            unset($extra['contract_url'], $extra['contract_sent_date'], $extra['contract_deadline'], $extra['contract_accepted'], $extra['contract_rejected'], $extra['expiring_email_sent']);
            
            $wpdb->update($table, [
                'production_status' => 'rejected',
                'extra_data' => wp_json_encode($extra)
            ], ['id' => $project_id]);

            // PUXAR CONFIGURAÇÕES OFICIAIS DO PLUGIN E ENVIAR E-MAIL
            $user = $wpdb->get_row($wpdb->prepare("SELECT user_email, user_nome FROM {$table_wallets} WHERE user_telemovel = %s", $project->artist_telemovel));
            
            if ($user && !empty($user->user_email) && strpos($user->user_email, '@1mz.local') === false) {
                
                $tpl = get_option('rmbz_email_templates');
                $sender_name = !empty($tpl['sender_name']) ? $tpl['sender_name'] : get_bloginfo('name');
                $sender_email = is_email($tpl['sender_email'] ?? '') ? $tpl['sender_email'] : get_option('admin_email');
                
                $subject = "Aviso sobre o contrato do videoclipe: " . esc_html($project->project_title);
                
                $inner_msg = '
                <p>Olá <strong>' . esc_html($user->user_nome) . '</strong>,</p>
                <p>Informamos que o orçamento e o contrato do videoclipe <strong>\'' . esc_html($project->project_title) . '\'</strong> foram <strong>cancelados e apagados</strong> pela nossa equipa de produção.</p>
                <p><strong>Motivo da Decisão:</strong></p>
                <div style="background:#ffebee;border-left:4px solid #c0392b;padding:15px;margin:10px 0;color:#c0392b;font-style:italic">
                O contrato foi revogado devido a falta de resposta, alteração do orçamento ou reajuste da agenda técnica da equipa.
                </div>
                <p>Se achar que foi um erro ou se desejar retomar a produção no futuro, não hesite em submeter uma nova candidatura e a nossa equipa gerará um novo orçamento para si.</p>
                <p>Estamos sempre aqui para apoiar o teu crescimento.</p>
                <p>Um abraço de força,<br /><strong>Equipa ' . esc_html($sender_name) . '</strong></p>';
                
                // Constrói o HTML usando o design oficial da 1MZ
                if ( function_exists('rmbz_build_html_email') ) {
                    $html_body = rmbz_build_html_email($inner_msg, $sender_name);
                } else {
                    $html_body = $inner_msg; 
                }

                $headers = [
                    'Content-Type: text/html; charset=UTF-8',
                    'From: ' . $sender_name . ' <' . $sender_email . '>'
                ];
                
                // Regista no sino
                $wpdb->insert($table_email, ['recipient_email' => $user->user_email, 'subject' => $subject, 'body' => "O seu contrato para o videoclipe ".esc_html($project->project_title)." foi cancelado pela administração.", 'sent_date' => current_time('mysql'), 'is_read' => 0, 'is_cleared' => 0]);
                
                // Envia e-mail
                $sent = wp_mail($user->user_email, $subject, $html_body, $headers);
                
                if (function_exists('rmbz_log_sent_email')) {
                    rmbz_log_sent_email($user->user_email, $subject, $html_body, $sent ? 'sucesso' : 'falha');
                }
            }
        }
        
        // Evita Ecrã Branco limpando qualquer espaço em branco perdido no código antes de redirecionar
        if (ob_get_length()) { ob_clean(); }
        wp_safe_redirect(wp_get_referer()); 
        exit;
    }
    add_action('admin_post_onemz_admin_revoke_contract', 'onemz_admin_revoke_contract');
}

// 2. ROBÔ DE EXPIRAÇÃO AUTOMÁTICA (DATA FLEXÍVEL OU 3 DIAS)
if ( ! function_exists( 'onemz_auto_expire_contracts' ) ) {
    function onemz_auto_expire_contracts() {
        if ( get_transient('onemz_contracts_checked') ) return;
        set_transient('onemz_contracts_checked', true, 3600); // Executa a cada 1 hora

        global $wpdb;
        $table_proj = $wpdb->prefix . 'rmbz_artist_projects';
        $table_email = $wpdb->prefix . 'rmbz_email_logs';
        $table_wallets = $wpdb->prefix . 'rmbz_wallets';
        
        $projects = $wpdb->get_results("SELECT * FROM {$table_proj} WHERE production_status NOT IN ('done', 'rejected')");

        foreach ($projects as $p) {
            $extra = !empty($p->extra_data) ? json_decode($p->extra_data, true) : [];
            
            if (empty($extra['contract_url']) || !empty($extra['contract_accepted']) || !empty($extra['contract_rejected'])) continue;

            $user = $wpdb->get_row($wpdb->prepare("SELECT user_email, user_nome FROM {$table_wallets} WHERE user_telemovel = %s", $p->artist_telemovel));
            $user_email = ($user && !empty($user->user_email) && strpos($user->user_email, '@1mz.local') === false) ? $user->user_email : '';

            $tpl = get_option('rmbz_email_templates');
            $sender_name = !empty($tpl['sender_name']) ? $tpl['sender_name'] : get_bloginfo('name');
            $sender_email = is_email($tpl['sender_email'] ?? '') ? $tpl['sender_email'] : get_option('admin_email');

            // ==========================================================
            // A) AVISO INICIAL - O RELÓGIO COMEÇOU A CONTAR (NOVO)
            // ==========================================================
            if (empty($extra['contract_sent_date'])) {
                $extra['contract_sent_date'] = time();
                $wpdb->update($table_proj, ['extra_data' => wp_json_encode($extra)], ['id' => $p->id]);
                
                if ($user_email) {
                    $deadline = !empty($extra['contract_deadline']) ? $extra['contract_deadline'] : ($extra['contract_sent_date'] + 259200);
                    $data_limite = wp_date('d/m/Y \à\s H:i', $deadline);

                    $subject = "⏳ Novo Contrato Disponível: " . esc_html($p->project_title);
                    
                    $inner_msg = '
                    <p>Olá <strong>' . esc_html($user->user_nome) . '</strong>,</p>
                    <p>A equipa da 1MZ acabou de disponibilizar o contrato oficial para a produção do seu videoclipe <strong>\'' . esc_html($p->project_title) . '\'</strong>.</p>
                    
                    <div style="background:#1a1a1a; border:1px solid #333; border-left:4px solid #f0b90b; padding:15px; margin:20px 0; color:#dddddd; border-radius:4px;">
                    <strong style="font-size:16px; color:#f0b90b;">⏳ O Cronómetro Iniciou!</strong><br><br>
                    Para garantirmos a disponibilidade da nossa equipa e equipamento, este orçamento tem uma data de validade. Tem até ao dia <strong style="color:#ffffff;">' . $data_limite . '</strong> para ler e assinar o documento na plataforma.
                    </div>
                    
                    <p>Se não recebermos a sua resposta até essa data, o orçamento será cancelado e apagado automaticamente pelo sistema.</p>
                    <p>Aceda agora mesmo ao seu painel 1MZ, no separador <strong>"Produção Vídeos 🏆"</strong>, para dar o próximo passo.</p>
                    <p>Um abraço de força,<br /><strong>Equipa ' . esc_html($sender_name) . '</strong></p>';
                    
                    if ( function_exists('rmbz_build_html_email') ) { $html_body = rmbz_build_html_email($inner_msg, $sender_name); } else { $html_body = $inner_msg; }
                    
                    $headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $sender_name . ' <' . $sender_email . '>'];
                    $wpdb->insert($table_email, ['recipient_email' => $user_email, 'subject' => $subject, 'body' => "O contrato foi enviado. Prazo limite definido.", 'sent_date' => current_time('mysql'), 'is_read' => 0, 'is_cleared' => 0]);
                    
                    $sent = wp_mail($user_email, $subject, $html_body, $headers);
                    if (function_exists('rmbz_log_sent_email')) { rmbz_log_sent_email($user_email, $subject, $html_body, $sent ? 'sucesso' : 'falha'); }
                }
                continue;
            }

            // ==========================================================
            // CÁLCULO DO TEMPO
            // ==========================================================
            $deadline = !empty($extra['contract_deadline']) ? $extra['contract_deadline'] : ($extra['contract_sent_date'] + 259200);
            $time_left = $deadline - time(); 

            // ==========================================================
            // B) AVISO DE FALTAM 24 HORAS
            // ==========================================================
            if ($time_left > 0 && $time_left <= 86400 && empty($extra['expiring_email_sent'])) {
                $extra['expiring_email_sent'] = true;
                $wpdb->update($table_proj, ['extra_data' => wp_json_encode($extra)], ['id' => $p->id]);

                if ($user_email) {
                    $subject = "⚠️ O seu contrato expira em menos de 24 horas!";
                    
                    $inner_msg = '
                    <p>Olá <strong>' . esc_html($user->user_nome) . '</strong>,</p>
                    <p>O prazo para aceitar o contrato do videoclipe <strong>\'' . esc_html($p->project_title) . '\'</strong> termina em menos de 24 horas (no dia <strong>'.wp_date('d/m/Y \à\s H:i', $deadline).'</strong>).</p>
                    <p>Se não houver resposta, o orçamento será automaticamente cancelado pelo sistema para libertar a agenda da nossa equipa.</p>
                    <p>Por favor, aceda à sua conta na plataforma para ler e assinar o documento antes que o tempo esgote.</p>
                    <p>Um abraço de força,<br /><strong>Equipa ' . esc_html($sender_name) . '</strong></p>';
                    
                    if ( function_exists('rmbz_build_html_email') ) { $html_body = rmbz_build_html_email($inner_msg, $sender_name); } else { $html_body = $inner_msg; }
                    
                    $headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $sender_name . ' <' . $sender_email . '>'];
                    $wpdb->insert($table_email, ['recipient_email' => $user_email, 'subject' => $subject, 'body' => "O seu contrato expira em 24h.", 'sent_date' => current_time('mysql'), 'is_read' => 0, 'is_cleared' => 0]);
                    
                    $sent = wp_mail($user_email, $subject, $html_body, $headers);
                    if (function_exists('rmbz_log_sent_email')) { rmbz_log_sent_email($user_email, $subject, $html_body, $sent ? 'sucesso' : 'falha'); }
                }
            }

            // ==========================================================
            // C) O PRAZO ACABOU - CANCELAR TUDO
            // ==========================================================
            if ($time_left <= 0) {
                $extra['admin_rejection_reason'] = "O projeto foi cancelado automaticamente porque o prazo limite de resposta expirou.";
                unset($extra['contract_url'], $extra['contract_sent_date'], $extra['contract_deadline'], $extra['expiring_email_sent']);

                $wpdb->update($table_proj, [
                    'production_status' => 'rejected',
                    'extra_data' => wp_json_encode($extra)
                ], ['id' => $p->id]);

                if ($user_email) {
                    $subject = "❌ Prazo Expirado: Contrato Cancelado";
                    
                    $inner_msg = '
                    <p>Olá <strong>' . esc_html($user->user_nome) . '</strong>,</p>
                    <p>Informamos que o prazo limite para assinar o contrato do videoclipe <strong>\'' . esc_html($p->project_title) . '\'</strong> expirou.</p>
                    <p>Por esse motivo, o projeto e o orçamento foram <strong>apagados e cancelados automaticamente</strong> pelo nosso sistema e a equipa foi libertada para outras produções.</p>
                    <p><strong>Motivo da Decisão:</strong></p>
                    <div style="background:#ffebee;border-left:4px solid #c0392b;padding:15px;margin:10px 0;color:#c0392b;font-style:italic">
                    O prazo de resposta esgotou. O sistema fechou o orçamento para não prender a agenda técnica da equipa.
                    </div>
                    <p>Se desejar retomar a produção, não hesite em submeter uma nova candidatura e a nossa equipa gerará um novo orçamento para si.</p>
                    <p>Estamos sempre aqui para apoiar o teu crescimento.</p>
                    <p>Um abraço de força,<br /><strong>Equipa ' . esc_html($sender_name) . '</strong></p>';
                    
                    if ( function_exists('rmbz_build_html_email') ) { $html_body = rmbz_build_html_email($inner_msg, $sender_name); } else { $html_body = $inner_msg; }
                    
                    $headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $sender_name . ' <' . $sender_email . '>'];
                    $wpdb->insert($table_email, ['recipient_email' => $user_email, 'subject' => $subject, 'body' => "Prazo expirado. Contrato apagado.", 'sent_date' => current_time('mysql'), 'is_read' => 0, 'is_cleared' => 0]);
                    
                    $sent = wp_mail($user_email, $subject, $html_body, $headers);
                    if (function_exists('rmbz_log_sent_email')) { rmbz_log_sent_email($user_email, $subject, $html_body, $sent ? 'sucesso' : 'falha'); }
                }
            }
        }
    }
    add_action('init', 'onemz_auto_expire_contracts');
}

/* ------------------------------------------------------------------------- */
/* [1.5.397] IA: gerar "Pitch da Produção" a partir do pitch do artista       */
/* + ideias da equipa. Usa o cliente Anthropic já existente (rmbz_ai_messages)*/
/* ------------------------------------------------------------------------- */
add_action( 'wp_ajax_rmbz_ai_generate_pitch', 'rmbz_ai_generate_pitch_handler' );
function rmbz_ai_generate_pitch_handler() {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( [ 'message' => 'Sem permissão.' ], 403 );
    }
    $project_id = isset( $_POST['project_id'] ) ? (int) $_POST['project_id'] : 0;
    if ( $project_id <= 0 || ! check_ajax_referer( 'rmbz_ai_pitch_' . $project_id, '_wpnonce', false ) ) {
        wp_send_json_error( [ 'message' => 'Sessão expirada — recarrega a página.' ], 403 );
    }
    if ( ! function_exists( 'rmbz_ai_messages' ) || ! function_exists( 'rmbz_ai_available' ) || ! rmbz_ai_available() ) {
        wp_send_json_error( [ 'message' => 'IA não configurada (chave Anthropic em falta).' ] );
    }

    global $wpdb;
    $p = $wpdb->get_row( $wpdb->prepare(
        "SELECT p.*, w.user_nome
         FROM {$wpdb->prefix}rmbz_artist_projects p
         LEFT JOIN {$wpdb->prefix}rmbz_wallets w ON w.user_telemovel = p.artist_telemovel
         WHERE p.id = %d",
        $project_id
    ) );
    if ( ! $p ) {
        wp_send_json_error( [ 'message' => 'Projeto não encontrado.' ] );
    }

    $ideas  = isset( $_POST['ideas'] ) ? trim( sanitize_textarea_field( wp_unslash( (string) $_POST['ideas'] ) ) ) : '';
    $extra  = ! empty( $p->extra_data ) ? json_decode( $p->extra_data, true ) : [];
    if ( ! is_array( $extra ) ) $extra = [];
    $artist = $p->user_nome ?: 'Artista';

    // Localização escolhida/definida (se existir) dá cor local ao roteiro.
    $location = '';
    if ( ! empty( $extra['budget_locations'] ) && is_array( $extra['budget_locations'] ) ) {
        $names = [];
        foreach ( $extra['budget_locations'] as $loc ) {
            if ( ! empty( $loc['name'] ) ) $names[] = (string) $loc['name'];
        }
        $location = implode( ' / ', array_slice( $names, 0, 3 ) );
    }

    $system = 'És o diretor criativo da 1MZ Records (editora de música em Moçambique). '
        . 'Escreves o "Pitch da Produção": a visão criativa/roteiro que a equipa partilha com o artista '
        . 'sobre como o videoclipe vai ser produzido. Escreve em português de Moçambique, tom caloroso, '
        . 'entusiasmante e profissional, na 1ª pessoa do plural ("vamos"). '
        . 'REGRAS: texto corrido em 2-3 parágrafos curtos (120 a 200 palavras no total), SEM markdown, '
        . 'SEM listas, SEM títulos. Descreve cenas visuais concretas (planos, luz, ritmo de montagem) '
        . 'ligadas à música e à identidade do artista. NUNCA inventes valores, orçamentos, datas nem '
        . 'promessas de equipamento — fala só da visão criativa. Começa por cumprimentar o artista pelo nome.';

    $user = "ARTISTA: {$artist}\n"
        . 'MÚSICA/PROJETO: ' . ( $p->project_title ?: '—' ) . "\n"
        . 'CENÁRIO CONTRATADO: ' . ( $p->scenario_type ?: '—' ) . "\n"
        . ( $location !== '' ? "LOCALIZAÇÃO PREVISTA: {$location}\n" : '' )
        . "\nPITCH DO ARTISTA (a ideia dele para o vídeo):\n" . ( trim( (string) $p->project_pitch ) !== '' ? $p->project_pitch : '(o artista não escreveu pitch)' ) . "\n"
        . ( $ideas !== '' ? "\nIDEIAS DA EQUIPA 1MZ (incorpora-as na visão):\n{$ideas}\n" : '' )
        . "\nEscreve agora o Pitch da Produção para enviar ao {$artist}.";

    $resp = rmbz_ai_messages( $system, $user, [ 'max_tokens' => 700, 'temperature' => 0.8, 'timeout' => 40 ] );
    if ( empty( $resp['ok'] ) ) {
        wp_send_json_error( [ 'message' => 'A IA falhou: ' . ( $resp['error'] ?: 'erro desconhecido' ) ] );
    }

    wp_send_json_success( [ 'pitch' => trim( (string) $resp['text'] ) ] );
}
