namespace Theriapolis.Core.Entities;

/// <summary>
/// Owns live actors. Phase 4 only ever holds the single player actor; this
/// class exists so Phase 5/6 can add NPCs without further architectural change.
/// </summary>
public sealed class ActorManager
{
    private readonly List<Actor> _actors = new();
    private int _nextId = 1;

    public IReadOnlyList<Actor> All => _actors;

    public PlayerActor? Player { get; private set; }

    /// <summary>Creates the player actor. Idempotent on repeat calls — returns the existing player.</summary>
    public PlayerActor SpawnPlayer(Util.Vec2 worldPixelPos)
    {
        if (Player is not null) return Player;
        var p = new PlayerActor
        {
            Id         = _nextId++,
            Position   = worldPixelPos,
            Allegiance = Rules.Character.Allegiance.Player,
        };
        _actors.Add(p);
        Player = p;
        return p;
    }

    /// <summary>
    /// Phase 5 overload: spawn the player and attach a freshly-built character
    /// in one step. The character carries the player's name into the actor.
    /// </summary>
    public PlayerActor SpawnPlayer(Util.Vec2 worldPixelPos, Rules.Character.Character character)
    {
        var p = SpawnPlayer(worldPixelPos);
        p.Character = character;
        // The character record's identity (name) wins over the default actor name.
        // Other fields (HP, abilities, inventory) live exclusively on the Character.
        return p;
    }

    /// <summary>
    /// Restore a player actor from a save. Does not allocate a new id — the
    /// saved id is restored as-is so cross-save references stay stable.
    /// </summary>
    public PlayerActor RestorePlayer(PlayerActorState state)
    {
        var p = new PlayerActor { Id = state.Id };
        p.RestoreState(state);
        _actors.Add(p);
        Player = p;
        if (state.Id >= _nextId) _nextId = state.Id + 1;
        return p;
    }

    /// <summary>
    /// Phase 5 M5: spawn an NPC at the given world-pixel position. The caller
    /// chooses the template (including any DangerZone-driven per-zone selection).
    /// Returns the spawned actor with a freshly-allocated id.
    /// </summary>
    public NpcActor SpawnNpc(
        Data.NpcTemplateDef template,
        Util.Vec2 worldPixelPos,
        Tactical.ChunkCoord? sourceChunk = null,
        int? sourceSpawnIndex = null)
    {
        var npc = new NpcActor(template)
        {
            Id               = _nextId++,
            Position         = worldPixelPos,
            SourceChunk      = sourceChunk,
            SourceSpawnIndex = sourceSpawnIndex,
        };
        _actors.Add(npc);
        return npc;
    }

    /// <summary>
    /// Phase 6 M1 — adopt a pre-constructed NpcActor (e.g. a resident built
    /// by <see cref="Rules.Combat.ResidentInstantiator"/> with the resident
    /// template constructor). Assigns an id and registers in the actor list.
    /// </summary>
    public NpcActor SpawnNpc(NpcActor pre)
    {
        if (pre.Id <= 0)
        {
            // Use reflection-free init via a fresh actor copy. NpcActor.Id is
            // init-only, but the only place we hit this path is the
            // resident-instantiator which constructs `pre` with Id = -1
            // expecting us to assign. Build the real one here.
            NpcActor adopted = pre.Resident is not null
                ? new NpcActor(pre.Resident)
                {
                    Id               = _nextId++,
                    Position         = pre.Position,
                    SourceChunk      = pre.SourceChunk,
                    SourceSpawnIndex = pre.SourceSpawnIndex,
                    RoleTag          = pre.RoleTag,
                    HomeSettlementId = pre.HomeSettlementId,
                }
                : new NpcActor(pre.Template!)
                {
                    Id               = _nextId++,
                    Position         = pre.Position,
                    SourceChunk      = pre.SourceChunk,
                    SourceSpawnIndex = pre.SourceSpawnIndex,
                    RoleTag          = pre.RoleTag,
                    HomeSettlementId = pre.HomeSettlementId,
                };
            _actors.Add(adopted);
            return adopted;
        }
        _actors.Add(pre);
        if (pre.Id >= _nextId) _nextId = pre.Id + 1;
        return pre;
    }

    /// <summary>Remove an actor by id. Returns true if it was found.</summary>
    public bool RemoveActor(int id)
    {
        for (int i = 0; i < _actors.Count; i++)
        {
            if (_actors[i].Id == id) { _actors.RemoveAt(i); return true; }
        }
        return false;
    }

    /// <summary>Returns every NPC currently spawned.</summary>
    public IEnumerable<NpcActor> Npcs
    {
        get
        {
            foreach (var a in _actors) if (a is NpcActor npc) yield return npc;
        }
    }

    /// <summary>Find a live NPC originating from a specific chunk + spawn index.</summary>
    public NpcActor? FindNpcBySource(Tactical.ChunkCoord chunk, int spawnIndex)
    {
        foreach (var a in _actors)
            if (a is NpcActor npc &&
                npc.SourceChunk.HasValue && npc.SourceChunk.Value.Equals(chunk) &&
                npc.SourceSpawnIndex == spawnIndex)
                return npc;
        return null;
    }
}
