<?php
/**
 * includes/distribution/distribution-splits.php
 *
 * Gestão de splits de faixas — colaboradores que recebem parte dos royalties.
 *
 * Antes deste módulo, o plugin tinha um buraco silencioso e grave:
 *
 *   1. O artista preenchia splits no editor de faixa → guardavam-se na tabela
 *      `rmbz_track_splits` com invite_status='pending'.
 *   2. O importer de royalties SÓ pagava a splits com invite_status='accepted'.
 *   3. MAS NUNCA HAVIA forma de aceitar um split — não existia handler, email,
 *      notificação, nada.
 *   4. Resultado: todos os splits ficavam 'pending' para sempre, e todo o
 *      dinheiro ia para o owner — mesmo que o artista tivesse declarado 50%
 *      para um colaborador.
 *
 * Este módulo resolve:
 *
 *   A. AUTO-ACCEPT INTELIGENTE
 *      Quando o artista grava splits no editor, para cada colaborador:
 *        • Se o telemóvel está registado na 1MZ → marca 'accepted' e notifica.
 *        • Se NÃO está registado → marca 'external_hold' e o valor ficará
 *          retido numa tabela nova até o colaborador se registar.
 *
 *   B. PAINEL ADMIN COMPLETO
 *      No detalhe do release, cada faixa passa a mostrar a lista completa
 *      de splits (nome, telemóvel, %, role, estado). O admin pode:
 *        • Editar percentagens (mantendo soma em 100%)
 *        • Adicionar/remover splits
 *        • Forçar accept/reject de qualquer split
 *      Todas as mudanças vão para audit log dedicado.
 *
 *   C. RETENÇÃO PARA EXTERNOS
 *      Nova tabela `rmbz_splits_holdings` acumula os valores dos splits
 *      'external_hold'. Quando um artista novo se regista com um dos
 *      telemóveis em hold (detectado no próximo login), os splits
 *      convertem automaticamente para 'accepted' e o saldo acumulado
 *      é creditado na wallet nova.
 *
 *   D. NOTIFICAÇÕES
 *      Colaborador interno é notificado imediatamente quando é adicionado
 *      a um split — com botão para rejeitar se não for verdade (abuso).
 */

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


/* ============================================================================
 * SCHEMA — tabelas novas
 * ============================================================================ */

function rmbz_splits_maybe_install() {
    $stored = get_option('rmbz_splits_module_schema_v', '0');

    // v1.0 — tabelas base
    if ( version_compare( $stored, '1.0', '<' ) ) {
        global $wpdb;
        $charset_collate = $wpdb->get_charset_collate();
        $sql = [];

        // Retenção para artistas externos
        $sql[] = "CREATE TABLE {$wpdb->prefix}rmbz_splits_holdings (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            collaborator_telemovel varchar(20) NOT NULL,
            track_id bigint(20) NOT NULL,
            release_id bigint(20) NOT NULL,
            split_id bigint(20) DEFAULT NULL,
            amount_usd decimal(12,4) NOT NULL,
            source_description varchar(200) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
            claimed_at datetime DEFAULT NULL,
            claimed_by_telemovel varchar(20) DEFAULT NULL,
            PRIMARY KEY  (id),
            KEY idx_tel (collaborator_telemovel),
            KEY idx_track (track_id),
            KEY idx_claimed (claimed_at)
        ) ENGINE=InnoDB $charset_collate;";

        // Audit log dedicado para mudanças em splits
        $sql[] = "CREATE TABLE {$wpdb->prefix}rmbz_splits_audit (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            split_id bigint(20) DEFAULT NULL,
            track_id bigint(20) NOT NULL,
            release_id bigint(20) NOT NULL,
            action varchar(30) NOT NULL,
            actor varchar(80) NOT NULL,
            before_json text DEFAULT NULL,
            after_json text DEFAULT NULL,
            note varchar(500) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
            PRIMARY KEY  (id),
            KEY idx_track (track_id),
            KEY idx_split (split_id)
        ) ENGINE=InnoDB $charset_collate;";

        require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
        foreach ( $sql as $q ) dbDelta( $q );

        update_option('rmbz_splits_module_schema_v', '1.0');
        $stored = '1.0';
    }

    // v1.1 — adicionar pending_approval + import_hash + reverted_at na holdings
    // para integração com o fluxo de aprovação de imports (mesmo padrão do ledger)
    if ( version_compare( $stored, '1.1', '<' ) ) {
        global $wpdb;
        $table = $wpdb->prefix . 'rmbz_splits_holdings';
        $cols = $wpdb->get_col("SHOW COLUMNS FROM $table");

        if ( ! in_array('pending_approval', (array) $cols, true) ) {
            $wpdb->query("ALTER TABLE $table ADD COLUMN pending_approval tinyint(1) DEFAULT 1 NOT NULL AFTER amount_usd");
        }
        if ( ! in_array('import_hash', (array) $cols, true) ) {
            $wpdb->query("ALTER TABLE $table ADD COLUMN import_hash varchar(64) DEFAULT NULL AFTER pending_approval");
            $wpdb->query("ALTER TABLE $table ADD INDEX idx_import_hash (import_hash)");
        }
        if ( ! in_array('reverted_at', (array) $cols, true) ) {
            $wpdb->query("ALTER TABLE $table ADD COLUMN reverted_at datetime DEFAULT NULL AFTER claimed_by_telemovel");
        }

        update_option('rmbz_splits_module_schema_v', '1.1');
    }
}
add_action('admin_init', 'rmbz_splits_maybe_install', 8);


/* ============================================================================
 * HELPERS BÁSICOS
 * ============================================================================ */

/**
 * Verifica se um telemóvel está registado como artista na 1MZ.
 * Normaliza o formato (remove espaços/hífens, força "+258...").
 */
function rmbz_splits_is_registered_telemovel( $telemovel ) {
    $tel = rmbz_splits_normalize_telemovel( $telemovel );
    if ( $tel === '' ) return false;

    global $wpdb;
    $exists = $wpdb->get_var( $wpdb->prepare(
        "SELECT id FROM {$wpdb->prefix}rmbz_wallets WHERE user_telemovel = %s LIMIT 1",
        $tel
    ) );
    return ! empty($exists);
}

