<?php
/**
 * AURA FARMING — Etapa 18
 * Motor central de políticas de segurança para contas menores de idade.
 *
 * Regras importantes:
 * - falha fechada para recursos não reconhecidos em contas menores;
 * - responsável verificado e consentimento atual são avaliados no servidor;
 * - recursos de alto risco continuam bloqueados;
 * - Farm/câmera usam a camada técnica de câmera segura da Etapa 19;
 * - horários, pausa e limite diário são aplicados pelo painel do responsável da Etapa 20;
 * - menores de 16 anos ficam reservados ao modo de evento controlado.
 */

function afMinorPolicyDefinitions(): array
{
    return [
        'controlled_event_farm' => [
            'label' => 'Evento infantil controlado',
            'group' => 'controlled_event',
            'permission' => null,
            'minimum_age' => 0,
            'technical_gates' => ['minor_safe_camera_ready'],
            'high_risk' => false,
            'controlled_event_only' => true,
        ],
        'controlled_event_ranking' => [
            'label' => 'Ranking fechado do evento infantil',
            'group' => 'controlled_event',
            'permission' => null,
            'minimum_age' => 0,
            'technical_gates' => [],
            'high_risk' => false,
            'controlled_event_only' => true,
        ],
        'account_maintenance' => [
            'label' => 'Manutenção da conta',
            'group' => 'essential',
            'permission' => 'account_maintenance',
            'minimum_age' => 0,
            'technical_gates' => [],
            'high_risk' => false,
        ],
        'ranking_alias' => [
            'label' => 'Ranking com apelido protegido',
            'group' => 'social',
            'permission' => 'ranking_alias',
            'minimum_age' => 13,
            'technical_gates' => ['minor_ranking_alias_enabled'],
            'high_risk' => false,
        ],
        'guardian_notifications' => [
            'label' => 'Notificações ao responsável',
            'group' => 'notifications',
            'permission' => 'guardian_notifications',
            'minimum_age' => 0,
            'technical_gates' => [],
            'high_risk' => false,
        ],
        'camera' => [
            'label' => 'Câmera e detecção de movimentos',
            'group' => 'camera',
            'permission' => 'camera_motion_detection',
            'minimum_age' => 13,
            'technical_gates' => ['minor_safe_camera_ready'],
            'high_risk' => false,
        ],
        'body_processing' => [
            'label' => 'Processamento corporal',
            'group' => 'camera',
            'permission' => 'body_movement_processing',
            'minimum_age' => 13,
            'technical_gates' => ['minor_safe_camera_ready'],
            'high_risk' => false,
        ],
        'farm' => [
            'label' => 'Farm individual',
            'group' => 'gameplay',
            'permission' => 'individual_farm',
            'minimum_age' => 13,
            'technical_gates' => ['minor_safe_camera_ready','minor_individual_farm_enabled'],
            'high_risk' => false,
        ],
        'battle_hub' => [
            'label' => 'Área de Battle protegida',
            'group' => 'gameplay',
            'permission' => 'approved_contacts_battle',
            'minimum_age' => 13,
            'technical_gates' => ['minor_battle_enabled'],
            'high_risk' => false,
        ],
        'battle' => [
            'label' => 'Battle com contato aprovado',
            'group' => 'gameplay',
            'permission' => 'approved_contacts_battle',
            'minimum_age' => 13,
            'technical_gates' => ['minor_safe_camera_ready','minor_battle_enabled'],
            'requires_approved_contact' => true,
            'high_risk' => false,
        ],
        'competition' => [
            'label' => 'Competições públicas',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'live' => [
            'label' => 'Transmissões ao vivo',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'public_live' => [
            'label' => 'Transmissão pública',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'share' => [
            'label' => 'Compartilhamento público',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'challenge' => [
            'label' => 'Desafios públicos',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'real_video_replay' => [
            'label' => 'Replay com imagem real',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'video_download' => [
            'label' => 'Download de vídeo',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'stranger_messages' => [
            'label' => 'Mensagens de desconhecidos',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'social_follow' => [
            'label' => 'Seguir perfis públicos',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'full_public_profile' => [
            'label' => 'Perfil público completo',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
        'public_location' => [
            'label' => 'Localização pública',
            'group' => 'high_risk',
            'permission' => null,
            'minimum_age' => 18,
            'technical_gates' => [],
            'high_risk' => true,
        ],
    ];
}

function afMinorPolicyFeatureAlias(string $feature): string
{
    $feature = strtolower(trim($feature));
    return match ($feature) {
        'camera_motion_detection' => 'camera',
        'body_movement_processing' => 'body_processing',
        'individual_farm' => 'farm',
        'approved_contacts_battle' => 'battle',
        'battle_area' => 'battle_hub',
        'ranking' => 'ranking_alias',
        'notifications' => 'guardian_notifications',
        'profile_public', 'public_profile' => 'full_public_profile',
        'replay' => 'real_video_replay',
        'event_child', 'controlled_event', 'controlled_event_camera' => 'controlled_event_farm',
        'event_child_ranking' => 'controlled_event_ranking',
        default => $feature,
    };
}

function afMinorPolicyConfig(PDO $pdo, string $key, mixed $default = null): mixed
{
    static $cache = [];
    if (array_key_exists($key,$cache)) return $cache[$key];
    try {
        $st=$pdo->prepare('SELECT valor,tipo FROM af_configuracoes WHERE chave=? LIMIT 1');
        $st->execute([$key]);
        $row=$st->fetch();
        if (!$row) return $cache[$key]=$default;
        $cache[$key]=match((string)$row['tipo']){
            'bool'=>filter_var($row['valor'],FILTER_VALIDATE_BOOLEAN),
            'int'=>(int)$row['valor'],
            'float'=>(float)$row['valor'],
            'json'=>json_decode((string)$row['valor'],true),
            default=>$row['valor'],
        };
        return $cache[$key];
    } catch (Throwable $e) {
        return $cache[$key]=$default;
    }
}

function afMinorPolicyCurrentVersion(PDO $pdo): array
{
    static $version = null;
    if (is_array($version)) return $version;
    try {
        $wanted=(string)afMinorPolicyConfig($pdo,'minor_policy_current_version','1.0');
        $st=$pdo->prepare('SELECT * FROM af_minor_policy_versions WHERE version_key=? AND ativo=1 LIMIT 1');
        $st->execute([$wanted]);
        $version=$st->fetch() ?: null;
        if (!$version) {
            $st=$pdo->query('SELECT * FROM af_minor_policy_versions WHERE ativo=1 ORDER BY vigencia_em DESC,id DESC LIMIT 1');
            $version=$st?$st->fetch():null;
        }
    } catch (Throwable $e) {
        $version=null;
    }
    return $version ?: [
        'id'=>0,
        'version_key'=>'1.0',
        'titulo'=>'Política de segurança para menores',
        'resumo'=>'Regras de proteção da Etapa 18.',
        'content_hash'=>'',
    ];
}

function afMinorPolicyAgeBand(?int $age): string
{
    if ($age === null) return 'unverified';
    if ($age < 13) return 'child_under_13';
    if ($age < 16) return 'teen_13_15';
    if ($age < 18) return 'teen_16_17';
    return 'adult';
}

function afMinorPolicyAgeBandLabel(string $band): string
{
    return match($band){
        'child_under_13'=>'Menor de 13 anos',
        'teen_13_15'=>'Adolescente de 13 a 15 anos',
        'teen_16_17'=>'Adolescente de 16 a 17 anos',
        'adult'=>'Maior de 18 anos',
        default=>'Idade não confirmada',
    };
}

function afMinorPolicyContext(PDO $pdo, int $usuarioId): array
{
    if (isset($GLOBALS['af_minor_policy_context_cache'][$usuarioId])) {
        return $GLOBALS['af_minor_policy_context_cache'][$usuarioId];
    }
    $age=afResumoIdade($pdo,$usuarioId);
    $ageValue=isset($age['idade']) && $age['idade']!==null ? (int)$age['idade'] : null;
    $isMinor=($age['tipo_conta']??'')==='minor' || ($ageValue!==null && $ageValue<18);
    $guardian=$isMinor ? ($age['guardian'] ?? null) : null;
    if ($isMinor && !is_array($guardian) && function_exists('afGuardianResumoDoMenor')) {
        try{$guardian=afGuardianResumoDoMenor($pdo,$usuarioId);}catch(Throwable $e){$guardian=null;}
    }
    $consent=$isMinor ? ($age['consent'] ?? null) : null;
    if ($isMinor && !is_array($consent) && function_exists('afConsentResumoDoMenor')) {
        try{$consent=afConsentResumoDoMenor($pdo,$usuarioId);}catch(Throwable $e){$consent=null;}
    }
    $link=is_array($guardian)?($guardian['vinculo']??null):null;
    $familyConfirmed=is_array($link) && function_exists('afGuardianFamilyLinkConfirmed')
        ? afGuardianFamilyLinkConfirmed($pdo,(int)($link['id']??0))
        : !empty($link['family_confirmed_at']);
    $guardianVerified=is_array($link)
        && $familyConfirmed
        && (string)($link['guardian_status']??'')==='verified'
        && in_array((string)($link['status']??''),['verified_pending_consent','active'],true)
        && (!function_exists('afGuardianAdultVerified') || afGuardianAdultVerified($pdo,(int)($link['guardian_usuario_id']??0)));
    $consentActive=is_array($consent) && !empty($consent['active']);
    $currentConsentVersion='';
    try{$currentConsentVersion=(string)(afConsentCurrentVersion($pdo)['version_key']??'');}catch(Throwable $e){}
    $consentVersion=(string)($consent['consent']['version_key']??'');
    $permissions=[];
    foreach(($consent['permissions']??[]) as $key=>$row){
        $permissions[(string)$key]=is_array($row)?!empty($row['allowed']):(bool)$row;
    }
    $parentalAccess=null;
    if($isMinor && function_exists('afGuardianDashboardAccessDecision')){
        try{$parentalAccess=afGuardianDashboardAccessDecision($pdo,$usuarioId);}catch(Throwable $e){$parentalAccess=['allowed'=>false,'code'=>'PARENTAL_CONTROLS_UNAVAILABLE','message'=>'Os controles do responsável não puderam ser validados.'];}
    }
    $safetySettings=[];
    if($isMinor && function_exists('afMinorSafetySettingsEnsure')){
        try{$safetySettings=afMinorSafetySettingsEnsure($pdo,$usuarioId);}catch(Throwable $e){$safetySettings=[];}
    }
    $ctx=[
        'user_id'=>$usuarioId,
        'age'=>$age,
        'age_value'=>$ageValue,
        'age_band'=>afMinorPolicyAgeBand($ageValue),
        'is_minor'=>$isMinor,
        'guardian'=>$guardian,
        'guardian_link'=>$link,
        'guardian_linked'=>is_array($link),
        'guardian_verified'=>$guardianVerified,
        'family_confirmed'=>$familyConfirmed,
        'consent'=>$consent,
        'consent_active'=>$consentActive,
        'consent_version'=>$consentVersion,
        'current_consent_version'=>$currentConsentVersion,
        'consent_current'=>$consentActive && ($currentConsentVersion==='' || hash_equals($currentConsentVersion,$consentVersion)),
        'permissions'=>$permissions,
        'parental_access'=>$parentalAccess,
        'safety_settings'=>$safetySettings,
    ];
    $GLOBALS['af_minor_policy_context_cache'][$usuarioId]=$ctx;
    return $ctx;
}

function afMinorPolicyClearCache(?int $usuarioId = null): void
{
    if ($usuarioId===null) {
        $GLOBALS['af_minor_policy_context_cache']=[];
        $GLOBALS['af_minor_policy_decision_cache']=[];
        return;
    }
    unset($GLOBALS['af_minor_policy_context_cache'][$usuarioId]);
    foreach(array_keys($GLOBALS['af_minor_policy_decision_cache']??[]) as $key){
        if(str_starts_with($key,$usuarioId.'|'))unset($GLOBALS['af_minor_policy_decision_cache'][$key]);
    }
}

function afMinorPolicyDecisionResult(
    int $usuarioId,
    string $feature,
    bool $allowed,
    string $code,
    string $message,
    array $context,
    array $requirements = [],
    string $mode = 'blocked'
): array {
    return [
        'user_id'=>$usuarioId,
        'feature'=>$feature,
        'label'=>afMinorPolicyDefinitions()[$feature]['label']??$feature,
        'allowed'=>$allowed,
        'code'=>$code,
        'message'=>$message,
        'mode'=>$mode,
        'policy_version'=>(string)($context['policy_version']??'1.0'),
        'age_band'=>(string)($context['age_band']??'unverified'),
        'age_band_label'=>afMinorPolicyAgeBandLabel((string)($context['age_band']??'unverified')),
        'requirements'=>$requirements,
        'evaluated_at'=>date(DATE_ATOM),
    ];
}

function afMinorPolicyDecision(PDO $pdo, int $usuarioId, string $feature, array $requestContext = [], bool $audit = true): array
{
    $feature=afMinorPolicyFeatureAlias($feature);
    $cacheKey=$usuarioId.'|'.$feature.'|'.hash('sha256',json_encode($requestContext,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)?:'');
    if(isset($GLOBALS['af_minor_policy_decision_cache'][$cacheKey]))return $GLOBALS['af_minor_policy_decision_cache'][$cacheKey];

    $ctx=afMinorPolicyContext($pdo,$usuarioId);
    $version=afMinorPolicyCurrentVersion($pdo);
    $ctx['policy_version']=(string)($version['version_key']??'1.0');
    $definitions=afMinorPolicyDefinitions();
    $definition=$definitions[$feature]??null;

    if(!$ctx['age']['completo']){
        $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'AGE_VERIFICATION_REQUIRED','Confirme a data de nascimento antes de usar este recurso.',$ctx,['age_verified'=>false]);
    }elseif(!$ctx['is_minor']){
        $ageStatus=(string)($ctx['age']['status']??'unverified');
        $assurance=function_exists('afAgeAssuranceStatus')?afAgeAssuranceStatus($pdo,$usuarioId):['ready'=>in_array($ageStatus,['adult_verified','guardian_verified'],true),'code'=>'LEGACY'];
        if(in_array($ageStatus,['adult_declared','guardian_declared'],true) && empty($assurance['ready'])){
            $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'ADULT_ASSURANCE_REQUIRED',(string)($assurance['message']??'Confirme sua maioridade antes de usar este recurso.'),$ctx,['age_status'=>$ageStatus,'assurance'=>$assurance]);
        }else{
            $decision=afMinorPolicyDecisionResult($usuarioId,$feature,true,'ADULT_ACCOUNT','Recurso disponível para conta adulta confirmada.',$ctx,['age_verified'=>true],'standard');
        }
    }elseif(!$definition){
        $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'MINOR_POLICY_FAIL_CLOSED','Este recurso não possui regra segura para contas menores e foi bloqueado.',$ctx,['known_feature'=>false]);
    }elseif(!empty($definition['high_risk'])){
        $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'MINOR_HIGH_RISK_LOCKED','Este recurso de alto risco permanece desativado para contas menores.',$ctx,['high_risk_locked'=>true]);
    }elseif(!empty($definition['controlled_event_only'])){
        $eventId=(int)($requestContext['controlled_event_id']??0);
        $release=function_exists('afStage25MinorReleaseReady')?afStage25MinorReleaseReady($pdo,'controlled_event',$feature):(function_exists('afPrivacyMinorReleaseReady')?afPrivacyMinorReleaseReady($pdo):['ready'=>true,'code'=>'NOT_CONFIGURED']);
        if($eventId<=0){
            $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'CONTROLLED_EVENT_REQUIRED','Este recurso exige um evento infantil controlado válido.',$ctx,['controlled_event_id'=>0]);
        }elseif(empty($release['ready'])){
            $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'RISK_ASSESSMENT_REQUIRED','A liberação do evento infantil aguarda revisão jurídica, testes finais e aprovação da Etapa 25.',$ctx,['risk_assessment_approved'=>!empty($release['base']['ready']),'stage25_release_ready'=>!empty($release['ready']),'release_code'=>$release['code']??'','controlled_event_id'=>$eventId]);
        }else{
            $eventAccess=function_exists('afControlledEventAccessDecision')?afControlledEventAccessDecision($pdo,$usuarioId,$eventId,$feature==='controlled_event_farm'):['allowed'=>false,'code'=>'CONTROLLED_EVENT_UNAVAILABLE','message'=>'O módulo de evento infantil não está disponível.'];
            if(empty($eventAccess['allowed'])){
                $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,(string)($eventAccess['code']??'CONTROLLED_EVENT_BLOCKED'),(string)($eventAccess['message']??'O acesso ao evento foi bloqueado.'),$ctx,['controlled_event_id'=>$eventId]);
            }else{
                $failedGate=null;foreach(($definition['technical_gates']??[]) as $gate){if(!filter_var(afMinorPolicyConfig($pdo,(string)$gate,false),FILTER_VALIDATE_BOOLEAN)){$failedGate=(string)$gate;break;}}
                if($failedGate!==null)$decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'TECHNICAL_PROTECTION_PENDING','A proteção técnica necessária ao evento ainda não está disponível.',$ctx,['failed_gate'=>$failedGate,'controlled_event_id'=>$eventId]);
                else $decision=afMinorPolicyDecisionResult($usuarioId,$feature,true,'CONTROLLED_EVENT_ALLOWED','Recurso permitido somente dentro do evento infantil supervisionado.',$ctx,['controlled_event_id'=>$eventId,'supervised'=>true,'public_stream'=>false,'comments'=>false,'messages'=>false],'controlled_event');
            }
        }
    }elseif(in_array($ctx['age_band'],['child_under_13','teen_13_15'],true) && !in_array($feature,['account_maintenance','guardian_notifications'],true)){
        $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'CONTROLLED_EVENT_ONLY','Contas com menos de 16 anos permanecem restritas ao modo de evento controlado e supervisionado.',$ctx,['minimum_age'=>16,'controlled_event_required'=>true]);
    }elseif(!$ctx['guardian_linked']){
        $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'GUARDIAN_LINK_REQUIRED','Vincule uma conta de responsável legal para continuar.',$ctx,['guardian_linked'=>false]);
    }elseif(!$ctx['guardian_verified']){
        $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'GUARDIAN_VERIFICATION_REQUIRED','O responsável precisa concluir a verificação de adulto.',$ctx,['guardian_linked'=>true,'guardian_verified'=>false]);
    }elseif(!$ctx['consent_active']){
        $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'PARENTAL_CONSENT_REQUIRED','O responsável precisa registrar os consentimentos específicos.',$ctx,['guardian_verified'=>true,'consent_active'=>false]);
    }elseif(!$ctx['consent_current']){
        $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'CONSENT_UPDATE_REQUIRED','O responsável precisa revisar a versão atual dos consentimentos.',$ctx,['consent_current'=>false]);
    }else{
        $permission=(string)($definition['permission']??'');
        $release=function_exists('afStage25MinorReleaseReady')?afStage25MinorReleaseReady($pdo,'protected_minor',$feature):(function_exists('afPrivacyMinorReleaseReady')?afPrivacyMinorReleaseReady($pdo):['ready'=>true,'code'=>'NOT_CONFIGURED']);
        $explicitPermission=function_exists('afMinorFeaturePermission')?afMinorFeaturePermission($pdo,$usuarioId,$feature):null;
        $featureAccess=function_exists('afGuardianDashboardFeatureDecision')?afGuardianDashboardFeatureDecision($pdo,$usuarioId,$feature):$ctx['parental_access'];
        if($permission!=='' && empty($ctx['permissions'][$permission])){
            $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'PERMISSION_NOT_GRANTED','O responsável não autorizou este recurso.',$ctx,['permission'=>$permission,'permission_granted'=>false]);
        }elseif(!in_array($feature,['account_maintenance','guardian_notifications'],true) && empty($release['ready'])){
            $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'RISK_ASSESSMENT_REQUIRED','A liberação de contas menores aguarda revisão jurídica, testes finais e aprovação da Etapa 25.',$ctx,['risk_assessment_approved'=>!empty($release['base']['ready']),'stage25_release_ready'=>!empty($release['ready']),'release_code'=>$release['code']??'']);
        }elseif($explicitPermission===false){
            $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'FEATURE_DISABLED_BY_GUARDIAN','O responsável desativou este recurso no painel familiar.',$ctx,['feature_permission'=>false]);
        }elseif((int)($definition['minimum_age']??0)>0 && (int)$ctx['age_value']<(int)$definition['minimum_age']){
            $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'MINIMUM_AGE_NOT_MET','A idade mínima definida pela política ainda não foi atingida.',$ctx,['minimum_age'=>(int)$definition['minimum_age']]);
        }elseif(in_array($feature,['camera','body_processing','farm','battle','ranking_alias'],true) && is_array($featureAccess) && empty($featureAccess['allowed'])){
            $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,(string)($featureAccess['code']??'PARENTAL_CONTROLS_BLOCKED'),(string)($featureAccess['message']??'O responsável bloqueou temporariamente este recurso.'),$ctx,['parental_controls'=>true,'used_seconds'=>(int)($featureAccess['used_seconds']??0),'remaining_seconds'=>(int)($featureAccess['remaining_seconds']??0)]);
        }else{
            $failedGate=null;
            foreach(($definition['technical_gates']??[]) as $gate){
                if(!filter_var(afMinorPolicyConfig($pdo,(string)$gate,false),FILTER_VALIDATE_BOOLEAN)){$failedGate=(string)$gate;break;}
            }
            if($failedGate!==null){
                $message=$failedGate==='minor_safe_camera_ready'
                    ? 'A câmera segura está temporariamente indisponível para esta conta ou aparelho.'
                    : 'A autorização existe, mas este recurso ainda não foi liberado tecnicamente para menores.';
                $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'TECHNICAL_PROTECTION_PENDING',$message,$ctx,['failed_gate'=>$failedGate,'consent_granted'=>true]);
            }else{
                $approvedContact=!empty($requestContext['approved_contact']);
                $opponentId=(int)($requestContext['opponent_user_id']??0);
                if(!$approvedContact && $opponentId>0 && function_exists('afApprovedContactCheck'))$approvedContact=afApprovedContactCheck($pdo,$usuarioId,$opponentId);
                if(!empty($definition['requires_approved_contact']) && !$approvedContact){
                    $decision=afMinorPolicyDecisionResult($usuarioId,$feature,false,'APPROVED_CONTACT_REQUIRED','A partida só pode ocorrer com um contato previamente aprovado pelo responsável.',$ctx,['approved_contact'=>false,'opponent_user_id'=>$opponentId]);
                }else{
                    $mode=$feature==='ranking_alias'?'alias_only':'protected';
                    $decision=afMinorPolicyDecisionResult($usuarioId,$feature,true,'MINOR_POLICY_ALLOWED','Recurso permitido dentro das proteções da conta.',$ctx,['guardian_verified'=>true,'consent_current'=>true,'permission_granted'=>true,'approved_contact'=>$approvedContact],$mode);
                }
            }
        }
    }

    $GLOBALS['af_minor_policy_decision_cache'][$cacheKey]=$decision;
    if($audit && empty($decision['allowed']))afMinorPolicyAuditDecision($pdo,$decision,$requestContext);
    if($audit && $ctx['is_minor'] && function_exists('afMinorAccessLog'))afMinorAccessLog($pdo,$decision,$requestContext);
    return $decision;
}

