GameLogic/GameManager.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;

public enum GamePhase
{
	BufferZone,      // Entraînement / Préparation, timer masqué
	RoundIntro,      // Portes qui s'ouvrent, gladiateurs qui entrent
	RoundActive,     // Combat en cours, timer actif
	AwaitingVerdict, // Joueur au sol / mort, décision de l'Editor
	RoundRecap,      // Fin de manche, stats à l'écran
	GameOver,        // Exécution ou défaite définitive
	Victory          // Survie au terme des manches ou conquête du public (1 000 fans)
}

public sealed class GameManager : Component
{
	public static GameManager Instance { get; private set; }

	[Property, Group( "Liaisons" )] public PlayerController Player { get; set; }
	[Property, Group( "Liaisons" )] public ArenaDirector Director { get; set; }
	[Property, Group( "Liaisons" )] public ArenaDoor PlayerArenaDoor { get; set; }
	[Property, Group( "Liaisons" )] public GameObject PlayerSpawnBuffer { get; set; }

	[Property, Group( "Manches" )] public List<RoundConfig> Rounds { get; set; } = new();
	[Property, Group( "Manches" )] public int CurrentRoundIndex { get; private set; } = 0;

	// =========================================================================
	// SYSTÈME DE PARTISANS (FANS)
	// =========================================================================
	public const int TargetFans = 1000;
	public int TotalFans { get; private set; } = 0;
	public int LastRoundFanGain { get; private set; } = 0;

	public GamePhase CurrentPhase { get; private set; } = GamePhase.BufferZone;
	public float TimeRemaining { get; private set; } = 0f;
	public RoundConfig CurrentRound => (CurrentRoundIndex >= 0 && CurrentRoundIndex < Rounds.Count) ? Rounds[CurrentRoundIndex] : null;

	public static event Action<GamePhase> OnPhaseChanged;
	public static event Action<int> OnRoundChanged;

	private TimeSince _timeSinceLastLog = 0f;

	protected override void OnAwake()
	{
		Instance = this;
		Player ??= Scene.GetAllComponents<PlayerController>().FirstOrDefault();
	}

	protected override void OnStart()
	{
		var save = SaveManager.Load();
		if ( save != null )
		{
			CurrentRoundIndex = save.CurrentRoundIndex;
			Director?.RestoreRivals( save.Rivals );
			RestorePlayerData( save );
		}

		StartBufferZonePhase();
	}

	protected override void OnUpdate()
	{
		switch ( CurrentPhase )
		{
			case GamePhase.RoundActive:
				UpdateRoundTimer();
				CheckCombatConditions();
				MonitorCombatConsole();
				break;

			case GamePhase.RoundRecap:
				if ( Input.Pressed( "jump" ) || Input.Pressed( "use" ) )
				{
					Log.Info( "⏩ [INPUT] Validation du récapitulatif !" );
					AdvanceToNextRoundOrBuffer();
				}
				break;
		}
	}

	[Broadcast]
	public async void TriggerEnterArena()
	{
		if ( CurrentPhase != GamePhase.BufferZone ) return;

		if ( CurrentRound == null )
		{
			Log.Error( $"❌ [GAME MANAGER] Impossible d'entrer en arène : Aucune manche configurée pour l'index {CurrentRoundIndex} !" );
			return;
		}

		ChangePhase( GamePhase.RoundIntro );
		Director?.InitializeRound( CurrentRound );

		await Task.Delay( 1500 );
		if ( PlayerArenaDoor != null )
			await PlayerArenaDoor.OpenDoorAsync( autoCloseAfterSeconds: 6.0f );

		TimeRemaining = CurrentRound?.RoundDuration ?? 240f;
		ChangePhase( GamePhase.RoundActive );
	}

	private void UpdateRoundTimer()
	{
		TimeRemaining -= Time.Delta;
		if ( TimeRemaining <= 0f )
		{
			TimeRemaining = 0f;
			Log.Info( "⏰ [GONG] Temps écoulé ! Fin de la manche." );
			CompleteRound( playerWon: true );
		}
	}

	private void CheckCombatConditions()
	{
		if ( Director != null && Director.IsWaveCleared )
		{
			Log.Info( "🏆 [CARNAGE] Tous les gladiateurs de la manche ont été éliminés !" );
			CompleteRound( playerWon: true );
		}
	}

