using Dalamud.Plugin.Ipc;
using Microsoft.Extensions.Logging;

namespace HellionChat.Ipc;

public sealed class ExtraChat : IDisposable
{
    private readonly ILogger<ExtraChat> _logger;

#pragma warning disable CS0649 // Assigned through IPC
    [Serializable]
    private struct OverrideInfo
    {
        public string? Channel;
        public ushort UiColour;
        public uint Rgba;
    }
#pragma warning restore CS0649

    private ICallGateSubscriber<OverrideInfo, object> OverrideChannelGate { get; }
    private ICallGateSubscriber<
        Dictionary<string, uint>,
        Dictionary<string, uint>
    > ChannelCommandColoursGate { get; }
    private ICallGateSubscriber<
        Dictionary<Guid, string>,
        Dictionary<Guid, string>
    > ChannelNamesGate { get; }

    internal (string, uint)? ChannelOverride { get; set; }

    // volatile: IPC callbacks fire on a Dalamud thread while ImGui reads these.
    // Reference assignment is atomic on x64, but the barrier ensures visibility
    // across threads (especially Mono/Wine). See AUDIT-2026-05-05 [SEC-01].
    private volatile Dictionary<string, uint> ChannelCommandColoursInternal = new();
    internal IReadOnlyDictionary<string, uint> ChannelCommandColours =>
        ChannelCommandColoursInternal;

    private volatile Dictionary<Guid, string> ChannelNamesInternal = new();
    internal IReadOnlyDictionary<Guid, string> ChannelNames => ChannelNamesInternal;

    internal ExtraChat(ILogger<ExtraChat> logger)
    {
        _logger = logger;
        OverrideChannelGate = Plugin.Interface.GetIpcSubscriber<OverrideInfo, object>(
            "ExtraChat.OverrideChannelColour"
        );
        ChannelCommandColoursGate = Plugin.Interface.GetIpcSubscriber<
            Dictionary<string, uint>,
            Dictionary<string, uint>
        >("ExtraChat.ChannelCommandColours");
        ChannelNamesGate = Plugin.Interface.GetIpcSubscriber<
            Dictionary<Guid, string>,
            Dictionary<Guid, string>
        >("ExtraChat.ChannelNames");

        OverrideChannelGate.Subscribe(OnOverrideChannel);
        ChannelCommandColoursGate.Subscribe(OnChannelCommandColours);
        ChannelNamesGate.Subscribe(OnChannelNames);

        try
        {
            ChannelCommandColoursInternal = ChannelCommandColoursGate.InvokeFunc(null!);
            ChannelNamesInternal = ChannelNamesGate.InvokeFunc(null!);
        }
        catch (Exception ex)
        {
            // ExtraChat is optional; IPC failure is normal when the plugin isn't loaded.
            _logger.LogTrace(ex, "ExtraChat IPC initial state query failed (peer not loaded?)");
        }
    }

    public void Dispose()
    {
        OverrideChannelGate.Unsubscribe(OnOverrideChannel);
        ChannelCommandColoursGate.Unsubscribe(OnChannelCommandColours);
        ChannelNamesGate.Unsubscribe(OnChannelNames);
    }

    private void OnOverrideChannel(OverrideInfo info)
    {
        ChannelOverride = info.Channel == null ? null : (info.Channel, info.Rgba);
    }

    private void OnChannelCommandColours(Dictionary<string, uint> obj) =>
        ChannelCommandColoursInternal = obj;

    private void OnChannelNames(Dictionary<Guid, string> obj) => ChannelNamesInternal = obj;
}