function afMinorPolicyAuditDecision(PDO $pdo, array $decision, array $context = []): void
{
    if(!filter_var(afMinorPolicyConfig($pdo,'minor_policy_log_denials',true),FILTER_VALIDATE_BOOLEAN))return;
    $uid=(int)($decision['user_id']??0);
    if($uid<=0)return;
    try{
        $st=$pdo->prepare('SELECT id FROM af_minor_policy_decision_logs WHERE usuario_id=? AND feature_key=? AND reason_code=? AND criado_em>=DATE_SUB(NOW(),INTERVAL 1 HOUR) LIMIT 1');
        $st->execute([$uid,(string)$decision['feature'],(string)$decision['code']]);
        if($st->fetchColumn())return;
        $pdo->prepare('INSERT INTO af_minor_policy_decision_logs
            (usuario_id,policy_version,feature_key,decision_key,reason_code,age_band,context_json,ip_address,user_agent,device_uuid,criado_em)
            VALUES (?,?,?,?,?,?,?,?,?,?,NOW())')->execute([
                $uid,(string)$decision['policy_version'],substr((string)$decision['feature'],0,80),
                !empty($decision['allowed'])?'allow':'deny',substr((string)$decision['code'],0,80),
                substr((string)$decision['age_band'],0,40),
                $context?json_encode($context,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES):null,
                clientIp(),substr((string)($_SERVER['HTTP_USER_AGENT']??''),0,1000),deviceUuid()
            ]);
        if(empty($decision['allowed']) && function_exists('afGuardianDashboardCreateAlert')){
            afGuardianDashboardCreateAlert($pdo,$uid,'blocked_access','warning','Tentativa de acesso bloqueada',(string)($decision['message']??'Um recurso protegido foi bloqueado.'),['feature'=>$decision['feature']??'','code'=>$decision['code']??''],'minor_policy',null);
        }
        if(empty($decision['allowed']) && function_exists('afStage24SecurityAlert')){
            $code=(string)($decision['code']??'POLICY_DENIED');
            $sev=in_array($code,['MINOR_HIGH_RISK_LOCKED','CONTROLLED_EVENT_REQUIRED','RISK_ASSESSMENT_REQUIRED','APPROVED_CONTACT_REQUIRED'],true)?'high':'medium';
            afStage24SecurityAlert($pdo,'minor_policy_denial',$sev,'Política de menor bloqueou acesso',(string)($decision['message']??'Acesso bloqueado pela política de proteção de menores.'),$uid,$uid,'minor_policy','feature',(string)($decision['feature']??''),['feature'=>$decision['feature']??'','code'=>$code,'age_band'=>$decision['age_band']??'','context'=>$context],$sev==='high'?8:3);
        }
        if(function_exists('afStage24RegisterApiContext')){
            afStage24RegisterApiContext($uid,(string)($decision['feature']??''),!empty($decision['allowed']),(string)($decision['code']??''),['minor_policy'=>true]);
        }
    }catch(Throwable $e){if(defined('APP_DEBUG')&&APP_DEBUG)error_log('[AURA-MINOR-POLICY] '.$e->getMessage());}
}

