using Theriapolis.Core.Entities;

namespace Theriapolis.Core.World.Settlements;

/// <summary>
/// Phase 6 M1 — runtime map between symbolic ids used by quest scripts /
/// dialogue conditions and live world entities. Quest scripts never embed
/// world coordinates; they reference NPCs by role tag and locations by
/// anchor id (per master plan §8.4):
///
/// <code>
///   anchor:millhaven                  → the live Settlement
///   role:millhaven.innkeeper          → the live NpcActor for that named role
/// </code>
///
/// Built lazily as chunks stream in: when a settlement's buildings resolve
/// (and any named NPC instantiates), the entry registers here. Phase 6 M2
/// persists the registry; M1 rebuilds it on every load from the live
/// settlement list and active NpcActors.
/// </summary>
public sealed class AnchorRegistry
{
    private readonly Dictionary<string, int>      _anchorToSettlementId = new(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<string, int>      _roleToNpcId          = new(StringComparer.OrdinalIgnoreCase);

    /// <summary>Register a settlement under its anchor id (e.g. "anchor:millhaven").</summary>
    public void RegisterAnchor(NarrativeAnchor anchor, int settlementId)
    {
        string key = $"anchor:{anchor.ToString().ToLowerInvariant()}";
        _anchorToSettlementId[key] = settlementId;
    }

    /// <summary>Register an NpcActor under its named role tag (e.g. "role:millhaven.innkeeper").</summary>
    public void RegisterRole(string roleTag, int npcId)
    {
        if (string.IsNullOrEmpty(roleTag)) return;
        if (!roleTag.Contains('.')) return;  // generic role; only named (anchor.role) roles register
        _roleToNpcId[$"role:{roleTag.ToLowerInvariant()}"] = npcId;
    }

    /// <summary>Forget the role mapping (called on chunk evict / NPC despawn).</summary>
    public void UnregisterRole(string roleTag)
    {
        if (string.IsNullOrEmpty(roleTag)) return;
        _roleToNpcId.Remove($"role:{roleTag.ToLowerInvariant()}");
    }

    /// <summary>Resolve "anchor:millhaven" → SettlementId (or null when not registered yet).</summary>
    public int? ResolveAnchor(string id)
    {
        return _anchorToSettlementId.TryGetValue(id, out int sid) ? sid : null;
    }

    /// <summary>Resolve "role:millhaven.innkeeper" → NpcId (or null when not loaded / not yet streamed).</summary>
    public int? ResolveRole(string id)
    {
        return _roleToNpcId.TryGetValue(id, out int nid) ? nid : null;
    }

    /// <summary>Bulk re-register every settlement's anchor (e.g. after world load).</summary>
    public void RegisterAllAnchors(WorldState world)
    {
        foreach (var s in world.Settlements)
            if (s.Anchor is { } a)
                RegisterAnchor(a, s.Id);
    }

    /// <summary>For diagnostics: every (id → entityId) mapping currently held.</summary>
    public IReadOnlyDictionary<string, int> AllAnchors => _anchorToSettlementId;
    public IReadOnlyDictionary<string, int> AllRoles   => _roleToNpcId;

    public void Clear()
    {
        _anchorToSettlementId.Clear();
        _roleToNpcId.Clear();
    }
}
