using System.Collections.Concurrent;
using Dalamud.Plugin;
using LightlessSync.Interop.Ipc.Framework;
using LightlessSync.PlayerData.Handlers;
using LightlessSync.Services;
using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Logging;
using Penumbra.Api.Helpers;
using Penumbra.Api.IpcSubscribers;

namespace LightlessSync.Interop.Ipc.Penumbra;

public sealed class PenumbraResource : PenumbraBase
{
    private readonly ActorObjectService _actorObjectService;
    private readonly GetGameObjectResourcePaths _gameObjectResourcePaths;
    private readonly ResolvePlayerPathsAsync _resolvePlayerPaths;
    private readonly GetPlayerMetaManipulations _getPlayerMetaManipulations;
    private readonly EventSubscriber<nint, string, string> _gameObjectResourcePathResolved;
    private readonly ConcurrentDictionary<IntPtr, byte> _trackedActors = new();

    public PenumbraResource(
        ILogger logger,
        IDalamudPluginInterface pluginInterface,
        DalamudUtilService dalamudUtil,
        LightlessMediator mediator,
        ActorObjectService actorObjectService) : base(logger, pluginInterface, dalamudUtil, mediator)
    {
        _actorObjectService = actorObjectService;
        _gameObjectResourcePaths = new GetGameObjectResourcePaths(pluginInterface);
        _resolvePlayerPaths = new ResolvePlayerPathsAsync(pluginInterface);
        _getPlayerMetaManipulations = new GetPlayerMetaManipulations(pluginInterface);
        _gameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pluginInterface, HandleResourceLoaded);

        foreach (var descriptor in _actorObjectService.PlayerDescriptors)
        {
            TrackActor(descriptor.Address);
        }
    }

    public override string Name => "Penumbra.Resources";

    public async Task<Dictionary<string, HashSet<string>>?> GetCharacterDataAsync(ILogger logger, GameObjectHandler handler)
    {
        if (!IsAvailable)
        {
            return null;
        }

        return await DalamudUtil.RunOnFrameworkThread(() =>
        {
            logger.LogTrace("Calling On IPC: Penumbra.GetGameObjectResourcePaths");
            var idx = handler.GetGameObject()?.ObjectIndex;
            if (idx == null)
            {
                return null;
            }

            return _gameObjectResourcePaths.Invoke(idx.Value)[0];
        }).ConfigureAwait(false);
    }

    public string GetMetaManipulations()
        => IsAvailable ? _getPlayerMetaManipulations.Invoke() : string.Empty;

    public async Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forwardPaths, string[] reversePaths)
    {
        if (!IsAvailable)
        {
            return (Array.Empty<string>(), Array.Empty<string[]>());
        }

        return await _resolvePlayerPaths.Invoke(forwardPaths, reversePaths).ConfigureAwait(false);
    }

    public void TrackActor(nint address)
    {
        if (address != nint.Zero)
        {
            _trackedActors[(IntPtr)address] = 0;
        }
    }

    public void UntrackActor(nint address)
    {
        if (address != nint.Zero)
        {
            _trackedActors.TryRemove((IntPtr)address, out _);
        }
    }

    private void HandleResourceLoaded(nint ptr, string resolvedPath, string gamePath)
    {
        if (ptr == nint.Zero)
        {
            return;
        }

        if (!_trackedActors.ContainsKey(ptr))
        {
            var descriptor = _actorObjectService.PlayerDescriptors.FirstOrDefault(d => d.Address == ptr);
            if (descriptor.Address != nint.Zero)
            {
                _trackedActors[ptr] = 0;
            }
            else
            {
                return;
            }
        }

        if (string.Compare(resolvedPath, gamePath, StringComparison.OrdinalIgnoreCase) == 0)
        {
            return;
        }

        Mediator.Publish(new PenumbraResourceLoadMessage(ptr, resolvedPath, gamePath));
    }

    protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
    {
        if (current != IpcConnectionState.Available)
        {
            _trackedActors.Clear();
        }
        else
        {
            foreach (var descriptor in _actorObjectService.PlayerDescriptors)
            {
                TrackActor(descriptor.Address);
            }
        }
    }

    public override void Dispose()
    {
        base.Dispose();
        _gameObjectResourcePathResolved.Dispose();
    }
}