function afMinorPolicyRequire(PDO $pdo, string $feature, array $context = []): void
{
    $uid=(int)($_SESSION['usuario_id']??0);
    if($uid<=0){header('Location: '.SITE_URL.'/auth/login.php');exit;}
    $decision=afMinorPolicyDecision($pdo,$uid,$feature,$context,true);
    if(!empty($decision['allowed']))return;
    $_SESSION['af_minor_policy_last_decision']=$decision;
    header('Location: '.SITE_URL.'/pages/restricao-idade.php?feature='.rawurlencode(afMinorPolicyFeatureAlias($feature)));exit;
}

function afMinorPolicyApiAssert(PDO $pdo, array $usuario, string $feature, array $context = []): void
{
    $decision=afMinorPolicyDecision($pdo,(int)($usuario['id']??0),$feature,$context,true);
    if(!empty($decision['allowed']))return;
    jsonResponse(['ok'=>false,'code'=>$decision['code'],'erro'=>$decision['message'],'policy'=>$decision],403);
}

function afMinorPolicyCanExposePublicContent(PDO $pdo, int $usuarioId, string $feature = 'full_public_profile'): bool
{
    return !empty(afMinorPolicyDecision($pdo,$usuarioId,$feature,[],false)['allowed']);
}

function afMinorPolicyCanAppearInRanking(PDO $pdo, int $usuarioId): bool
{
    return !empty(afMinorPolicyDecision($pdo,$usuarioId,'ranking_alias',[],false)['allowed']);
}

