using System;
namespace AcDream.App.Input;
///
/// Phase K.2 — one-shot guard that auto-enters player mode after a
/// successful login once every prerequisite is satisfied. Owned by
/// GameWindow and ticked each frame from OnUpdate.
///
///
/// Why is this its own class? The auto-entry has four independent
/// preconditions (live session reaches InWorld, the player
/// entity has been streamed into the world dictionary, the player
/// movement controller is constructible, and the terrain under the
/// spawn position has streamed in) plus a manual-override path
/// (the user can flip into fly mode before the auto-entry fires —
/// their choice wins). All five interact with each other in a way
/// that's painful to test through GameWindow but trivial here against
/// fakes.
///
///
///
/// The public surface is:
///
/// - — call after EnterWorld succeeds to
/// arm the entry trigger.
/// - — call when the user manually enters
/// fly mode (or any other code path that pre-empts the auto-entry).
/// - — call once per frame; runs the
/// guard and fires the entry callback when armed AND every
/// precondition is satisfied; returns true on the firing tick.
///
///
///
///
/// All preconditions are passed in as predicates so the class doesn't
/// pull in WorldSession, PlayerMovementController, or
/// any GameWindow-internal types — the unit test wires them to plain
/// boolean fields.
///
///
public sealed class PlayerModeAutoEntry
{
private readonly Func _isLiveInWorld;
private readonly Func _isPlayerEntityPresent;
private readonly Func _isPlayerControllerReady;
private readonly Func _isSpawnGroundReady;
private readonly Action _enterPlayerMode;
private bool _armed;
///
/// Build an auto-entry guard.
///
/// True iff the live session is in the
/// InWorld state. Skip auto-entry when the session is null
/// or hasn't reached InWorld yet.
/// True iff the player's
/// server-guid is already in the local entity dictionary (server
/// has streamed at least one CreateObject for the character).
/// True iff the per-frame
/// PlayerMovementController is set up. Stays true once player mode
/// is established; the auto-entry's job is to flip it from false
/// to true exactly once.
/// True iff the terrain under the
/// player's spawn position has streamed into the physics engine.
/// #106 gate-2 (2026-06-09): entering player mode earlier integrates
/// gravity against an empty world and free-falls the player into the
/// void. Retail never has this state — it loads cells synchronously;
/// this hold is the async-streaming equivalent of that invariant.
/// Action invoked on the firing
/// tick. The same routine the manual Tab handler invokes (fly →
/// player transition). Must construct the controller + chase
/// camera and switch the active camera; the auto-entry doesn't
/// reach inside.
public PlayerModeAutoEntry(
Func isLiveInWorld,
Func isPlayerEntityPresent,
Func isPlayerControllerReady,
Func isSpawnGroundReady,
Action enterPlayerMode)
{
_isLiveInWorld = isLiveInWorld ?? throw new ArgumentNullException(nameof(isLiveInWorld));
_isPlayerEntityPresent = isPlayerEntityPresent ?? throw new ArgumentNullException(nameof(isPlayerEntityPresent));
_isPlayerControllerReady = isPlayerControllerReady ?? throw new ArgumentNullException(nameof(isPlayerControllerReady));
_isSpawnGroundReady = isSpawnGroundReady ?? throw new ArgumentNullException(nameof(isSpawnGroundReady));
_enterPlayerMode = enterPlayerMode ?? throw new ArgumentNullException(nameof(enterPlayerMode));
}
/// True iff would still fire if the
/// preconditions become true. Flips false on a successful entry
/// (one-shot) or when is invoked.
public bool IsArmed => _armed;
///
/// Arm the trigger. Call after WorldSession.EnterWorld
/// returns successfully. Calling again while already armed is a
/// no-op.
///
public void Arm() => _armed = true;
///
/// Disarm the trigger without firing the callback. Call when the
/// user has manually entered fly mode (or any other code path
/// that pre-empts the auto-entry) — the user's choice wins.
///
public void Cancel() => _armed = false;
///
/// Guard tick. If the trigger is armed AND every precondition is
/// satisfied, invokes enterPlayerMode, disarms, and
/// returns true. Returns false otherwise (no side effects).
///
public bool TryEnter()
{
if (!_armed) return false;
if (!_isLiveInWorld()) return false;
if (!_isPlayerEntityPresent()) return false;
if (!_isPlayerControllerReady()) return false;
if (!_isSpawnGroundReady()) return false;
_armed = false;
_enterPlayerMode();
return true;
}
}