	private void MonitorCombatConsole()
	{
		if ( _timeSinceLastLog >= 15.0f )
		{
			_timeSinceLastLog = 0f;
			var aura = Player?.Components.Get<FighterAura>()?.CurrentAura ?? 0f;
			Log.Info( $"⏱️ [COMBAT] Temps restant: {TimeRemaining:F0}s | Ennemis: {Director?.LivingEnemiesCount} (Quota: {Director?.RemainingQuota}) | Aura: {aura:F0}% | Fans: {TotalFans}/{TargetFans}" );
		}
	}

	public void HandlePlayerKnockedDownOrKilled( Fighter playerFighter )
	{
		if ( CurrentPhase != GamePhase.RoundActive ) return;

		ChangePhase( GamePhase.AwaitingVerdict );
		CrowdManager.Instance?.EvaluateEditorVerdict( playerFighter, ( spared ) =>
		{
			if ( spared )
			{
				Log.Info( "🏛️ L'Editor accorde sa grâce !" );
				CompleteRound( playerWon: false );
			}
			else
			{
				Log.Info( "💀 L'Editor ordonne l'exécution !" );
				SaveManager.ClearSave();
				ChangePhase( GamePhase.GameOver );
			}
		} );
	}

	public void CompleteRound( bool playerWon )
	{
		Director?.StopSpawningAndPacify();

		// Calcul de conversion d'aura cachée (0% -> -100 fans | 100% -> +300 fans)
		var aura = Player?.Components.Get<FighterAura>()?.CurrentAura ?? 0f;
		LastRoundFanGain = (int)MathF.Round( MathX.Lerp( -100f, 300f, Math.Clamp( aura / 100f, 0f, 1f ) ) );
		TotalFans = Math.Clamp( TotalFans + LastRoundFanGain, 0, TargetFans );

		ChangePhase( GamePhase.RoundRecap );

		Log.Info( "==========================================================" );
		Log.Info( $"📊 [RÉCAP MANCHE {CurrentRoundIndex + 1}] {(playerWon ? "VICTOIRE" : "GRÂCE ACCORDÉE")}" );
		Log.Info( $"🏛️ [FANS] Gain : {(LastRoundFanGain >= 0 ? "+" : "")}{LastRoundFanGain} | Total : {TotalFans}/{TargetFans}" );
		Log.Info( "👉 Appuie sur [ESPACE] ou [E] pour retourner au camp d'entraînement." );
		Log.Info( "==========================================================" );
	}

	public void AdvanceToNextRoundOrBuffer()
	{
		Director?.ClearRemainingEnemies();
		CurrentRoundIndex++;

		// Victoire si le joueur a conquis Rome (1 000 fans) OU terminé toutes les manches
		if ( TotalFans >= TargetFans || CurrentRoundIndex >= Rounds.Count )
		{
			SaveManager.ClearSave();
			ChangePhase( GamePhase.Victory );
			Log.Info( "👑 [VICTOIRE FINALE] Rome est à vos pieds !" );
			return;
		}

		OnRoundChanged?.Invoke( CurrentRoundIndex + 1 );
		TeleportPlayerToBuffer();
		StartBufferZonePhase();
	}

	private void StartBufferZonePhase()
	{
		ChangePhase( GamePhase.BufferZone );
		PlayerArenaDoor?.CloseDoor();

		var fighter = Player?.Components.Get<Fighter>();
		if ( fighter != null && !fighter.IsDead )
		{
			fighter.CurrentBalance = fighter.MaxBalance;
		}

		Log.Info( $"🛡️ [CAMP D'ENTRAÎNEMENT] Préparation Manche {CurrentRoundIndex + 1}. Fans actuels : {TotalFans}/{TargetFans}." );
		SaveCurrentProgress();
	}

	public void ResetCampaignProgression()
	{
		TotalFans = 0;
		LastRoundFanGain = 0;
		CurrentRoundIndex = 0;

		var fighter = Player?.Components.Get<Fighter>();
		if ( fighter != null )
		{
			fighter.MaxHealth = 100f;
			fighter.CurrentHealth = 100f;
			fighter.MaxBalance = 100f;
			fighter.CurrentBalance = 100f;
		}

		var aura = Player?.Components.Get<FighterAura>();
		if ( aura != null )
		{
			aura.ApplyAuraDelta( aura.StartingAura - aura.CurrentAura );
		}
	}