function afMinorPolicyProtectRankingItem(array $item): array
{
    if(($item['tipo_conta']??'')==='minor' || (isset($item['idade_declarada']) && (int)$item['idade_declarada']<18)){
        $item['nome']=(string)($item['username']??'Farmer protegido');
        $item['foto_perfil']=null;
        $item['foto_externa']=null;
        $item['cidade']='';$item['estado']='';$item['pais']='';
        $item['minor_protected']=true;
        $item['identity_mode']='alias_only';
    }else{
        $item['minor_protected']=false;
        $item['identity_mode']='standard';
    }
    return $item;
}

function afMinorPolicyRankingEligibilitySql(string $ageAlias = 'ag', string $userAlias = 'u'): string
{
    $minAge=16;
    return "(
        {$ageAlias}.status_idade IN ('adult_verified','guardian_verified')
        OR (
            {$ageAlias}.tipo_conta='minor'
            AND {$ageAlias}.status_idade='minor_authorized'
            AND {$ageAlias}.idade_declarada>={$minAge}
            AND EXISTS (
                SELECT 1
                FROM af_guardian_minor_links mpl
                JOIN af_guardian_accounts mga ON mga.usuario_id=mpl.guardian_usuario_id AND mga.status_verificacao='verified'
                JOIN af_guardian_manual_reviews mgr ON mgr.guardian_account_id=mga.id AND mgr.status='approved' AND (mgr.valid_until IS NULL OR mgr.valid_until>NOW())
                JOIN af_parental_consents mpc ON mpc.link_id=mpl.id AND mpc.status='active'
                JOIN af_consent_versions mcv ON mcv.id=mpc.consent_version_id AND mcv.ativo=1
                    AND mcv.version_key=(SELECT valor FROM af_configuracoes WHERE chave='parental_consent_current_version' LIMIT 1)
                JOIN af_consent_permissions mpp ON mpp.consent_id=mpc.id
                    AND mpp.permission_key='ranking_alias' AND mpp.permitido=1 AND mpp.revogado_em IS NULL
                JOIN af_guardian_controls mgc ON mgc.link_id=mpl.id AND mgc.access_status='active' AND mgc.ranking_enabled=1
                LEFT JOIN af_feature_permissions mfp ON mfp.minor_usuario_id={$userAlias}.id AND mfp.feature_key='ranking_alias'
                WHERE mpl.minor_usuario_id={$userAlias}.id AND mpl.status='active' AND mpl.family_confirmed_at IS NOT NULL AND COALESCE(mfp.allowed,1)=1
            )
        )
    )";
}

