<?php
/**
 * AURA FARMING — Etapa 10
 * Duelo direto, torneios eliminatórios e eventos com ranking próprio.
 */

require_once __DIR__ . '/diamond_functions.php';

function afCodigoCompeticao(PDO $pdo, string $tabela, int $tamanho = 8): string
{
    $alfabeto = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    do {
        $codigo = '';
        for ($i=0; $i<$tamanho; $i++) $codigo .= $alfabeto[random_int(0, strlen($alfabeto)-1)];
        $st = $pdo->prepare("SELECT id FROM {$tabela} WHERE codigo=? LIMIT 1");
        $st->execute([$codigo]);
    } while ($st->fetchColumn());
    return $codigo;
}

function afCodigoLimpo(string $codigo): string
{
    return strtoupper(preg_replace('/[^A-Z0-9]/i','',$codigo));
}

/**
 * Preços oficiais para ativação de torneios.
 * O duelo direto VS 1x1 permanece gratuito e não passa por esta tabela.
 */
function afCompeticaoCustosDiamantes(): array
{
    return [
        4 => 20,
        8 => 30,
        16 => 45,
        32 => 80,
    ];
}

function afCompeticaoCustoDiamantes(int $maxParticipantes): int
{
    $custos = afCompeticaoCustosDiamantes();
    if (!isset($custos[$maxParticipantes])) {
        throw new RuntimeException('Escolha um torneio com 4, 8, 16 ou 32 participantes.');
    }
    return (int)$custos[$maxParticipantes];
}

function afBatalhaEvento(PDO $pdo, int $batalhaId, ?int $usuarioId, string $tipo, array $dados=[]): void
{
    $pdo->prepare('INSERT INTO af_batalha_eventos(batalha_id,usuario_id,tipo,dados,criada_em) VALUES (?,?,?,?,NOW())')
        ->execute([$batalhaId,$usuarioId,$tipo,$dados ? json_encode($dados,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) : null]);
}

function afBuscarBatalha(PDO $pdo, int $batalhaId, bool $forUpdate=false): ?array
{
    $sql = "SELECT b.*,c.nome competicao_nome,c.tipo competicao_tipo,c.status competicao_status
            FROM af_batalhas b LEFT JOIN af_competicoes c ON c.id=b.competicao_id WHERE b.id=? LIMIT 1";
    if ($forUpdate) $sql .= ' FOR UPDATE';
    $st=$pdo->prepare($sql);$st->execute([$batalhaId]);
    return $st->fetch() ?: null;
}