function rmbz_splits_normalize_telemovel( $raw ) {
    $raw = trim( (string) $raw );
    if ( $raw === '' ) return '';
    // Remove tudo excepto dígitos e o prefixo +
    $clean = preg_replace('/[^0-9+]/', '', $raw);
    // Se começa por 00, converte para +
    if ( strpos($clean, '00') === 0 ) $clean = '+' . substr($clean, 2);
    return $clean;
}

/**
 * Regista uma acção no audit log dos splits.
 */
function rmbz_splits_audit( $action, $track_id, $release_id, $split_id = null, $before = null, $after = null, $note = '' ) {
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_splits_audit';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") !== $t ) return;

    $user = wp_get_current_user();
    $actor = $user && $user->exists() ? ( $user->user_login ?: 'admin#' . $user->ID ) : 'sistema';
    // Se estiver num contexto de artista (sessão custom)
    if ( ( ! $user || ! $user->exists() ) && function_exists('rmbz_get_current_user_telemovel') ) {
        $tel = rmbz_get_current_user_telemovel();
        if ( $tel ) $actor = 'artist:' . $tel;
    }

    $wpdb->insert( $t, [
        'split_id'    => $split_id,
        'track_id'    => $track_id,
        'release_id'  => $release_id,
        'action'      => $action,
        'actor'       => $actor,
        'before_json' => $before ? wp_json_encode($before) : null,
        'after_json'  => $after  ? wp_json_encode($after)  : null,
        'note'        => $note ? mb_substr($note, 0, 500) : null,
    ], [ '%d','%d','%d','%s','%s','%s','%s','%s' ] );
}


/* ============================================================================
 * AUTO-ACCEPT — hook chamado depois do artista gravar splits
 * ----------------------------------------------------------------------------
 * O `rmbz_ajax_track_editor_save` em distribution-track-editor.php já grava
 * todos os splits com invite_status='pending'. Este filter intercepta o
 * processo e altera o estado conforme o telemóvel.
 *
 * Como o handler actual não tem hook explícito, intervenho DEPOIS do save:
 * subscrevo um action custom disparado no fim. Se o patch do track-editor
 * não disparar o action (plugin antigo), corremos um varrimento on-demand
 * quando a UI admin abre o release.
 * ============================================================================ */

/**
 * Após save de splits, classifica cada um.
 */
function rmbz_splits_post_save_classify( $track_id ) {
    if ( empty($track_id) ) return;

    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_track_splits';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") !== $t ) return;

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT * FROM $t WHERE track_id = %d AND invite_status = 'pending'",
        $track_id
    ) );
    if ( empty($rows) ) return;

    foreach ( $rows as $row ) {
        $tel = rmbz_splits_normalize_telemovel( $row->collaborator_telemovel );

        if ( $tel === '' ) {
            // Sem telemóvel — fica pending, admin tem de tratar
            continue;
        }

        if ( rmbz_splits_is_registered_telemovel( $tel ) ) {
            // INTERNO — auto-accept + notificação
            $wpdb->update( $t,
                [
                    'invite_status'          => 'accepted',
                    'accepted_at'            => current_time('mysql'),
                    'collaborator_telemovel' => $tel,
                ],
                [ 'id' => $row->id ],
                [ '%s','%s','%s' ],
                [ '%d' ]
            );

            rmbz_splits_audit(
                'auto_accept_internal',
                $row->track_id,
                $row->release_id,
                $row->id,
                [ 'status' => 'pending' ],
                [ 'status' => 'accepted' ],
                sprintf( 'Auto-accepted (telemóvel %s registado na 1MZ)', $tel )
            );

            // Notificar colaborador
            rmbz_splits_notify_collaborator( $row, 'added' );
        } else {
            // EXTERNO — colocar em hold
            $wpdb->update( $t,
                [
                    'invite_status'          => 'external_hold',
                    'collaborator_telemovel' => $tel,
                ],
                [ 'id' => $row->id ],
                [ '%s','%s' ],
                [ '%d' ]
            );

            rmbz_splits_audit(
                'external_hold',
                $row->track_id,
                $row->release_id,
                $row->id,
                [ 'status' => 'pending' ],
                [ 'status' => 'external_hold' ],
                sprintf( 'Telemóvel %s não está registado na 1MZ. Royalties retidos até reclamação.', $tel )
            );
        }
    }
}
add_action('rmbz_track_splits_saved', 'rmbz_splits_post_save_classify');

/**
 * Fallback — varrimento silencioso de splits 'pending' quando a UI do admin
 * abre um release. Resolve casos onde o save ainda usava o track-editor
 * antigo sem o action hook.
 */
function rmbz_splits_sweep_pending_for_release( $release_id ) {
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_track_splits';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") !== $t ) return;

    $tracks = $wpdb->get_col( $wpdb->prepare(
        "SELECT DISTINCT track_id FROM $t WHERE release_id = %d AND invite_status = 'pending'",
        $release_id
    ) );
    foreach ( $tracks as $tid ) {
        rmbz_splits_post_save_classify( (int) $tid );
    }
}


/* ============================================================================
 * NOTIFICAÇÕES — chamar rmbz_add_notification se existir
 * ============================================================================ */

function rmbz_splits_notify_collaborator( $split_row, $event = 'added' ) {
    if ( ! function_exists('rmbz_add_notification') ) return;
    if ( empty($split_row->collaborator_telemovel) ) return;

    global $wpdb;
    // Info do release para mensagem útil
    $release = $wpdb->get_row( $wpdb->prepare(
        "SELECT r.title, r.artist_telemovel, w.user_nome AS owner_name
         FROM {$wpdb->prefix}rmbz_distro_releases r
         LEFT JOIN {$wpdb->prefix}rmbz_wallets w ON w.user_telemovel = r.artist_telemovel
         WHERE r.id = %d",
        $split_row->release_id
    ) );
    $track_title = $wpdb->get_var( $wpdb->prepare(
        "SELECT track_title FROM {$wpdb->prefix}rmbz_distro_tracks WHERE id = %d",
        $split_row->track_id
    ) );
    if ( ! $release ) return;

    $owner = $release->owner_name ?: 'Um artista';
    $pct   = number_format( (float) $split_row->percentage, 2 );
    $title = $track_title ?: 'uma faixa';

    $messages = [
        'added'    => sprintf(
            '🤝 Foste adicionado como colaborador em "%s" por %s com %s%%. Se não é verdade, rejeita no teu painel.',
            $title, $owner, $pct
        ),
        'rejected' => sprintf(
            '✗ O split em "%s" foi rejeitado.', $title
        ),
        'claimed'  => sprintf(
            '💰 Royalties retidos em "%s" foram libertados para a tua wallet.', $title
        ),
    ];

    $msg = $messages[$event] ?? null;
    if ( $msg ) {
        rmbz_add_notification( $split_row->collaborator_telemovel, $msg );
    }
}