function afMinorPolicySummary(PDO $pdo, int $usuarioId, bool $persist = true): array
{
    $ctx=afMinorPolicyContext($pdo,$usuarioId);
    $features=[];$allowed=[];$blocked=[];
    foreach(afMinorPolicyDefinitions() as $key=>$definition){
        $decision=afMinorPolicyDecision($pdo,$usuarioId,$key,[],false);
        $features[$key]=$decision;
        if($decision['allowed'])$allowed[]=$key;else$blocked[]=$key;
    }
    $status=!$ctx['age']['completo']?'age_pending':(!$ctx['is_minor']?'adult':(
        in_array($ctx['age_band'],['child_under_13','teen_13_15'],true)?'controlled_event_only':(
            !$ctx['guardian_linked']?'guardian_required':(
                !$ctx['guardian_verified']?'guardian_verification_required':(
                    !$ctx['consent_active']?'consent_required':'protected'
                )
            )
        )
    ));
    $summary=[
        'version'=>(string)(afMinorPolicyCurrentVersion($pdo)['version_key']??'1.0'),
        'status'=>$status,
        'is_minor'=>(bool)$ctx['is_minor'],
        'age_band'=>$ctx['age_band'],
        'age_band_label'=>afMinorPolicyAgeBandLabel($ctx['age_band']),
        'guardian_linked'=>(bool)$ctx['guardian_linked'],
        'guardian_verified'=>(bool)$ctx['guardian_verified'],
        'family_confirmed'=>(bool)($ctx['family_confirmed']??false),
        'consent_active'=>(bool)$ctx['consent_active'],
        'consent_current'=>(bool)$ctx['consent_current'],
        'allowed_features'=>$allowed,
        'blocked_features'=>$blocked,
        'features'=>$features,
        'safe_camera_ready'=>filter_var(afMinorPolicyConfig($pdo,'minor_safe_camera_ready',false),FILTER_VALIDATE_BOOLEAN),
        'evaluated_at'=>date(DATE_ATOM),
    ];
    if($persist && $ctx['is_minor'])afMinorPolicyPersistSnapshot($pdo,$usuarioId,$summary,$ctx);
    return $summary;
}

