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

namespace HellionChat.Ipc;

using ChatInputState = (
    bool InputVisible,
    bool InputFocused,
    bool HasText,
    bool IsTyping,
    int TextLength,
    ChatType ChannelType
);

internal sealed class TypingIpc : IDisposable
{
    private Plugin Plugin { get; }

    private ICallGateProvider<ChatInputState> StateQueryGate { get; }
    private ICallGateProvider<ChatInputState, object?> StateChangedGate { get; }

    // v1.4.9 R4: ChatTwo IPC compatibility mirror. Some third-party plugins
    // have a no-fork policy and subscribe only to ChatTwo.*-prefixed IPC
    // gates. HellionChat replaces ChatTwo (conflict detection prevents
    // parallel loading), so mirroring the ChatTwo provider slots lets those
    // plugins keep working without code changes on their side. The tuple
    // shape is textually identical to ChatTwo's IPC surface (same member
    // order, same underlying types — ChatType is `ushort` in both repos)
    // so Dalamud's IPC marshalling matches across plugin boundaries.
    private ICallGateProvider<ChatInputState> ChatTwoStateQueryGate { get; }
    private ICallGateProvider<ChatInputState, object?> ChatTwoStateChangedGate { get; }

    private ChatInputState LastState;
    private bool HasState;

    private readonly ILogger<TypingIpc> _logger;

    internal TypingIpc(Plugin plugin, ILogger<TypingIpc> logger)
    {
        Plugin = plugin;
        _logger = logger;

        StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
            "HellionChat.GetChatInputState"
        );
        StateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>(
            "HellionChat.ChatInputStateChanged"
        );

        // v1.4.9 R4: ChatTwo-prefixed compatibility slots (see class-level comment).
        ChatTwoStateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
            "ChatTwo.GetChatInputState"
        );
        ChatTwoStateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>(
            "ChatTwo.ChatInputStateChanged"
        );

        StateQueryGate.RegisterFunc(GetState);
        ChatTwoStateQueryGate.RegisterFunc(GetState);
    }

    private ChatInputState BuildState()
    {
        var log = Plugin.ChatLogWindow;

        var usedChannel = Plugin.CurrentTab.CurrentChannel;
        var inputChannel = usedChannel.UseTempChannel
            ? usedChannel.TempChannel
            : usedChannel.Channel;
        var channelType = inputChannel.ToChatType();

        return (
            InputVisible: !log.IsHidden,
            log.InputFocused,
            HasText: log.Chat.Length > 0,
            IsTyping: log is { InputFocused: true, Chat.Length: > 0 },
            TextLength: log.Chat.Length,
            ChannelType: channelType
        );
    }

    private ChatInputState GetState() => BuildState();

    internal void Update()
    {
        var state = BuildState();
        if (HasState && state.Equals(LastState))
            return;

        HasState = true;
        LastState = state;
        StateChangedGate.SendMessage(state);
        // v1.4.9 R4: mirror on ChatTwo-prefixed slot for no-fork-policy plugins.
        ChatTwoStateChangedGate.SendMessage(state);
    }

    public void Dispose()
    {
        StateQueryGate.UnregisterFunc();
        ChatTwoStateQueryGate.UnregisterFunc();
    }
}