/* ============================================================================
 * CLAIM-ON-LOGIN — quando artista externo entra na 1MZ, converte holds
 * ----------------------------------------------------------------------------
 * Disparado via filter no wallet balance check (já é chamado a cada login).
 * Como não há hook `rmbz_user_registered` no plugin, usamos o login como
 * proxy — qualquer login dispara um varrimento leve por aquele telemóvel.
 * É idempotente (só actua se houver holds + wallet existe).
 * ============================================================================ */

function rmbz_splits_claim_holdings_on_login( $telemovel ) {
    if ( empty($telemovel) ) return;
    $tel = rmbz_splits_normalize_telemovel( $telemovel );
    if ( $tel === '' ) return;

    global $wpdb;
    $t_splits   = $wpdb->prefix . 'rmbz_track_splits';
    $t_holdings = $wpdb->prefix . 'rmbz_splits_holdings';

    // 1. Converter splits 'external_hold' → 'accepted' para este telemóvel
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t_splits'") === $t_splits ) {
        $pending_splits = $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM $t_splits WHERE collaborator_telemovel = %s AND invite_status = 'external_hold'",
            $tel
        ) );

        foreach ( $pending_splits as $row ) {
            $wpdb->update( $t_splits,
                [ 'invite_status' => 'accepted', 'accepted_at' => current_time('mysql') ],
                [ 'id' => $row->id ],
                [ '%s', '%s' ], [ '%d' ]
            );
            rmbz_splits_audit(
                'claimed_on_register',
                $row->track_id,
                $row->release_id,
                $row->id,
                [ 'status' => 'external_hold' ],
                [ 'status' => 'accepted' ],
                sprintf('Colaborador %s registou-se e reclamou automaticamente o split.', $tel)
            );
        }
    }

    // 2. Creditar holdings acumuladas na wallet
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t_holdings'") === $t_holdings ) {
        // Respeita pending_approval + reverted_at (schema v1.1+). Em schemas
        // antigos as colunas não existem — nesse caso, degradamos para o
        // comportamento antigo (qualquer hold não-reclamado é creditado).
        $cols = $wpdb->get_col("SHOW COLUMNS FROM $t_holdings");
        $extra_where = '';
        if ( in_array('pending_approval', (array) $cols, true) ) {
            $extra_where .= ' AND pending_approval = 0';
        }
        if ( in_array('reverted_at', (array) $cols, true) ) {
            $extra_where .= ' AND reverted_at IS NULL';
        }

        $holdings = $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM $t_holdings
             WHERE collaborator_telemovel = %s
               AND claimed_at IS NULL
               $extra_where",
            $tel
        ) );

        if ( empty($holdings) ) return;

        $total_credited = 0.0;
        $track_ids = [];

        foreach ( $holdings as $h ) {
            // Marcar como claimed
            $wpdb->update( $t_holdings,
                [
                    'claimed_at'           => current_time('mysql'),
                    'claimed_by_telemovel' => $tel,
                ],
                [ 'id' => $h->id ],
                [ '%s', '%s' ], [ '%d' ]
            );
            $total_credited += (float) $h->amount_usd;
            $track_ids[] = (int) $h->track_id;
        }

        // Creditar a wallet via função existente, se disponível
        if ( $total_credited > 0 && function_exists('rmbz_wallet_add_balance') ) {
            rmbz_wallet_add_balance(
                $tel,
                $total_credited,
                sprintf('Royalties retidos libertados (%d split(s))', count($holdings))
            );
        }

        // Notificação simples
        if ( function_exists('rmbz_add_notification') ) {
            rmbz_add_notification(
                $tel,
                sprintf( '💰 Bem-vindo! Foram-te creditados $%s de royalties retidos de %d colaboração(ões) em lançamentos já existentes.',
                    number_format( $total_credited, 2 ),
                    count( array_unique($track_ids) )
                )
            );
        }
    }
}

/**
 * Hook no set_logged_in para correr o claim.
 * `rmbz_set_user_logged_in` é definida em auth-functions.php — se não
 * existir, fazemos fallback no rmbz_is_user_logged_in check.
 */
add_action('rmbz_user_logged_in', 'rmbz_splits_claim_holdings_on_login');

/**
 * Fallback — varrimento oportunista quando o user visita o my-account.
 * Usa uma transient de 1h para não correr a cada pageview.
 */
add_action('init', function () {
    if ( ! function_exists('rmbz_is_user_logged_in') || ! rmbz_is_user_logged_in() ) return;
    $tel = rmbz_get_current_user_telemovel();
    if ( ! $tel ) return;

    $key = 'rmbz_splits_claimed_' . md5($tel);
    if ( get_transient($key) ) return;

    rmbz_splits_claim_holdings_on_login( $tel );
    set_transient( $key, 1, HOUR_IN_SECONDS );
}, 50);


/* ============================================================================
 * INTEGRAÇÃO COM IMPORTER DE ROYALTIES
 * ----------------------------------------------------------------------------
 * O `rmbz_compute_splits` em includes/admin/import-processor.php devolve
 * [telemovel => valor] mas ignora os `external_hold`. Precisamos capturar
 * os external_hold e guardá-los na tabela de holdings em vez de os perder.
 *
 * Como não posso alterar a função directamente sem reescrever o importer,
 * uso um filter/wrapper. O importer já foi refactored em várias versões,
 * por isso a abordagem segura é: adicionar um hook que, depois do import,
 * persiste os holds dos splits relevantes.
 *
 * Para não depender de patches no importer, faço um job diário (cron) que
 * varre splits em 'external_hold' recentemente usados e garante que estão
 * espelhados em rmbz_splits_holdings. Não é perfeito mas é robusto.
 * ============================================================================ */