function afMinorPolicyPersistSnapshot(PDO $pdo, int $usuarioId, array $summary, array $ctx): void
{
    try{
        $linkId=(int)($ctx['guardian_link']['id']??0);
        $consentId=(int)($ctx['consent']['consent']['id']??0);
        $pdo->prepare('INSERT INTO af_minor_policy_snapshots
            (minor_usuario_id,policy_version,age_band,policy_status,guardian_link_id,consent_id,allowed_features_json,blocked_features_json,avaliado_em,atualizado_em)
            VALUES (?,?,?,?,?,?,?,?,NOW(),NOW())
            ON DUPLICATE KEY UPDATE policy_version=VALUES(policy_version),age_band=VALUES(age_band),policy_status=VALUES(policy_status),guardian_link_id=VALUES(guardian_link_id),consent_id=VALUES(consent_id),allowed_features_json=VALUES(allowed_features_json),blocked_features_json=VALUES(blocked_features_json),avaliado_em=NOW(),atualizado_em=NOW()')->execute([
                $usuarioId,(string)$summary['version'],(string)$summary['age_band'],(string)$summary['status'],
                $linkId?:null,$consentId?:null,
                json_encode($summary['allowed_features'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
                json_encode($summary['blocked_features'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)
            ]);
    }catch(Throwable $e){if(defined('APP_DEBUG')&&APP_DEBUG)error_log('[AURA-MINOR-SNAPSHOT] '.$e->getMessage());}
}

function afMinorPolicyApiSummary(array $summary): array
{
    $features=[];
    foreach($summary['features']??[] as $key=>$decision){
        $features[$key]=[
            'allowed'=>(bool)($decision['allowed']??false),
            'code'=>(string)($decision['code']??''),
            'message'=>(string)($decision['message']??''),
            'mode'=>(string)($decision['mode']??'blocked'),
        ];
    }
    return [
        'version'=>$summary['version']??'1.0',
        'status'=>$summary['status']??'unknown',
        'is_minor'=>(bool)($summary['is_minor']??false),
        'age_band'=>$summary['age_band']??'unverified',
        'guardian_linked'=>(bool)($summary['guardian_linked']??false),
        'guardian_verified'=>(bool)($summary['guardian_verified']??false),
        'family_confirmed'=>(bool)($summary['family_confirmed']??false),
        'consent_active'=>(bool)($summary['consent_active']??false),
        'consent_current'=>(bool)($summary['consent_current']??false),
        'safe_camera_ready'=>(bool)($summary['safe_camera_ready']??false),
        'features'=>$features,
        'evaluated_at'=>$summary['evaluated_at']??null,
    ];
}
