using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CUE4Parse_Conversion.Textures;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Settings;
using FModel.Views.Snooper.Animations;
using FModel.Views.Snooper.Lights;
using FModel.Views.Snooper.Models;
using FModel.Views.Snooper.Shading;
using SkiaSharp;

namespace FModel.Views.Snooper;

public class Options
{
    public FGuid SelectedModel { get; private set; }
    public int SelectedSection { get; private set; }
    public int SelectedMorph { get; private set; }
    public int SelectedAnimation{ get; private set; }

    public readonly Dictionary<FGuid, IRenderableModel> Models;
    public readonly Dictionary<FGuid, Texture> Textures;
    public readonly List<Light> Lights;

    public readonly TimeTracker Tracker;
    public readonly List<Animation> Animations;

    public readonly Dictionary<string, Texture> Icons;

    private readonly string _game;

    public Options()
    {
        Models = new Dictionary<FGuid, IRenderableModel>();
        Textures = new Dictionary<FGuid, Texture>();
        Lights = new List<Light>();

        Tracker = new TimeTracker();
        Animations = new List<Animation>();

        Icons = new Dictionary<string, Texture>
        {
            ["material"] = new ("materialicon"),
            ["square"] = new ("square"),
            ["square_off"] = new ("square_off"),
            ["cube"] = new ("cube"),
            ["cube_off"] = new ("cube_off"),
            ["light"] = new ("light"),
            ["light_off"] = new ("light_off"),
            ["noimage"] = new ("T_Placeholder_Item_Image"),
            ["checker"] = new ("checker"),
            ["pointlight"] = new ("pointlight"),
            ["spotlight"] = new ("spotlight"),
            ["link_on"] = new ("link_on"),
            ["link_off"] = new ("link_off"),
            ["link_has"] = new ("link_has"),
            ["tl_play"] = new ("tl_play"),
            ["tl_pause"] = new ("tl_pause"),
            ["tl_rewind"] = new ("tl_rewind"),
            ["tl_forward"] = new ("tl_forward"),
            ["tl_previous"] = new ("tl_previous"),
            ["tl_next"] = new ("tl_next"),
        };

        _game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.ProjectName.ToUpper();

        SelectModel(Guid.Empty);
    }

    public void SetupModelsAndLights()
    {
        foreach (var model in Models.Values)
        {
            if (model.IsSetup) continue;
            model.Setup(this);
        }

        foreach (var light in Lights)
        {
            if (light.IsSetup) continue;
            light.Setup();
        }
    }

    public void SelectModel(FGuid guid)
    {
        // unselect old
        if (TryGetModel(out var model))
            model.IsSelected = false;

        // select new
        if (!TryGetModel(guid, out model))
            SelectedModel = Guid.Empty;
        else
        {
            model.IsSelected = true;
            SelectedModel = guid;
        }

        SelectedSection = 0;
        SelectedMorph = 0;
    }

    public void SelectAnimation(int animation)
    {
        SelectedAnimation = animation;
    }

    public void RemoveModel(FGuid guid)
    {
        if (!TryGetModel(guid, out var m) || m is null)
            return;

        DetachAndRemoveModels(m, true);
        m.Dispose();
        Models.Remove(guid);
    }

    private void DetachAndRemoveModels(IRenderableModel model, bool detach)
    {
        foreach (var socket in model.Sockets.ToList())
        {
            foreach (var info in socket.AttachedModels)
            {
                if (!TryGetModel(info.Guid, out var m) || m is null)
                    continue;

                var t = m.GetTransform();
                if (m.IsProp)
                {
                    m.Attachments.SafeDetach(model, t);
                    RemoveModel(info.Guid);
                }
                else if (detach) m.Attachments.SafeDetach(model, t);
            }

            if (socket.IsVirtual)
            {
                socket.Dispose();
                model.Sockets.Remove(socket);
            }
        }
    }

    public void AddAnimation(Animation animation)
    {
        Animations.Add(animation);
    }

    public void RemoveAnimations()
    {
        Tracker.Reset();
        SelectedAnimation = 0;

        foreach (var animation in Animations)
        {
            foreach (var guid in animation.AttachedModels)
            {
                if (!TryGetModel(guid, out var model) || model is not SkeletalModel animatedModel)
                    continue;

                animatedModel.Skeleton.ResetAnimatedData(true);
                DetachAndRemoveModels(animatedModel, false);
            }

            animation.Dispose();
        }

        foreach (var kvp in Models)
            if (kvp.Value.IsProp)
                RemoveModel(kvp.Key);

        Animations.Clear();
    }

    public void SelectSection(int index)
    {
        SelectedSection = index;
    }

    public void SelectMorph(int index, SkeletalModel model)
    {
        SelectedMorph = index;
        model.UpdateMorph(SelectedMorph);
    }

    public bool TryGetTexture(UTexture o, bool fix, out Texture texture)
    {
        var guid = o.LightingGuid;
        if (Textures.TryGetValue(guid, out texture)) return texture != null;
        if (o.Format == EPixelFormat.PF_BC6H) return false; // BC6H is not supported by Decode thus randomly crashes the app

        var bitmap = o switch
        {
            UTexture2D texture2D => texture2D.Decode(UserSettings.Default.PreviewMaxTextureSize, UserSettings.Default.CurrentDir.TexturePlatform),
            UTexture2DArray texture2DArray => texture2DArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform)?.FirstOrDefault(),
            _ => o.Decode(UserSettings.Default.CurrentDir.TexturePlatform)
        };

        if (bitmap is not null)
        {
            texture = new Texture(bitmap.ToSkBitmap(), o);
            if (fix) TextureHelper.FixChannels(_game, texture);
            Textures[guid] = texture;
        }

        return texture != null;
    }

    public bool TryGetModel([MaybeNullWhen(false)] out IRenderableModel model) => Models.TryGetValue(SelectedModel, out model);
    public bool TryGetModel(FGuid guid, [MaybeNullWhen(false)] out IRenderableModel model) => Models.TryGetValue(guid, out model);

    public bool TryGetSection(out Section section) => TryGetSection(SelectedModel, out section);
    public bool TryGetSection(FGuid guid, out Section section)
    {
        if (TryGetModel(guid, out var model))
        {
            return TryGetSection(model, out section);
        }

        section = null;
        return false;
    }
    public bool TryGetSection(IRenderableModel model, out Section section)
    {
        if (SelectedSection >= 0 && SelectedSection < model.Sections.Length)
            section = model.Sections[SelectedSection]; else section = null;
        return section != null;
    }

    public void SwapMaterial(bool value)
    {
        Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsOverwritingMaterial = value;
    }

    public void AnimateMesh(bool value)
    {
        Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation = value;
    }

    /// <summary>
    /// Skip emissive for specific games, cause of excessive use in their materials
    /// </summary>
    public bool SkipEmissive()
    {
        return _game switch
        {
            "LIESOFP" or "CODEVEIN2" or "HIGHONLIFE2" or "MORTALSHELL2" => true,
            _ => false,
        };
    }

    public void ResetModelsLightsAnimations()
    {
        foreach (var model in Models.Values)
        {
            model.Dispose();
        }
        Models.Clear();
        Lights.Clear();
        Tracker.Reset();
        foreach (var animation in Animations)
        {
            animation.Dispose();
        }
        Animations.Clear();
    }

    public void Dispose()
    {
        ResetModelsLightsAnimations();
        foreach (var texture in Textures.Values)
        {
            texture.Dispose();
        }
        Textures.Clear();
        foreach (var texture in Icons.Values)
        {
            texture.Dispose();
        }
        Icons.Clear();
    }
}