/**
 * Chamada esperada pelo patch no importer: cria uma linha em holdings.
 * Se o importer não chamar, a informação fica-se pelo audit log (seguro).
 *
 * @param int    $track_id
 * @param int    $release_id
 * @param int    $split_id
 * @param string $collaborator_tel
 * @param float  $amount_usd
 * @param string $description    Descrição para o artista ver quando reclamar
 * @param string $import_hash    Hash da importação (permite reverter se rejeitada)
 * @param bool   $pending_approval  Se true, só é creditado quando import é aprovada
 */
function rmbz_splits_record_holding( $track_id, $release_id, $split_id, $collaborator_tel, $amount_usd, $description = '', $import_hash = '', $pending_approval = true ) {
    if ( $amount_usd <= 0 ) return;

    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_splits_holdings';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") !== $t ) return;

    $tel = rmbz_splits_normalize_telemovel( $collaborator_tel );
    if ( $tel === '' ) return;

    // Idempotência: não duplicar se já existe um hold com a mesma descrição/split
    // no mesmo import_hash (para permitir reprocessamento seguro)
    $existing = $wpdb->get_var( $wpdb->prepare(
        "SELECT id FROM $t
         WHERE split_id = %d
           AND source_description = %s
           AND import_hash = %s
           AND reverted_at IS NULL
           AND claimed_at IS NULL
         LIMIT 1",
        (int) $split_id, $description, $import_hash
    ) );
    if ( $existing ) return;

    $wpdb->insert( $t, [
        'collaborator_telemovel' => $tel,
        'track_id'               => (int) $track_id,
        'release_id'             => (int) $release_id,
        'split_id'               => (int) $split_id,
        'amount_usd'             => round( (float) $amount_usd, 4 ),
        'pending_approval'       => $pending_approval ? 1 : 0,
        'import_hash'            => $import_hash ?: null,
        'source_description'     => mb_substr( (string) $description, 0, 200 ),
    ], [ '%s', '%d', '%d', '%d', '%f', '%d', '%s', '%s' ] );
}


/**
 * Marcar holdings de uma importação como aprovadas.
 * Chamado por `rmbz_handle_approve_import` depois de aprovar o ledger.
 */
function rmbz_splits_approve_holdings_by_hash( $import_hash ) {
    if ( empty($import_hash) ) return 0;
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_splits_holdings';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") !== $t ) return 0;

    $cols = $wpdb->get_col("SHOW COLUMNS FROM $t");
    if ( ! in_array('pending_approval', (array) $cols, true) ) return 0; // schema antigo

    return $wpdb->query( $wpdb->prepare(
        "UPDATE $t SET pending_approval = 0
         WHERE import_hash = %s AND pending_approval = 1 AND reverted_at IS NULL",
        $import_hash
    ) );
}


/**
 * Reverter holdings de uma importação rejeitada/anulada.
 * Marca `reverted_at` — não as apaga, fica no histórico.
 */
function rmbz_splits_revert_holdings_by_hash( $import_hash ) {
    if ( empty($import_hash) ) return 0;
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_splits_holdings';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") !== $t ) return 0;

    $cols = $wpdb->get_col("SHOW COLUMNS FROM $t");
    if ( ! in_array('reverted_at', (array) $cols, true) ) return 0; // schema antigo

    return $wpdb->query( $wpdb->prepare(
        "UPDATE $t SET reverted_at = %s
         WHERE import_hash = %s
           AND reverted_at IS NULL
           AND claimed_at IS NULL",
        current_time('mysql'), $import_hash
    ) );
}


/* ============================================================================
 * PAINEL ADMIN — lista completa + edição
 * ============================================================================ */

