using Dalamud.Plugin;
using LightlessSync.Interop.Ipc.Framework;
using LightlessSync.Interop.Ipc.Penumbra;
using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.PlayerData.Handlers;
using LightlessSync.Services;
using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Logging;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.Api.IpcSubscribers;

namespace LightlessSync.Interop.Ipc;

public sealed class IpcCallerPenumbra : IpcServiceBase
{
    private static readonly IpcServiceDescriptor PenumbraDescriptor = new("Penumbra", "Penumbra", new Version(1, 2, 0, 22));

    private readonly PenumbraCollections _collections;
    private readonly PenumbraResource _resources;
    private readonly PenumbraRedraw _redraw;
    private readonly PenumbraTexture _textures;

    private readonly GetEnabledState _penumbraEnabled;
    private readonly GetModDirectory _penumbraGetModDirectory;
    private readonly EventSubscriber _penumbraInit;
    private readonly EventSubscriber _penumbraDispose;
    private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _penumbraModSettingChanged;

    private bool _shownPenumbraUnavailable;
    private string? _modDirectory;

    public IpcCallerPenumbra(
        ILogger<IpcCallerPenumbra> logger,
        IDalamudPluginInterface pluginInterface,
        DalamudUtilService dalamudUtil,
        LightlessMediator mediator,
        RedrawManager redrawManager,
        ActorObjectService actorObjectService) : base(logger, mediator, pluginInterface, PenumbraDescriptor)
    {
        _penumbraEnabled = new GetEnabledState(pluginInterface);
        _penumbraGetModDirectory = new GetModDirectory(pluginInterface);
        _penumbraInit = Initialized.Subscriber(pluginInterface, HandlePenumbraInitialized);
        _penumbraDispose = Disposed.Subscriber(pluginInterface, HandlePenumbraDisposed);
        _penumbraModSettingChanged = ModSettingChanged.Subscriber(pluginInterface, HandlePenumbraModSettingChanged);

        _collections = RegisterInterop(new PenumbraCollections(logger, pluginInterface, dalamudUtil, mediator));
        _resources = RegisterInterop(new PenumbraResource(logger, pluginInterface, dalamudUtil, mediator, actorObjectService));
        _redraw = RegisterInterop(new PenumbraRedraw(logger, pluginInterface, dalamudUtil, mediator, redrawManager));
        _textures = RegisterInterop(new PenumbraTexture(logger, pluginInterface, dalamudUtil, mediator, _redraw));

        SubscribeMediatorEvents();

        CheckAPI();
        CheckModDirectory();
    }

    public string? ModDirectory
    {
        get => _modDirectory;
        private set
        {
            if (string.Equals(_modDirectory, value, StringComparison.Ordinal))
            {
                return;
            }

            _modDirectory = value;
            Mediator.Publish(new PenumbraDirectoryChangedMessage(_modDirectory));
        }
    }

    public Task AssignTemporaryCollectionAsync(ILogger logger, Guid collectionId, int objectIndex)
        => _collections.AssignTemporaryCollectionAsync(logger, collectionId, objectIndex);

    public Task<Guid> CreateTemporaryCollectionAsync(ILogger logger, string uid)
        => _collections.CreateTemporaryCollectionAsync(logger, uid);

    public Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collectionId)
        => _collections.RemoveTemporaryCollectionAsync(logger, applicationId, collectionId);

    public Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collectionId, Dictionary<string, string> modPaths)
        => _collections.SetTemporaryModsAsync(logger, applicationId, collectionId, modPaths);

    public Task SetManipulationDataAsync(ILogger logger, Guid applicationId, Guid collectionId, string manipulationData)
        => _collections.SetManipulationDataAsync(logger, applicationId, collectionId, manipulationData);

    public Task<Dictionary<string, HashSet<string>>?> GetCharacterData(ILogger logger, GameObjectHandler handler)
        => _resources.GetCharacterDataAsync(logger, handler);

    public string GetMetaManipulations()
        => _resources.GetMetaManipulations();

    public Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forward, string[] reverse)
        => _resources.ResolvePathsAsync(forward, reverse);

    public string ResolveGameObjectPath(string gamePath, int objectIndex)
        => _resources.ResolveGameObjectPath(gamePath, objectIndex);

    public string[] ReverseResolveGameObjectPath(string moddedPath, int objectIndex)
        => _resources.ReverseResolveGameObjectPath(moddedPath, objectIndex);

    public Task RedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token)
        => _redraw.RedrawAsync(logger, handler, applicationId, token);

    public Task ConvertTextureFiles(ILogger logger, IReadOnlyList<TextureConversionJob> jobs, IProgress<TextureConversionProgress>? progress, CancellationToken token)
        => _textures.ConvertTextureFilesAsync(logger, jobs, progress, token);

    public Task ConvertTextureFileDirectAsync(TextureConversionJob job, CancellationToken token)
        => _textures.ConvertTextureFileDirectAsync(job, token);

    public void CheckModDirectory()
    {
        if (!APIAvailable)
        {
            ModDirectory = string.Empty;
            return;
        }

        try
        {
            ModDirectory = _penumbraGetModDirectory.Invoke().ToLowerInvariant();
        }
        catch (Exception ex)
        {
            Logger.LogWarning(ex, "Failed to resolve Penumbra mod directory");
        }
    }

    protected override bool IsPluginEnabled(IExposedPlugin plugin)
    {
        try
        {
            return _penumbraEnabled.Invoke();
        }
        catch
        {
            return false;
        }
    }

    protected override void OnConnectionStateChanged(IpcConnectionState previous, IpcConnectionState current)
    {
        base.OnConnectionStateChanged(previous, current);

        if (current == IpcConnectionState.Available)
        {
            _shownPenumbraUnavailable = false;
            if (string.IsNullOrEmpty(ModDirectory))
            {
                CheckModDirectory();
            }
            return;
        }

        ModDirectory = string.Empty;
        _redraw.CancelPendingRedraws();

        if (_shownPenumbraUnavailable || current == IpcConnectionState.Unknown)
        {
            return;
        }

        _shownPenumbraUnavailable = true;
        Mediator.Publish(new NotificationMessage(
            "Penumbra inactive",
            "Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Lightless. If you just updated Penumbra, ignore this message.",
            NotificationType.Error));
    }

    private void SubscribeMediatorEvents()
    {
        Mediator.Subscribe<PenumbraRedrawCharacterMessage>(this, msg =>
        {
            _redraw.RequestImmediateRedraw(msg.Character.ObjectIndex, RedrawType.AfterGPose);
        });

        Mediator.Subscribe<DalamudLoginMessage>(this, _ => _shownPenumbraUnavailable = false);
    }

    private void HandlePenumbraInitialized()
    {
        Mediator.Publish(new PenumbraInitializedMessage());
        CheckModDirectory();
        _redraw.RequestImmediateRedraw(0, RedrawType.Redraw);
        CheckAPI();
    }

    private void HandlePenumbraDisposed()
    {
        _redraw.CancelPendingRedraws();
        ModDirectory = string.Empty;
        Mediator.Publish(new PenumbraDisposedMessage());
        CheckAPI();
    }

    private void HandlePenumbraModSettingChanged(ModSettingChange change, Guid _, string __, bool ___)
    {
        if (change == ModSettingChange.EnableState)
        {
            Mediator.Publish(new PenumbraModSettingChangedMessage());
            CheckAPI();
        }
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        if (!disposing)
        {
            return;
        }

        _penumbraModSettingChanged.Dispose();
        _penumbraDispose.Dispose();
        _penumbraInit.Dispose();
    }
}