	public void SaveCurrentProgress()
	{
		if ( Player == null ) Player = Scene.GetAllComponents<PlayerController>().FirstOrDefault();

		var fighter = Player?.Components.Get<Fighter>();
		var aura = Player?.Components.Get<FighterAura>();

		var save = new CampaignSaveData
		{
			CurrentRoundIndex = CurrentRoundIndex,
			PlayerAura = aura?.CurrentAura ?? 20f,
			PlayerMaxHealth = fighter?.MaxHealth ?? 100f,
			PlayerMaxBalance = fighter?.MaxBalance ?? 100f,
			TotalFans = TotalFans,
			Rivals = Director?.ExportSurvivingRivals() ?? new()
		};

		SaveManager.Save( save );
	}

	private void RestorePlayerData( CampaignSaveData save )
	{
		if ( Player == null ) Player = Scene.GetAllComponents<PlayerController>().FirstOrDefault();
		if ( Player == null ) return;

		TotalFans = save.TotalFans;

		var fighter = Player.Components.Get<Fighter>();
		if ( fighter != null )
		{
			fighter.MaxHealth = save.PlayerMaxHealth;
			fighter.CurrentHealth = save.PlayerMaxHealth;
			fighter.MaxBalance = save.PlayerMaxBalance;
			fighter.CurrentBalance = save.PlayerMaxBalance;
		}

		var aura = Player.Components.Get<FighterAura>();
		if ( aura != null )
		{
			float targetAura = save.PlayerAura <= 0f ? 20f : save.PlayerAura;
			aura.ApplyAuraDelta( targetAura - aura.CurrentAura );
		}
	}

	private void TeleportPlayerToBuffer()
	{
		if ( Player == null ) Player = Scene.GetAllComponents<PlayerController>().FirstOrDefault();
		if ( Player == null ) return;

		if ( PlayerSpawnBuffer == null )
		{
			Log.Warning( "⚠️ [GAME MANAGER] PlayerSpawnBuffer n'est pas assigné ! Le joueur n'a pas pu être téléporté." );
			return;
		}

		var movement = Player.Components.Get<FighterMovement>();
		if ( movement != null )
		{
			movement.WishVelocity = Vector3.Zero;
			movement.WantsSprint = false;
			movement.WantsCrouch = false;
		}

		var cc = Player.Components.Get<CharacterController>();
		if ( cc != null && cc.IsValid )
		{
			cc.Velocity = Vector3.Zero;
		}

		var rb = Player.Components.Get<Rigidbody>();
		if ( rb != null && rb.IsValid )
		{
			rb.Velocity = Vector3.Zero;
			rb.AngularVelocity = Vector3.Zero;
		}

		// Élévation de sécurité de 10 unités pour ne pas fusionner avec le collider du sol
		Player.WorldPosition = PlayerSpawnBuffer.WorldPosition + Vector3.Up * 10.0f;
		Player.WorldRotation = PlayerSpawnBuffer.WorldRotation;

		if ( cc != null && cc.IsValid )
		{
			cc.Punch( Vector3.Down * 0.1f );
		}
	}

	private void ChangePhase( GamePhase newPhase )
	{
		CurrentPhase = newPhase;
		OnPhaseChanged?.Invoke( newPhase );
	}

	public void StartCustomRound( RoundConfig customRound )
	{
		if ( customRound == null ) return;

		if ( (customRound.EnemyPrefabs == null || customRound.EnemyPrefabs.Count == 0) && Rounds.Count > 0 )
		{
			customRound.EnemyPrefabs = Rounds[0].EnemyPrefabs;
		}

		Rounds = new List<RoundConfig> { customRound };
		CurrentRoundIndex = 0;

		Director?.ClearRemainingEnemies();
		TeleportPlayerToBuffer();
		StartBufferZonePhase();

		Log.Info( $"🎪 [FREE PLAY] Manche configurée : {customRound.TotalCombatants} ennemis au total ({customRound.MaxConcurrentFighters} max simultanés)." );
	}
}