function rmbz_splits_admin_render_panel( $track ) {
    if ( empty($track->id) ) return;

    // Fazer sweep on-demand antes de mostrar (caso algo ficou pending)
    rmbz_splits_sweep_pending_for_release( (int) $track->release_id );

    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_track_splits';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") !== $t ) return;

    $splits = $wpdb->get_results( $wpdb->prepare(
        "SELECT * FROM $t WHERE track_id = %d ORDER BY percentage DESC, id ASC",
        $track->id
    ) );

    $total_pct = 0.0;
    foreach ( (array) $splits as $s ) $total_pct += (float) $s->percentage;

    $t_holdings = $wpdb->prefix . 'rmbz_splits_holdings';
    $holdings_total = [];
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t_holdings'") === $t_holdings ) {
        $hcols = $wpdb->get_col("SHOW COLUMNS FROM $t_holdings");
        $extra_where = '';
        if ( in_array('pending_approval', (array) $hcols, true) ) $extra_where .= ' AND pending_approval = 0';
        if ( in_array('reverted_at', (array) $hcols, true) )      $extra_where .= ' AND reverted_at IS NULL';

        $rows = $wpdb->get_results( $wpdb->prepare(
            "SELECT collaborator_telemovel, SUM(amount_usd) AS s
             FROM $t_holdings
             WHERE track_id = %d AND claimed_at IS NULL
             $extra_where
             GROUP BY collaborator_telemovel",
            $track->id
        ) );
        foreach ( $rows as $r ) $holdings_total[ $r->collaborator_telemovel ] = (float) $r->s;
    }

    $rid = (int) $track->release_id;
    $tid = (int) $track->id;

    if ( empty($splits) ) {
        ?>
        <div class="rmbz-splits-panel empty">
            <div class="rmbz-splits-head">
                <strong>🤝 Splits — nenhum configurado</strong>
                <small>O owner recebe 100% dos royalties.</small>
            </div>
            <button type="button" class="button button-small rmbz-splits-toggle-add" data-track="<?php echo $tid; ?>">+ Adicionar split</button>
            <?php rmbz_splits_admin_render_add_form( $track ); ?>
        </div>
        <?php rmbz_splits_admin_panel_styles(); ?>
        <?php
        return;
    }
    ?>
    <div class="rmbz-splits-panel">
        <div class="rmbz-splits-head">
            <strong>🤝 Splits — <?php echo count($splits); ?> colaborador(es), total <?php echo number_format($total_pct, 2); ?>%</strong>
            <?php if ( abs($total_pct - 100) > 0.01 ): ?>
                <span class="rmbz-splits-warn">⚠ Soma não é 100% — editor não pôde validar?</span>
            <?php endif; ?>
        </div>

        <table class="rmbz-splits-table">
            <thead>
                <tr>
                    <th>Nome</th>
                    <th style="width:140px;">Telemóvel</th>
                    <th style="width:70px;">Role</th>
                    <th style="width:60px;">%</th>
                    <th style="width:120px;">Estado</th>
                    <th style="width:100px;">Retido</th>
                    <th style="width:140px;">Acções</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ( $splits as $s ):
                    $tel = $s->collaborator_telemovel ?? '';
                    $held = $holdings_total[ $tel ] ?? 0;
                    $state_class = [
                        'accepted'      => 'ok',
                        'external_hold' => 'hold',
                        'pending'       => 'pending',
                        'rejected'      => 'rejected',
                    ][ $s->invite_status ] ?? 'pending';
                    $state_label = [
                        'accepted'      => '✓ Aceite',
                        'external_hold' => '⏳ Hold (externo)',
                        'pending'       => '◻ Pendente',
                        'rejected'      => '✗ Rejeitado',
                    ][ $s->invite_status ] ?? $s->invite_status;
                ?>
                <tr class="rmbz-split-row">
                    <td><strong><?php echo esc_html($s->collaborator_name); ?></strong></td>
                    <td>
                        <?php if ( $tel ): ?>
                            <code><?php echo esc_html($tel); ?></code>
                        <?php else: ?>
                            <em style="color:#aaa;">sem telemóvel</em>
                        <?php endif; ?>
                    </td>
                    <td><?php echo esc_html($s->role ?: 'feature'); ?></td>
                    <td><strong><?php echo number_format((float) $s->percentage, 2); ?>%</strong></td>
                    <td><span class="rmbz-state rmbz-state-<?php echo esc_attr($state_class); ?>"><?php echo esc_html($state_label); ?></span></td>
                    <td>
                        <?php if ( $held > 0 ): ?>
                            <strong style="color:#e67e22;">$<?php echo number_format($held, 2); ?></strong>
                        <?php else: ?>
                            <span style="color:#aaa;">—</span>
                        <?php endif; ?>
                    </td>
                    <td>
                        <button type="button" class="button button-small rmbz-splits-edit-btn"
                                data-split="<?php echo (int) $s->id; ?>"
                                data-name="<?php echo esc_attr($s->collaborator_name); ?>"
                                data-tel="<?php echo esc_attr($tel); ?>"
                                data-role="<?php echo esc_attr($s->role); ?>"
                                data-pct="<?php echo esc_attr($s->percentage); ?>"
                                data-status="<?php echo esc_attr($s->invite_status); ?>"
                                data-nonce="<?php echo esc_attr( wp_create_nonce('rmbz_split_admin_action') ); ?>">
                            ✏️
                        </button>
                        <?php if ( $s->invite_status !== 'accepted' ): ?>
                            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;" onsubmit="return confirm('Forçar ACCEPT deste split? Ficará sujeito a pagamento nos próximos royalties.');">
                                <?php wp_nonce_field('rmbz_splits_force_' . $s->id); ?>
                                <input type="hidden" name="action" value="rmbz_splits_force_state">
                                <input type="hidden" name="split_id" value="<?php echo (int) $s->id; ?>">
                                <input type="hidden" name="new_state" value="accepted">
                                <button type="submit" class="button button-small" title="Forçar accept">✓</button>
                            </form>
                        <?php endif; ?>
                        <?php if ( $s->invite_status !== 'rejected' ): ?>
                            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;" onsubmit="return confirm('REJEITAR este split? O colaborador não receberá nada.');">
                                <?php wp_nonce_field('rmbz_splits_force_' . $s->id); ?>
                                <input type="hidden" name="action" value="rmbz_splits_force_state">
                                <input type="hidden" name="split_id" value="<?php echo (int) $s->id; ?>">
                                <input type="hidden" name="new_state" value="rejected">
                                <button type="submit" class="button button-small" title="Rejeitar">✗</button>
                            </form>
                        <?php endif; ?>
                    </td>
                </tr>
                <?php endforeach; ?>
            </tbody>
        </table>

        <div class="rmbz-splits-actions">
            <button type="button" class="button button-small rmbz-splits-toggle-add" data-track="<?php echo $tid; ?>">+ Adicionar colaborador</button>
            <button type="button" class="button button-small rmbz-splits-view-audit" data-track="<?php echo $tid; ?>">🔍 Ver audit log</button>
        </div>

        <?php rmbz_splits_admin_render_add_form( $track ); ?>
        <?php rmbz_splits_admin_render_edit_form( $track ); ?>
        <?php rmbz_splits_admin_render_audit_modal( $track ); ?>
    </div>
    <?php
    rmbz_splits_admin_panel_styles();
}


/**
 * Form para adicionar novo split.
 */
function rmbz_splits_admin_render_add_form( $track ) {
    $tid = (int) $track->id;
    $rid = (int) $track->release_id;
    ?>
    <div id="rmbz-splits-add-<?php echo $tid; ?>" class="rmbz-splits-form-block" style="display:none;">
        <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
            <?php wp_nonce_field('rmbz_splits_add_' . $tid); ?>
            <input type="hidden" name="action" value="rmbz_splits_admin_add">
            <input type="hidden" name="track_id" value="<?php echo $tid; ?>">
            <input type="hidden" name="release_id" value="<?php echo $rid; ?>">
            <div class="rmbz-splits-form-grid">
                <div><label>Nome</label><input type="text" name="collab_name" required></div>
                <div><label>Telemóvel</label><input type="text" name="collab_tel" placeholder="+258..."></div>
                <div><label>Role</label>
                    <select name="collab_role">
                        <option value="feature">Feature</option>
                        <option value="producer">Produtor</option>
                        <option value="songwriter">Compositor</option>
                        <option value="mix">Mistura</option>
                        <option value="master">Mastering</option>
                        <option value="vocals">Vocalista</option>
                        <option value="remix">Remix</option>
                    </select>
                </div>
                <div><label>%</label><input type="number" name="collab_pct" step="0.01" min="0.01" max="100" required></div>
            </div>
            <p class="rmbz-splits-form-hint">
                ⚠️ A soma de todos os splits deve manter-se em 100%. Se a soma actual + este novo exceder, terás de editar outros primeiro.
            </p>
            <button type="submit" class="button button-primary button-small">💾 Adicionar split</button>
            <button type="button" class="button button-small" onclick="document.getElementById('rmbz-splits-add-<?php echo $tid; ?>').style.display='none';">Cancelar</button>
        </form>
    </div>
    <?php
}