function afJogadoresBatalha(PDO $pdo, int $batalhaId): array
{
    $st=$pdo->prepare("SELECT bj.*,u.nome,u.username,u.aura_id,u.foto_perfil,u.foto_externa,u.titulo,u.nivel,
                       COALESCE(NULLIF(p.pontuacao_validada,0),CASE WHEN p.antifraude_status='aprovada' THEN p.pontuacao_total ELSE 0 END,bj.pontuacao) pontuacao_live,
                       COALESCE(p.aura_gerada,bj.aura) aura_live,
                       COALESCE((p.likes_recebidos*.25+p.superlikes_recebidos),bj.pontuacao_social) social_live,
                       COALESCE(p.combo_maximo,bj.combo_maximo) combo_live
                       FROM af_batalha_jogadores bj JOIN af_usuarios u ON u.id=bj.usuario_id
                       LEFT JOIN af_partidas p ON p.id=bj.partida_id
                       WHERE bj.batalha_id=? ORDER BY bj.slot ASC");
    $st->execute([$batalhaId]);
    return $st->fetchAll() ?: [];
}

function afBatalhaPublica(PDO $pdo, array $batalha, int $viewerId=0): array
{
    $jogadores = array_map(static function(array $j) use ($viewerId):array {
        return [
            'usuario_id'=>(int)$j['usuario_id'],'slot'=>(int)$j['slot'],'nome'=>$j['nome'],'username'=>$j['username'],
            'aura_id'=>$j['aura_id'],'avatar'=>urlAvatar($j['foto_perfil']??null,$j['foto_externa']??null),'titulo'=>$j['titulo'],
            'status'=>$j['status'],'pontuacao'=>(float)($j['pontuacao_live']??$j['pontuacao']),'aura'=>(float)($j['aura_live']??$j['aura']),
            'pontuacao_social'=>(float)($j['social_live']??$j['pontuacao_social']),'combo_maximo'=>(int)($j['combo_live']??$j['combo_maximo']),
            'partida_id'=>(int)($j['partida_id']??0),'eu'=>$viewerId>0 && (int)$j['usuario_id']===$viewerId,
        ];
    },afJogadoresBatalha($pdo,(int)$batalha['id']));
    $restante = 0;
    if (!empty($batalha['expira_em'])) $restante=max(0,strtotime((string)$batalha['expira_em'])-time());
    return [
        'id'=>(int)$batalha['id'],'codigo'=>$batalha['codigo'],'tipo'=>$batalha['tipo'],'status'=>$batalha['status'],
        'competicao_id'=>(int)($batalha['competicao_id']??0),'competicao_nome'=>$batalha['competicao_nome']??null,
        'rodada'=>(int)$batalha['rodada'],'chave_ordem'=>(int)$batalha['chave_ordem'],'duracao'=>(int)$batalha['duracao_segundos'],
        'inicio_programado_em'=>$batalha['inicio_programado_em'],'iniciada_em'=>$batalha['iniciada_em'],'expira_em'=>$batalha['expira_em'],
        'tempo_restante'=>$restante,'vencedor_id'=>(int)($batalha['vencedor_id']??0),'desempate'=>(bool)$batalha['desempate'],
        'jogadores'=>$jogadores,'sou_participante'=>count(array_filter($jogadores,static fn($j)=>$j['eu']))>0,
    ];
}

function afListarBatalhasUsuario(PDO $pdo, int $usuarioId, int $limite=30): array
{
    $limite=max(1,min(100,$limite));
    $st=$pdo->prepare("SELECT DISTINCT b.*,c.nome competicao_nome,c.tipo competicao_tipo,c.status competicao_status
                       FROM af_batalhas b JOIN af_batalha_jogadores bj ON bj.batalha_id=b.id
                       LEFT JOIN af_competicoes c ON c.id=b.competicao_id
                       WHERE bj.usuario_id=? ORDER BY b.atualizada_em DESC LIMIT {$limite}");
    $st->execute([$usuarioId]);
    return array_map(fn($b)=>afBatalhaPublica($pdo,$b,$usuarioId),$st->fetchAll()?:[]);
}


function afAssertBattleSafety(PDO $pdo, int $userId, int $opponentId): void
{
    if(!function_exists('afMinorPolicyDecision'))return;
    $decision=afMinorPolicyDecision($pdo,$userId,'battle',['opponent_user_id'=>$opponentId],true);
    if(empty($decision['allowed']))throw new RuntimeException((string)$decision['message']);
}

function afBattleOpponentId(PDO $pdo,int $battleId,int $userId): int
{
    $st=$pdo->prepare('SELECT usuario_id FROM af_batalha_jogadores WHERE batalha_id=? AND usuario_id<>? ORDER BY slot LIMIT 1');
    $st->execute([$battleId,$userId]);return (int)($st->fetchColumn()?:0);
}

function afCriarDuelo(PDO $pdo, int $criadorId, string $auraId): array
{
    $auraId=strtoupper(trim($auraId));
    if (!preg_match('/^AURA-[0-9]{4}$/',$auraId)) throw new RuntimeException('Informe um ID AURA válido.');
    $st=$pdo->prepare('SELECT id,nome FROM af_usuarios WHERE aura_id=? AND status=1 LIMIT 1');$st->execute([$auraId]);
    $alvo=$st->fetch();
    if (!$alvo) throw new RuntimeException('Jogador não encontrado.');
    if ((int)$alvo['id']===$criadorId) throw new RuntimeException('Você não pode desafiar a si mesmo.');
    afAssertBattleSafety($pdo,$criadorId,(int)$alvo['id']);
    afAssertBattleSafety($pdo,(int)$alvo['id'],$criadorId);

    $st=$pdo->prepare("SELECT b.id FROM af_batalhas b JOIN af_batalha_jogadores a ON a.batalha_id=b.id AND a.usuario_id=?
                       JOIN af_batalha_jogadores z ON z.batalha_id=b.id AND z.usuario_id=?
                       WHERE b.tipo='duelo' AND b.status IN ('convite','aguardando_prontidao','preparando','em_andamento') LIMIT 1");
    $st->execute([$criadorId,(int)$alvo['id']]);
    if ($id=(int)($st->fetchColumn()?:0)) return afBuscarBatalha($pdo,$id) ?: [];

    $pdo->beginTransaction();
    try {
        $codigo=afCodigoCompeticao($pdo,'af_batalhas',10);
        $pdo->prepare("INSERT INTO af_batalhas(criador_id,codigo,tipo,status,duracao_segundos,criada_em,atualizada_em)
                       VALUES (?,?,'duelo','convite',180,NOW(),NOW())")->execute([$criadorId,$codigo]);
        $id=(int)$pdo->lastInsertId();
        $pdo->prepare("INSERT INTO af_batalha_jogadores(batalha_id,usuario_id,slot,status,aceito_em,criada_em) VALUES (?,?,1,'aceito',NOW(),NOW()),(?,?,2,'convidado',NULL,NOW())")
            ->execute([$id,$criadorId,$id,(int)$alvo['id']]);
        afBatalhaEvento($pdo,$id,$criadorId,'convite_criado',['alvo_id'=>(int)$alvo['id']]);
        criarNotificacao($pdo,(int)$alvo['id'],'battle_invite','Novo desafio de Aura Battle!', 'Você recebeu um duelo protegido. Entre na Arena para aceitar.',['batalha_id'=>$id]);
        if(function_exists('afGuardianDashboardCreateAlert')){
            afGuardianDashboardCreateAlert($pdo,$criadorId,'battle_start','info','Battle iniciada','O dependente enviou um convite para um contato aprovado.',['battle_id'=>$id,'opponent_id'=>(int)$alvo['id']],'battle',$id);
            afGuardianDashboardCreateAlert($pdo,(int)$alvo['id'],'battle_invite','info','Convite de Battle','O dependente recebeu um convite de um contato aprovado.',['battle_id'=>$id,'opponent_id'=>$criadorId],'battle',$id);
        }
        $pdo->commit();
        return afBuscarBatalha($pdo,$id) ?: [];
    } catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
}

function afResponderDuelo(PDO $pdo, int $batalhaId, int $usuarioId, bool $aceitar): array
{
    $pdo->beginTransaction();
    try {
        $b=afBuscarBatalha($pdo,$batalhaId,true);
        if(!$b) throw new RuntimeException('Batalha não encontrada.');
        $st=$pdo->prepare('SELECT * FROM af_batalha_jogadores WHERE batalha_id=? AND usuario_id=? LIMIT 1 FOR UPDATE');$st->execute([$batalhaId,$usuarioId]);$j=$st->fetch();
        if(!$j) throw new RuntimeException('Você não participa desta batalha.');
        if($j['status']!=='convidado') throw new RuntimeException('Este convite já foi respondido.');
        if(!in_array($b['status'],['convite','aguardando_prontidao'],true)) throw new RuntimeException('Este convite não pode mais ser alterado.');
        if($aceitar){
            $opponentId=afBattleOpponentId($pdo,$batalhaId,$usuarioId);
            if($opponentId<=0)throw new RuntimeException('Adversário não encontrado.');
            afAssertBattleSafety($pdo,$usuarioId,$opponentId);
            afAssertBattleSafety($pdo,$opponentId,$usuarioId);
            $pdo->prepare("UPDATE af_batalha_jogadores SET status='aceito',aceito_em=NOW() WHERE id=?")->execute([(int)$j['id']]);
            $pdo->prepare("UPDATE af_batalhas SET status='aguardando_prontidao',atualizada_em=NOW() WHERE id=?")->execute([$batalhaId]);
            afBatalhaEvento($pdo,$batalhaId,$usuarioId,'convite_aceito');
        }else{
            $pdo->prepare("UPDATE af_batalha_jogadores SET status='recusado' WHERE id=?")->execute([(int)$j['id']]);
            $pdo->prepare("UPDATE af_batalhas SET status='cancelada',finalizada_em=NOW(),atualizada_em=NOW() WHERE id=?")->execute([$batalhaId]);
            afBatalhaEvento($pdo,$batalhaId,$usuarioId,'convite_recusado');
        }
        $pdo->commit();
        return afBuscarBatalha($pdo,$batalhaId) ?: [];
    }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
}

function afValidarBatalhaParaPartida(PDO $pdo, int $batalhaId, int $usuarioId): array
{
    $b=afBuscarBatalha($pdo,$batalhaId);
    if(!$b) throw new RuntimeException('Batalha não encontrada.');
    if(!in_array($b['status'],['aguardando_prontidao','preparando','em_andamento'],true)) throw new RuntimeException('A batalha não está pronta para jogar.');
    $st=$pdo->prepare('SELECT * FROM af_batalha_jogadores WHERE batalha_id=? AND usuario_id=? LIMIT 1');$st->execute([$batalhaId,$usuarioId]);$j=$st->fetch();
    if(!$j || !in_array($j['status'],['aceito','camera_pronta','jogando'],true)) throw new RuntimeException('Você não está confirmado nesta batalha.');
    $opponentId=afBattleOpponentId($pdo,$batalhaId,$usuarioId);
    if($opponentId<=0)throw new RuntimeException('Adversário não encontrado.');
    afAssertBattleSafety($pdo,$usuarioId,$opponentId);
    afAssertBattleSafety($pdo,$opponentId,$usuarioId);
    return [$b,$j];
}

function afMarcarCameraProntaBatalha(PDO $pdo, int $batalhaId, int $usuarioId, int $partidaId): array
{
    $opponentId=afBattleOpponentId($pdo,$batalhaId,$usuarioId);
    if($opponentId<=0)throw new RuntimeException('Adversário não encontrado.');
    afAssertBattleSafety($pdo,$usuarioId,$opponentId);
    afAssertBattleSafety($pdo,$opponentId,$usuarioId);
    $pdo->beginTransaction();
    try {
        $b=afBuscarBatalha($pdo,$batalhaId,true);
        if(!$b) throw new RuntimeException('Batalha não encontrada.');
        $st=$pdo->prepare('SELECT * FROM af_batalha_jogadores WHERE batalha_id=? AND usuario_id=? LIMIT 1 FOR UPDATE');$st->execute([$batalhaId,$usuarioId]);$j=$st->fetch();
        if(!$j) throw new RuntimeException('Você não participa desta batalha.');
        if(!in_array($b['status'],['aguardando_prontidao','preparando','em_andamento'],true)) throw new RuntimeException('A batalha não aceita mais jogadores.');
        $pdo->prepare("UPDATE af_batalha_jogadores SET status=IF(status='finalizado','finalizado','camera_pronta'),partida_id=?,camera_pronta_em=COALESCE(camera_pronta_em,NOW()) WHERE id=?")
            ->execute([$partidaId,(int)$j['id']]);
        $st=$pdo->prepare("SELECT COUNT(*) total,SUM(status IN ('camera_pronta','jogando','finalizado')) prontos FROM af_batalha_jogadores WHERE batalha_id=? AND status<>'recusado'");
        $st->execute([$batalhaId]);$c=$st->fetch();
        if((int)$c['total']>=2 && (int)$c['prontos']===(int)$c['total'] && $b['status']!=='em_andamento'){
            $pdo->prepare("UPDATE af_batalhas SET status='em_andamento',inicio_programado_em=DATE_ADD(NOW(),INTERVAL 5 SECOND),iniciada_em=DATE_ADD(NOW(),INTERVAL 5 SECOND),expira_em=DATE_ADD(NOW(),INTERVAL 185 SECOND),atualizada_em=NOW() WHERE id=?")
                ->execute([$batalhaId]);
            afBatalhaEvento($pdo,$batalhaId,$usuarioId,'todos_prontos');
        }else{
            $pdo->prepare("UPDATE af_batalhas SET status='preparando',atualizada_em=NOW() WHERE id=? AND status<>'em_andamento'")->execute([$batalhaId]);
            afBatalhaEvento($pdo,$batalhaId,$usuarioId,'camera_pronta',['partida_id'=>$partidaId]);
        }
        $pdo->commit();
        $out=afBuscarBatalha($pdo,$batalhaId) ?: [];
        return afBatalhaPublica($pdo,$out,$usuarioId);
    }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
}

function afResolverBatalha(PDO $pdo, int $batalhaId): array
{
    $pdo->beginTransaction();
    try{
        $b=afBuscarBatalha($pdo,$batalhaId,true);
        if(!$b) throw new RuntimeException('Batalha não encontrada.');
        if(in_array($b['status'],['finalizada','cancelada','expirada'],true)){$pdo->commit();return $b;}
        $jogadores=afJogadoresBatalha($pdo,$batalhaId);
        $finalizados=array_values(array_filter($jogadores,static fn($j)=>in_array($j['status'],['finalizado','ausente'],true)));
        $venceuPorPrazo=!empty($b['expira_em']) && strtotime((string)$b['expira_em'])+20<=time();
        if(count($finalizados)<count($jogadores) && !$venceuPorPrazo){$pdo->commit();return $b;}
        foreach($jogadores as $j){if($j['status']!=='finalizado')$pdo->prepare("UPDATE af_batalha_jogadores SET status='ausente',finalizado_em=NOW() WHERE id=?")->execute([(int)$j['id']]);}
        $jogadores=afJogadoresBatalha($pdo,$batalhaId);
        usort($jogadores,static function($a,$z){
            foreach(['pontuacao','aura','pontuacao_social','combo_maximo'] as $k){$cmp=(float)$z[$k]<=>(float)$a[$k];if($cmp!==0)return $cmp;}
            return (int)$a['usuario_id']<=>(int)$z['usuario_id'];
        });
        $vencedor=$jogadores[0]??null;
        $desempate=false;
        if(isset($jogadores[1]) && (float)$jogadores[0]['pontuacao']===(float)$jogadores[1]['pontuacao'])$desempate=true;
        $todosSemResultado = count(array_filter($jogadores,static fn($j)=>(float)$j['pontuacao']>0 || $j['status']==='finalizado'))===0;
        $vencedorId=(!$todosSemResultado && $vencedor)?(int)$vencedor['usuario_id']:0;
        $statusFinal=$todosSemResultado?'expirada':'finalizada';
        $pdo->prepare("UPDATE af_batalhas SET status=?,vencedor_id=?,desempate=?,finalizada_em=NOW(),atualizada_em=NOW() WHERE id=?")
            ->execute([$statusFinal,$vencedorId?:null,$desempate?1:0,$batalhaId]);
        if($vencedorId){
            $pdo->prepare('UPDATE af_usuarios SET total_vitorias=total_vitorias+1,atualizado_em=NOW() WHERE id=?')->execute([$vencedorId]);
            criarNotificacao($pdo,$vencedorId,'battle_win','Você venceu uma Aura Battle!','Confira o placar completo na Arena.',['batalha_id'=>$batalhaId]);
        }
        foreach($jogadores as $j){
            if((int)$j['usuario_id']!==$vencedorId) criarNotificacao($pdo,(int)$j['usuario_id'],'battle_result','Aura Battle finalizada','O placar da batalha já está disponível.',['batalha_id'=>$batalhaId]);
        }
        afBatalhaEvento($pdo,$batalhaId,$vencedorId?:null,'resultado',['vencedor_id'=>$vencedorId,'desempate'=>$desempate]);
        if(!empty($b['competicao_id'])){
            foreach($jogadores as $j){
                $venceu=(int)$j['usuario_id']===$vencedorId;
                $novoStatus = $b['tipo']==='torneio' ? ($venceu?'ativo':'eliminado') : 'ativo';
                $pdo->prepare("UPDATE af_competicao_participantes SET partidas=partidas+1,vitorias=vitorias+?,derrotas=derrotas+?,pontuacao_total=pontuacao_total+?,melhor_pontuacao=GREATEST(melhor_pontuacao,?),status=?,atualizado_em=NOW() WHERE competicao_id=? AND usuario_id=?")
                    ->execute([$venceu?1:0,$venceu?0:1,(float)$j['pontuacao'],(float)$j['pontuacao'],$novoStatus,(int)$b['competicao_id'],(int)$j['usuario_id']]);
            }
        }
        $pdo->commit();
        if(!empty($b['competicao_id']) && $b['tipo']==='torneio') afAvancarTorneio($pdo,(int)$b['competicao_id']);
        return afBuscarBatalha($pdo,$batalhaId) ?: [];
    }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
}

function afRegistrarResultadoBatalha(PDO $pdo, array $partida): void
{
    $batalhaId=(int)($partida['batalha_id']??0);if($batalhaId<=0)return;
    $social=(float)$partida['likes_recebidos']*.25+(float)$partida['superlikes_recebidos'];
    $pdo->prepare("UPDATE af_batalha_jogadores SET status='finalizado',partida_id=?,pontuacao=?,aura=?,pontuacao_social=?,combo_maximo=?,finalizado_em=NOW(),atualizada_em=NOW() WHERE batalha_id=? AND usuario_id=?")
        ->execute([(int)$partida['id'],(float)($partida['pontuacao_validada'] ?? 0),(float)$partida['aura_gerada'],$social,(int)($partida['combo_maximo']??1),$batalhaId,(int)$partida['usuario_id']]);
    afBatalhaEvento($pdo,$batalhaId,(int)$partida['usuario_id'],'jogador_finalizou',['partida_id'=>(int)$partida['id'],'pontuacao'=>(float)($partida['pontuacao_validada'] ?? 0)]);
    afResolverBatalha($pdo,$batalhaId);
}

function afBuscarCompeticao(PDO $pdo, int $id): ?array
{
    $st=$pdo->prepare("SELECT c.*,u.nome criador_nome,u.aura_id criador_aura_id,cam.nome campeao_nome,cam.aura_id campeao_aura_id
                       FROM af_competicoes c JOIN af_usuarios u ON u.id=c.criador_id LEFT JOIN af_usuarios cam ON cam.id=c.campeao_id WHERE c.id=? LIMIT 1");
    $st->execute([$id]);return $st->fetch()?:null;
}

function afParticipantesCompeticao(PDO $pdo, int $id): array
{
    $st=$pdo->prepare("SELECT cp.*,u.nome,u.username,u.aura_id,u.foto_perfil,u.foto_externa,u.titulo
                       FROM af_competicao_participantes cp JOIN af_usuarios u ON u.id=cp.usuario_id
                       WHERE cp.competicao_id=? ORDER BY COALESCE(cp.posicao,9999),COALESCE(cp.seed,9999),cp.melhor_pontuacao DESC,cp.entrou_em ASC");
    $st->execute([$id]);return $st->fetchAll()?:[];
}

function afCompeticaoPublica(PDO $pdo,array $c,int $viewerId=0):array
{
    $participantes=array_map(static fn($p)=>[
        'usuario_id'=>(int)$p['usuario_id'],'nome'=>$p['nome'],'username'=>$p['username'],'aura_id'=>$p['aura_id'],
        'avatar'=>urlAvatar($p['foto_perfil']??null,$p['foto_externa']??null),'titulo'=>$p['titulo'],'status'=>$p['status'],
        'seed'=>(int)($p['seed']??0),'vitorias'=>(int)$p['vitorias'],'derrotas'=>(int)$p['derrotas'],'partidas'=>(int)$p['partidas'],
        'pontuacao_total'=>(float)$p['pontuacao_total'],'melhor_pontuacao'=>(float)$p['melhor_pontuacao'],'posicao'=>(int)($p['posicao']??0),
        'eu'=>$viewerId>0&&(int)$p['usuario_id']===$viewerId,
    ],afParticipantesCompeticao($pdo,(int)$c['id']));
    $st=$pdo->prepare('SELECT b.* FROM af_batalhas b WHERE b.competicao_id=? ORDER BY b.rodada,b.chave_ordem');$st->execute([(int)$c['id']]);
    $batalhas=array_map(fn($b)=>afBatalhaPublica($pdo,array_merge($b,['competicao_nome'=>$c['nome'],'competicao_tipo'=>$c['tipo'],'competicao_status'=>$c['status']]),$viewerId),$st->fetchAll()?:[]);
    return ['id'=>(int)$c['id'],'codigo'=>$c['codigo'],'tipo'=>$c['tipo'],'nome'=>$c['nome'],'descricao'=>$c['descricao'],'formato'=>$c['formato'],
        'max_participantes'=>(int)$c['max_participantes'],'status'=>$c['status'],'visibilidade'=>$c['visibilidade'],'cidade'=>$c['cidade'],'estado'=>$c['estado'],'pais'=>$c['pais'],
        'criador_id'=>(int)$c['criador_id'],'criador_nome'=>$c['criador_nome'],'campeao_id'=>(int)($c['campeao_id']??0),'campeao_nome'=>$c['campeao_nome']??null,
        'participantes'=>$participantes,'batalhas'=>$batalhas,'total_participantes'=>count($participantes),'sou_criador'=>(int)$c['criador_id']===$viewerId,
        'inscrito'=>count(array_filter($participantes,static fn($p)=>$p['eu']))>0,'iniciada_em'=>$c['iniciada_em'],'finalizada_em'=>$c['finalizada_em']];
}

function afCriarCompeticao(PDO $pdo,int $criadorId,string $tipo,string $nome,string $descricao,int $max,string $visibilidade='publico',string $requestToken=''):array
{
    $tipo=in_array($tipo,['torneio','evento'],true)?$tipo:'torneio';
    $nome=trim($nome);
    if(mb_strlen($nome)<3||mb_strlen($nome)>100)throw new RuntimeException('Informe um nome entre 3 e 100 caracteres.');
    $descricao=mb_substr(trim($descricao),0,500);
    $custoDiamantes=afCompeticaoCustoDiamantes($max);
    $visibilidade=$visibilidade==='privado'?'privado':'publico';
    $requestToken=trim($requestToken);
    if($requestToken==='')$requestToken=bin2hex(random_bytes(24));
    if(strlen($requestToken)>180)$requestToken=hash('sha256',$requestToken);

    $st=$pdo->prepare('SELECT cidade,estado,pais FROM af_perfis WHERE usuario_id=? LIMIT 1');
    $st->execute([$criadorId]);
    $local=$st->fetch()?:[];

    $pdo->beginTransaction();
    try{
        $codigo=afCodigoCompeticao($pdo,'af_competicoes',8);
        $formato=$tipo==='torneio'?'eliminatorio':'ranking';
        $pdo->prepare('INSERT INTO af_competicoes(criador_id,codigo,tipo,nome,descricao,formato,max_participantes,status,visibilidade,cidade,estado,pais,criada_em,atualizada_em) VALUES (?,?,?,?,?,?,?,\'inscricoes\',?,?,?,?,NOW(),NOW())')
            ->execute([$criadorId,$codigo,$tipo,$nome,$descricao?:null,$formato,$max,$visibilidade,$local['cidade']??null,$local['estado']??null,$local['pais']??'Brasil']);
        $id=(int)$pdo->lastInsertId();
        $pdo->prepare("INSERT INTO af_competicao_participantes(competicao_id,usuario_id,status,entrou_em) VALUES (?,?,'inscrito',NOW())")
            ->execute([$id,$criadorId]);

        $debito=afDiamondsDebit(
            $pdo,
            $criadorId,
            $custoDiamantes,
            'system_correction',
            'Criação de '.($tipo==='torneio'?'torneio':'evento').' para '.$max.' participantes',
            [
                'reference_type'=>'competition_creation',
                'reference_id'=>(string)$id,
                'metadata'=>[
                    'competition_id'=>$id,
                    'competition_code'=>$codigo,
                    'competition_type'=>$tipo,
                    'max_participants'=>$max,
                    'diamonds_charged'=>$custoDiamantes,
                ],
                'idempotency_key'=>'competition_create:'.$criadorId.':'.$requestToken,
            ]
        );

        // Uma repetição do mesmo envio nunca pode criar outro torneio.
        if(!empty($debito['duplicate'])){
            throw new RuntimeException('Esta solicitação de criação já foi processada. Atualize a Arena.');
        }

        $pdo->commit();
        return afBuscarCompeticao($pdo,$id)?:[];
    }catch(Throwable $e){
        if($pdo->inTransaction())$pdo->rollBack();
        throw $e;
    }
}

function afEntrarCompeticao(PDO $pdo,int $usuarioId,string|int $codigoOuId):array
{
    if(is_numeric($codigoOuId)){$st=$pdo->prepare('SELECT * FROM af_competicoes WHERE id=? LIMIT 1');$st->execute([(int)$codigoOuId]);}
    else{$codigo=afCodigoLimpo((string)$codigoOuId);$st=$pdo->prepare('SELECT * FROM af_competicoes WHERE codigo=? LIMIT 1');$st->execute([$codigo]);}
    $c=$st->fetch();if(!$c)throw new RuntimeException('Competição não encontrada.');
    if($c['status']!=='inscricoes')throw new RuntimeException('As inscrições estão encerradas.');
    $st=$pdo->prepare('SELECT COUNT(*) FROM af_competicao_participantes WHERE competicao_id=?');$st->execute([(int)$c['id']]);
    if((int)$st->fetchColumn()>=(int)$c['max_participantes'])throw new RuntimeException('A competição já está lotada.');
    $pdo->prepare("INSERT INTO af_competicao_participantes(competicao_id,usuario_id,status,entrou_em) VALUES (?,?,'inscrito',NOW()) ON DUPLICATE KEY UPDATE atualizado_em=NOW()")
        ->execute([(int)$c['id'],$usuarioId]);
    return afBuscarCompeticao($pdo,(int)$c['id'])?:[];
}

function afCriarBatalhaCompeticao(PDO $pdo,int $competicaoId,int $criadorId,int $rodada,int $ordem,int $j1,int $j2,string $tipo='torneio'):int
{
    $codigo=afCodigoCompeticao($pdo,'af_batalhas',10);
    $pdo->prepare("INSERT INTO af_batalhas(competicao_id,criador_id,codigo,tipo,rodada,chave_ordem,status,duracao_segundos,criada_em,atualizada_em) VALUES (?,?,?,?,?,?,'aguardando_prontidao',180,NOW(),NOW())")
        ->execute([$competicaoId,$criadorId,$codigo,$tipo,$rodada,$ordem]);$id=(int)$pdo->lastInsertId();
    $pdo->prepare("INSERT INTO af_batalha_jogadores(batalha_id,usuario_id,slot,status,aceito_em,criada_em) VALUES (?,?,1,'aceito',NOW(),NOW()),(?,?,2,'aceito',NOW(),NOW())")
        ->execute([$id,$j1,$id,$j2]);
    foreach([$j1,$j2] as $uid)criarNotificacao($pdo,$uid,'competition_battle','Sua próxima batalha está pronta!','Abra a competição e prepare sua câmera.',['batalha_id'=>$id,'competicao_id'=>$competicaoId]);
    return $id;
}

function afIniciarCompeticao(PDO $pdo,int $competicaoId,int $usuarioId):array
{
    $pdo->beginTransaction();
    try{
        $st=$pdo->prepare('SELECT * FROM af_competicoes WHERE id=? LIMIT 1 FOR UPDATE');$st->execute([$competicaoId]);$c=$st->fetch();
        if(!$c)throw new RuntimeException('Competição não encontrada.');if((int)$c['criador_id']!==$usuarioId)throw new RuntimeException('Somente o criador pode iniciar.');
        if($c['status']!=='inscricoes')throw new RuntimeException('A competição já foi iniciada.');
        $st=$pdo->prepare('SELECT usuario_id FROM af_competicao_participantes WHERE competicao_id=? ORDER BY entrou_em');$st->execute([$competicaoId]);$ids=array_map('intval',$st->fetchAll(PDO::FETCH_COLUMN));
        if(count($ids)<2)throw new RuntimeException('São necessários pelo menos 2 participantes.');
        if($c['tipo']==='torneio'){
            $maxParticipantes=(int)$c['max_participantes'];
            afCompeticaoCustoDiamantes($maxParticipantes);
            if(count($ids)!==$maxParticipantes){
                throw new RuntimeException('O torneio precisa estar completo com exatamente '.$maxParticipantes.' participantes antes de iniciar.');
            }
        }
        shuffle($ids);foreach($ids as $i=>$uid)$pdo->prepare("UPDATE af_competicao_participantes SET seed=?,status='ativo' WHERE competicao_id=? AND usuario_id=?")->execute([$i+1,$competicaoId,$uid]);
        $pdo->prepare("UPDATE af_competicoes SET status='em_andamento',iniciada_em=NOW(),atualizada_em=NOW() WHERE id=?")->execute([$competicaoId]);
        if($c['tipo']==='torneio'){
            for($i=0,$ordem=1;$i<count($ids);$i+=2,$ordem++)afCriarBatalhaCompeticao($pdo,$competicaoId,$usuarioId,1,$ordem,$ids[$i],$ids[$i+1],'torneio');
        }else{
            foreach($ids as $uid)criarNotificacao($pdo,$uid,'event_started','Evento iniciado!','Você já pode realizar sua farmada oficial do evento.',['competicao_id'=>$competicaoId]);
        }
        $pdo->commit();return afBuscarCompeticao($pdo,$competicaoId)?:[];
    }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
}

function afAvancarTorneio(PDO $pdo,int $competicaoId):void
{
    $c=afBuscarCompeticao($pdo,$competicaoId);if(!$c||$c['tipo']!=='torneio'||$c['status']!=='em_andamento')return;
    $st=$pdo->prepare('SELECT MAX(rodada) FROM af_batalhas WHERE competicao_id=?');$st->execute([$competicaoId]);$rodada=(int)$st->fetchColumn();
    $st=$pdo->prepare('SELECT * FROM af_batalhas WHERE competicao_id=? AND rodada=? ORDER BY chave_ordem');$st->execute([$competicaoId,$rodada]);$batalhas=$st->fetchAll()?:[];
    if(!$batalhas||count(array_filter($batalhas,static fn($b)=>$b['status']==='finalizada'))!==count($batalhas))return;
    $vencedores=array_values(array_filter(array_map(static fn($b)=>(int)($b['vencedor_id']??0),$batalhas)));
    if(count($vencedores)===1){
        $champ=$vencedores[0];$pdo->beginTransaction();
        try{$pdo->prepare("UPDATE af_competicoes SET status='finalizada',campeao_id=?,finalizada_em=NOW(),atualizada_em=NOW() WHERE id=?")->execute([$champ,$competicaoId]);
            $pdo->prepare("UPDATE af_competicao_participantes SET status=IF(usuario_id=?,'campeao','eliminado'),posicao=IF(usuario_id=?,1,posicao),atualizado_em=NOW() WHERE competicao_id=?")->execute([$champ,$champ,$competicaoId]);
            criarNotificacao($pdo,$champ,'tournament_champion','Você é campeão!','Parabéns! Você venceu o torneio '.$c['nome'].'.',['competicao_id'=>$competicaoId]);$pdo->commit();
        }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}return;
    }
    if(count($vencedores)<2)return;
    $next=$rodada+1;$st=$pdo->prepare('SELECT COUNT(*) FROM af_batalhas WHERE competicao_id=? AND rodada=?');$st->execute([$competicaoId,$next]);if((int)$st->fetchColumn()>0)return;
    $pdo->beginTransaction();
    try{for($i=0,$ordem=1;$i<count($vencedores);$i+=2,$ordem++)afCriarBatalhaCompeticao($pdo,$competicaoId,(int)$c['criador_id'],$next,$ordem,$vencedores[$i],$vencedores[$i+1],'torneio');$pdo->commit();}
    catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
}

function afValidarEventoParaPartida(PDO $pdo,int $competicaoId,int $usuarioId):array
{
    $c=afBuscarCompeticao($pdo,$competicaoId);if(!$c||$c['tipo']!=='evento')throw new RuntimeException('Evento não encontrado.');
    if($c['status']!=='em_andamento')throw new RuntimeException('O evento não está em andamento.');
    $st=$pdo->prepare("SELECT * FROM af_competicao_participantes WHERE competicao_id=? AND usuario_id=? LIMIT 1");$st->execute([$competicaoId,$usuarioId]);$p=$st->fetch();if(!$p)throw new RuntimeException('Você não está inscrito neste evento.');
    $st=$pdo->prepare("SELECT id,status FROM af_partidas WHERE competicao_id=? AND usuario_id=? AND modo='evento' ORDER BY id DESC LIMIT 1");$st->execute([$competicaoId,$usuarioId]);$exist=$st->fetch();
    if($exist && $exist['status']==='finalizada')throw new RuntimeException('Você já concluiu sua participação neste evento.');
    return [$c,$p];
}

function afRegistrarResultadoEvento(PDO $pdo,array $partida):void
{
    $comp=(int)($partida['competicao_id']??0);if($comp<=0||($partida['modo']??'')!=='evento')return;
    $pdo->prepare("UPDATE af_competicao_participantes cp SET status='ativo',
        partidas=(SELECT COUNT(*) FROM af_partidas p WHERE p.competicao_id=cp.competicao_id AND p.usuario_id=cp.usuario_id AND p.modo='evento' AND p.status='finalizada' AND p.antifraude_status='aprovada' AND COALESCE(p.admin_status,'normal')='normal'),
        pontuacao_total=(SELECT COALESCE(SUM(p.pontuacao_validada),0) FROM af_partidas p WHERE p.competicao_id=cp.competicao_id AND p.usuario_id=cp.usuario_id AND p.modo='evento' AND p.status='finalizada' AND p.antifraude_status='aprovada' AND COALESCE(p.admin_status,'normal')='normal'),
        melhor_pontuacao=(SELECT COALESCE(MAX(p.pontuacao_validada),0) FROM af_partidas p WHERE p.competicao_id=cp.competicao_id AND p.usuario_id=cp.usuario_id AND p.modo='evento' AND p.status='finalizada' AND p.antifraude_status='aprovada' AND COALESCE(p.admin_status,'normal')='normal'),
        atualizado_em=NOW() WHERE cp.competicao_id=? AND cp.usuario_id=?")
        ->execute([$comp,(int)$partida['usuario_id']]);
}

function afFinalizarEvento(PDO $pdo,int $competicaoId,int $usuarioId):array
{
    $c=afBuscarCompeticao($pdo,$competicaoId);if(!$c||$c['tipo']!=='evento')throw new RuntimeException('Evento não encontrado.');if((int)$c['criador_id']!==$usuarioId)throw new RuntimeException('Somente o criador pode finalizar.');
    if($c['status']!=='em_andamento')throw new RuntimeException('O evento não está em andamento.');
    $parts=afParticipantesCompeticao($pdo,$competicaoId);usort($parts,static fn($a,$z)=>(float)$z['melhor_pontuacao']<=>(float)$a['melhor_pontuacao']);
    if(!$parts||(float)$parts[0]['melhor_pontuacao']<=0)throw new RuntimeException('Nenhum participante concluiu uma partida ainda.');
    $pdo->beginTransaction();try{
        foreach($parts as $i=>$p)$pdo->prepare("UPDATE af_competicao_participantes SET posicao=?,status=IF(?=1,'campeao','ativo'),atualizado_em=NOW() WHERE id=?")->execute([$i+1,$i===0?1:0,(int)$p['id']]);
        $champ=(int)$parts[0]['usuario_id'];$pdo->prepare("UPDATE af_competicoes SET status='finalizada',campeao_id=?,finalizada_em=NOW(),atualizada_em=NOW() WHERE id=?")->execute([$champ,$competicaoId]);
        criarNotificacao($pdo,$champ,'event_champion','Você venceu um evento!','Sua pontuação ficou em primeiro lugar no evento '.$c['nome'].'.',['competicao_id'=>$competicaoId]);$pdo->commit();return afBuscarCompeticao($pdo,$competicaoId)?:[];
    }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
}

function afListarCompeticoes(PDO $pdo,int $usuarioId,int $limite=30):array
{
    $limite=max(1,min(100,$limite));
    $st=$pdo->prepare("SELECT DISTINCT c.*,u.nome criador_nome,u.aura_id criador_aura_id,cam.nome campeao_nome,cam.aura_id campeao_aura_id
                       FROM af_competicoes c JOIN af_usuarios u ON u.id=c.criador_id LEFT JOIN af_usuarios cam ON cam.id=c.campeao_id
                       LEFT JOIN af_competicao_participantes cp ON cp.competicao_id=c.id
                       WHERE c.visibilidade='publico' OR c.criador_id=? OR cp.usuario_id=?
                       ORDER BY FIELD(c.status,'em_andamento','inscricoes','finalizada','cancelada'),c.atualizada_em DESC LIMIT {$limite}");
    $st->execute([$usuarioId,$usuarioId]);return array_map(fn($c)=>afCompeticaoPublica($pdo,$c,$usuarioId),$st->fetchAll()?:[]);
}