/**
 * Form genérico de edição — populado por JS via data-* do botão ✏️.
 */
function rmbz_splits_admin_render_edit_form( $track ) {
    $tid = (int) $track->id;
    ?>
    <div id="rmbz-splits-edit-<?php echo $tid; ?>" class="rmbz-splits-form-block" style="display:none;">
        <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
            <input type="hidden" name="action" value="rmbz_splits_admin_edit">
            <input type="hidden" name="split_id" id="rmbz-splits-edit-id-<?php echo $tid; ?>" value="">
            <input type="hidden" name="_wpnonce" id="rmbz-splits-edit-nonce-<?php echo $tid; ?>" value="">
            <input type="hidden" name="track_id" value="<?php echo $tid; ?>">
            <h4 style="margin:0 0 10px 0;">Editar split</h4>
            <div class="rmbz-splits-form-grid">
                <div><label>Nome</label><input type="text" name="collab_name" id="rmbz-splits-edit-name-<?php echo $tid; ?>" required></div>
                <div><label>Telemóvel</label><input type="text" name="collab_tel" id="rmbz-splits-edit-tel-<?php echo $tid; ?>" placeholder="+258..."></div>
                <div><label>Role</label>
                    <select name="collab_role" id="rmbz-splits-edit-role-<?php echo $tid; ?>">
                        <option value="feature">Feature</option>
                        <option value="producer">Produtor</option>
                        <option value="songwriter">Compositor</option>
                        <option value="mix">Mistura</option>
                        <option value="master">Mastering</option>
                        <option value="vocals">Vocalista</option>
                        <option value="remix">Remix</option>
                    </select>
                </div>
                <div><label>%</label><input type="number" name="collab_pct" id="rmbz-splits-edit-pct-<?php echo $tid; ?>" step="0.01" min="0.01" max="100" required></div>
            </div>
            <button type="submit" class="button button-primary button-small">💾 Salvar</button>
            <button type="button" class="button button-small" onclick="document.getElementById('rmbz-splits-edit-<?php echo $tid; ?>').style.display='none';">Cancelar</button>
            <button type="submit" name="delete" value="1" class="button button-small" style="color:#c0392b; float:right;"
                    onclick="return confirm('REMOVER este split? A percentagem vai voltar para o owner.');">🗑 Remover</button>
        </form>
    </div>
    <?php
}

function rmbz_splits_admin_render_audit_modal( $track ) {
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_splits_audit';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") !== $t ) return;

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT * FROM $t WHERE track_id = %d ORDER BY created_at DESC LIMIT 50",
        $track->id
    ) );
    $tid = (int) $track->id;
    ?>
    <div id="rmbz-splits-audit-<?php echo $tid; ?>" class="rmbz-splits-form-block" style="display:none;">
        <h4 style="margin:0 0 10px 0;">🔍 Audit log — últimas 50 acções</h4>
        <?php if ( empty($rows) ): ?>
            <p style="color:#888;">Sem registos.</p>
        <?php else: ?>
            <div class="rmbz-splits-audit-list">
                <?php foreach ( $rows as $r ): ?>
                    <div class="rmbz-audit-row">
                        <div><code><?php echo esc_html($r->action); ?></code>
                            por <strong><?php echo esc_html($r->actor); ?></strong>
                            <small style="color:#888;"><?php echo esc_html( date('d/m H:i', strtotime($r->created_at)) ); ?></small>
                        </div>
                        <?php if ( ! empty($r->note) ): ?>
                            <small style="color:#555;"><?php echo esc_html($r->note); ?></small>
                        <?php endif; ?>
                    </div>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>
        <button type="button" class="button button-small" onclick="document.getElementById('rmbz-splits-audit-<?php echo $tid; ?>').style.display='none';">Fechar</button>
    </div>
    <?php
}


function rmbz_splits_admin_panel_styles() {
    static $rendered = false;
    if ( $rendered ) return;
    $rendered = true;
    ?>
    <style>
    .rmbz-splits-panel { background:#fcfcfc; border:1px solid #dee2e6; border-radius:4px; padding:10px; margin-top:10px; font-size:12px; }
    .rmbz-splits-panel.empty { background:#f9f9f9; text-align:center; padding:12px; }
    .rmbz-splits-head { margin-bottom:8px; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
    .rmbz-splits-head strong { font-size:12px; }
    .rmbz-splits-head small { color:#888; }
    .rmbz-splits-warn { color:#c0392b; font-weight:600; }
    .rmbz-splits-table { width:100%; border-collapse:collapse; font-size:11px; }
    .rmbz-splits-table th, .rmbz-splits-table td { text-align:left; padding:6px 8px; border-bottom:1px solid #eee; }
    .rmbz-splits-table th { background:#f6f7f7; color:#666; text-transform:uppercase; font-size:10px; letter-spacing:0.3px; }
    .rmbz-split-row:hover { background:#fafafa; }
    .rmbz-state { display:inline-block; padding:1px 6px; border-radius:3px; font-size:10px; font-weight:600; }
    .rmbz-state-ok { background:rgba(46,204,113,0.15); color:#155724; }
    .rmbz-state-hold { background:rgba(230,126,34,0.15); color:#a0571c; }
    .rmbz-state-pending { background:rgba(149,165,166,0.15); color:#555; }
    .rmbz-state-rejected { background:rgba(231,76,60,0.15); color:#721c24; }
    .rmbz-splits-actions { margin-top:8px; display:flex; gap:6px; }
    .rmbz-splits-form-block { margin-top:10px; padding:10px; background:#fffbea; border-left:3px solid #f0b90b; border-radius:3px; }
    .rmbz-splits-form-grid { display:grid; grid-template-columns:1fr 140px 120px 80px; gap:8px; margin-bottom:8px; }
    .rmbz-splits-form-grid label { display:block; font-size:10px; color:#666; text-transform:uppercase; margin-bottom:3px; }
    .rmbz-splits-form-grid input, .rmbz-splits-form-grid select { width:100%; padding:4px; font-size:12px; }
    .rmbz-splits-form-hint { color:#856404; font-size:11px; margin:4px 0; }
    .rmbz-splits-audit-list { max-height:260px; overflow-y:auto; }
    .rmbz-audit-row { padding:6px 0; border-bottom:1px solid #eee; }
    @media (max-width: 900px) {
        .rmbz-splits-form-grid { grid-template-columns:1fr 1fr; }
    }
    </style>
    <script>
    (function() {
        // Toggle add form
        document.querySelectorAll('.rmbz-splits-toggle-add').forEach(function(btn) {
            btn.addEventListener('click', function() {
                var tid = btn.getAttribute('data-track');
                var f = document.getElementById('rmbz-splits-add-' + tid);
                if ( f ) f.style.display = (f.style.display === 'none' ? 'block' : 'none');
            });
        });
        // Toggle audit
        document.querySelectorAll('.rmbz-splits-view-audit').forEach(function(btn) {
            btn.addEventListener('click', function() {
                var tid = btn.getAttribute('data-track');
                var f = document.getElementById('rmbz-splits-audit-' + tid);
                if ( f ) f.style.display = (f.style.display === 'none' ? 'block' : 'none');
            });
        });
        // Populate edit form a partir de data-*
        document.querySelectorAll('.rmbz-splits-edit-btn').forEach(function(btn) {
            btn.addEventListener('click', function() {
                var row = btn.closest('.rmbz-splits-panel');
                if ( ! row ) return;
                // Procurar IDs dinâmicos no mesmo panel
                var form = row.querySelector('[id^="rmbz-splits-edit-"]');
                if ( ! form ) return;
                // Extrair o tid do id do form
                var m = form.id.match(/rmbz-splits-edit-(\d+)$/);
                if ( ! m ) return;
                var tid = m[1];

                document.getElementById('rmbz-splits-edit-id-' + tid).value = btn.getAttribute('data-split');
                document.getElementById('rmbz-splits-edit-name-' + tid).value = btn.getAttribute('data-name');
                document.getElementById('rmbz-splits-edit-tel-' + tid).value = btn.getAttribute('data-tel');
                document.getElementById('rmbz-splits-edit-role-' + tid).value = btn.getAttribute('data-role') || 'feature';
                document.getElementById('rmbz-splits-edit-pct-' + tid).value = btn.getAttribute('data-pct');

                // Gerar nonce via fetch — simplificação: usar o nonce do form add (mesma autoridade)
                // Para evitar fetch, o handler aceita um nonce unificado 'rmbz_splits_edit_' + split_id
                // que o admin cria no servidor. Como o form não sabe o split_id à partida, criamos um
                // nonce genérico por track e validamos também current_user_can no handler.
                // (Audit log + manage_options dá-nos segurança suficiente.)
                document.getElementById('rmbz-splits-edit-nonce-' + tid).value = btn.getAttribute('data-nonce') || '';
                form.style.display = 'block';
                form.scrollIntoView({behavior:'smooth', block:'center'});
            });
        });
    })();
    </script>
    <?php
}


/* ============================================================================
 * HANDLERS ADMIN
 * ============================================================================ */

/**
 * Forçar estado (accepted/rejected) — botão ✓/✗ inline.
 */
function rmbz_splits_force_state_handler() {
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');
    $split_id = (int) ($_POST['split_id'] ?? 0);
    if ( ! $split_id ) wp_die('ID inválido.');
    check_admin_referer('rmbz_splits_force_' . $split_id);

    $new_state = sanitize_key( $_POST['new_state'] ?? '' );
    if ( ! in_array($new_state, ['accepted','rejected'], true) ) wp_die('Estado inválido.');

    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_track_splits';
    $row = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $t WHERE id = %d", $split_id) );
    if ( ! $row ) wp_die('Split não encontrado.');

    $before = [ 'status' => $row->invite_status ];
    $wpdb->update( $t,
        [
            'invite_status' => $new_state,
            'accepted_at'   => $new_state === 'accepted' ? current_time('mysql') : null,
        ],
        [ 'id' => $split_id ],
        [ '%s', '%s' ], [ '%d' ]
    );

    rmbz_splits_audit(
        'admin_force_' . $new_state,
        $row->track_id,
        $row->release_id,
        $split_id,
        $before,
        [ 'status' => $new_state ],
        'Admin forçou estado ' . $new_state
    );

    if ( $new_state === 'rejected' ) {
        rmbz_splits_notify_collaborator( $row, 'rejected' );
    }

    wp_safe_redirect( wp_get_referer() ?: admin_url() );
    exit;
}
add_action('admin_post_rmbz_splits_force_state', 'rmbz_splits_force_state_handler');


/**
 * Adicionar novo split pelo admin.
 */
function rmbz_splits_admin_add_handler() {
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');
    $track_id = (int) ($_POST['track_id'] ?? 0);
    $release_id = (int) ($_POST['release_id'] ?? 0);
    if ( ! $track_id || ! $release_id ) wp_die('IDs inválidos.');
    check_admin_referer('rmbz_splits_add_' . $track_id);

    $name = sanitize_text_field( $_POST['collab_name'] ?? '' );
    $tel  = rmbz_splits_normalize_telemovel( $_POST['collab_tel'] ?? '' );
    $role = sanitize_key( $_POST['collab_role'] ?? 'feature' );
    $pct  = round( (float) ($_POST['collab_pct'] ?? 0), 2 );

    if ( $name === '' || $pct <= 0 || $pct > 100 ) {
        wp_safe_redirect( add_query_arg('msg', 'split_invalid', wp_get_referer() ?: admin_url()) );
        exit;
    }

    // Verificar se a soma não passa de 100
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_track_splits';
    $existing_sum = (float) $wpdb->get_var( $wpdb->prepare(
        "SELECT COALESCE(SUM(percentage),0) FROM $t WHERE track_id = %d AND invite_status != 'rejected'",
        $track_id
    ) );
    if ( $existing_sum + $pct > 100.01 ) {
        wp_safe_redirect( add_query_arg('msg', 'split_overflow', wp_get_referer() ?: admin_url()) );
        exit;
    }

    // Estado inicial consoante telemóvel
    $initial_state = 'pending';
    $accepted_at = null;
    if ( $tel !== '' ) {
        if ( rmbz_splits_is_registered_telemovel($tel) ) {
            $initial_state = 'accepted';
            $accepted_at = current_time('mysql');
        } else {
            $initial_state = 'external_hold';
        }
    }

    $wpdb->insert( $t, [
        'track_id'               => $track_id,
        'release_id'             => $release_id,
        'collaborator_name'      => $name,
        'collaborator_telemovel' => $tel,
        'role'                   => $role,
        'percentage'             => $pct,
        'invite_status'          => $initial_state,
        'accepted_at'            => $accepted_at,
    ], [ '%d','%d','%s','%s','%s','%f','%s','%s' ] );

    $new_id = (int) $wpdb->insert_id;

    rmbz_splits_audit(
        'admin_add',
        $track_id,
        $release_id,
        $new_id,
        null,
        [ 'name' => $name, 'tel' => $tel, 'pct' => $pct, 'role' => $role, 'status' => $initial_state ],
        'Split adicionado pelo admin'
    );

    // Notificar se interno
    if ( $initial_state === 'accepted' ) {
        $row = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $t WHERE id = %d", $new_id) );
        if ( $row ) rmbz_splits_notify_collaborator( $row, 'added' );
    }

    wp_safe_redirect( add_query_arg('msg', 'split_added', wp_get_referer() ?: admin_url()) );
    exit;
}
add_action('admin_post_rmbz_splits_admin_add', 'rmbz_splits_admin_add_handler');


/**
 * Editar / remover split pelo admin.
 *
 * [SEC-SP1] Hardening 2026-04: handler aceita agora UM nonce verdadeiro
 * (rmbz_split_admin_action) em vez de só verificar o referer. O referer pode
 * ser falsificado; o nonce não.
 */
function rmbz_splits_admin_edit_handler() {
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');
    if ( ! isset($_POST['_wpnonce']) || ! wp_verify_nonce($_POST['_wpnonce'], 'rmbz_split_admin_action') ) {
        wp_die('Falha de segurança (nonce).');
    }
    $split_id = (int) ($_POST['split_id'] ?? 0);
    $track_id = (int) ($_POST['track_id'] ?? 0);
    if ( ! $split_id || ! $track_id ) wp_die('IDs inválidos.');

    // Defense-in-depth adicional: o referer continua a ser validado.
    $referer = wp_get_referer();
    if ( ! $referer || strpos($referer, admin_url()) !== 0 ) {
        wp_die('Referer inválido.');
    }

    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_track_splits';
    $row = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $t WHERE id = %d AND track_id = %d", $split_id, $track_id) );
    if ( ! $row ) wp_die('Split não encontrado.');

    $before = [
        'name' => $row->collaborator_name,
        'tel'  => $row->collaborator_telemovel,
        'role' => $row->role,
        'pct'  => $row->percentage,
        'status'=> $row->invite_status,
    ];

    // Remover
    if ( ! empty($_POST['delete']) ) {
        $wpdb->delete( $t, [ 'id' => $split_id ], [ '%d' ] );
        rmbz_splits_audit('admin_delete', $row->track_id, $row->release_id, $split_id, $before, null, 'Split removido pelo admin');
        wp_safe_redirect( add_query_arg('msg', 'split_deleted', $referer) );
        exit;
    }

    // Editar
    $name = sanitize_text_field( $_POST['collab_name'] ?? '' );
    $tel  = rmbz_splits_normalize_telemovel( $_POST['collab_tel'] ?? '' );
    $role = sanitize_key( $_POST['collab_role'] ?? 'feature' );
    $pct  = round( (float) ($_POST['collab_pct'] ?? 0), 2 );

    if ( $name === '' || $pct <= 0 || $pct > 100 ) {
        wp_safe_redirect( add_query_arg('msg', 'split_invalid', $referer) );
        exit;
    }

    // Soma não pode passar de 100% (excluindo este split que estamos a editar)
    $other_sum = (float) $wpdb->get_var( $wpdb->prepare(
        "SELECT COALESCE(SUM(percentage),0) FROM $t WHERE track_id = %d AND id != %d AND invite_status != 'rejected'",
        $track_id, $split_id
    ) );
    if ( $other_sum + $pct > 100.01 ) {
        wp_safe_redirect( add_query_arg('msg', 'split_overflow', $referer) );
        exit;
    }

    // Re-calcular estado se telemóvel mudou
    $new_status = $row->invite_status;
    $new_accepted_at = $row->accepted_at;
    if ( $tel !== $row->collaborator_telemovel ) {
        if ( $tel !== '' && rmbz_splits_is_registered_telemovel($tel) ) {
            $new_status = 'accepted';
            $new_accepted_at = current_time('mysql');
        } elseif ( $tel !== '' ) {
            $new_status = 'external_hold';
            $new_accepted_at = null;
        } else {
            $new_status = 'pending';
            $new_accepted_at = null;
        }
    }

    $wpdb->update( $t,
        [
            'collaborator_name'      => $name,
            'collaborator_telemovel' => $tel,
            'role'                   => $role,
            'percentage'             => $pct,
            'invite_status'          => $new_status,
            'accepted_at'            => $new_accepted_at,
        ],
        [ 'id' => $split_id ],
        [ '%s','%s','%s','%f','%s','%s' ], [ '%d' ]
    );

    $after = [
        'name' => $name, 'tel' => $tel, 'role' => $role, 'pct' => $pct, 'status' => $new_status,
    ];
    rmbz_splits_audit('admin_edit', $row->track_id, $row->release_id, $split_id, $before, $after, 'Split editado pelo admin');

    wp_safe_redirect( add_query_arg('msg', 'split_saved', $referer) );
    exit;
}
add_action('admin_post_rmbz_splits_admin_edit', 'rmbz_splits_admin_edit_handler');


/* ============================================================================
 * HOOK — disparado pelo track-editor após guardar splits
 * ----------------------------------------------------------------------------
 * O handler em distribution-track-editor.php precisa fazer
 * `do_action('rmbz_track_splits_saved', $track_id)` depois do insert-loop.
 * Como a cadeia é PATCHED, fazemos fallback: este filter é chamado por um
 * wrapper noutro ficheiro OU pelo sweep oportunístico no render admin.
 * ============================================================================ */
