using System.Collections;
using ABI_RC.Core;
using ABI_RC.Core.IO;
using ABI_RC.Core.Networking.IO.Social;
using ABI_RC.Core.Player;
using ABI_RC.Core.Util;
using HarmonyLib;
using UnityEngine;

namespace NAK.AvatarQueueSystemTweaks.Patches;

// internal static class CVRObjectLoaderPatches
// {
//     private static readonly YieldInstruction _yieldInstruction = new WaitForSeconds(0.2f);
//     
//     [HarmonyPostfix]
//     [HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateAvatarFromBundle))]
//     [HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateSpawnableFromBundle))]
//     //[HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateAvatarFromExistingPrefab))]
//     //[HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateSpawnableFromExistingPrefab))]
//     private static IEnumerator MyWrapper(IEnumerator result)
//     {
//         while (result.MoveNext())
//             yield return result.Current;
//         
//         if (AvatarQueueSystemTweaksMod.EntryChokeInstantiation.Value)
//             yield return _yieldInstruction;
//     }
// }

internal static class AvatarQueueSystemPatches
{
    [HarmonyPrefix]
    [HarmonyPatch(typeof(AvatarQueueSystem.CoroutineHandle), nameof(AvatarQueueSystem.CoroutineHandle.RunManagedCoroutine))]
    private static bool Prefix_AvatarQueueSystem_CoroutineHandle_RunManagedCoroutine(AvatarQueueSystem.CoroutineHandle __instance)
    {
        AvatarQueueSystem.Instance.jobIsActive = true;
        
        // difference from original: doesn't immediately call CheckAvailability on same frame of completion
        
        __instance._activeCoroutine = AvatarQueueSystem.Instance.StartCoroutine(CoroutineUtil.RunThrowingIterator(__instance._function, delegate(Exception exception)
        {
            CommonTools.Log(CommonTools.LogLevelType_t.Error, string.Format("[CVRGame => {0}] Avatar loading job has failed!\n{1}", __instance.GetType().Name, exception), "A0108");
            __instance.CleanCurrentJob();
            var onException = __instance._onException;
            onException?.Invoke(exception);
        }, delegate
        {
            CommonTools.Log(CommonTools.LogLevelType_t.Info, "[CVRGame => " + __instance.GetType().Name + "] Avatar loading job has completed successfully.", "A0107");
            __instance.CleanCurrentJob();
            Action onSuccess = __instance._onSuccess;
            onSuccess?.Invoke();
        }));

        return false;
    }
    
    [HarmonyPrefix]
    [HarmonyPatch(typeof(AvatarQueueSystem), nameof(AvatarQueueSystem.CheckAvailability))]
    private static bool Prefix_AvatarQueueSystem_CheckAvailability(AvatarQueueSystem __instance)
    {
        if (__instance.JobIsActive 
            || __instance.activeCoroutines.Count == 0)
            return false;
        
        bool prioritizeSelf = AvatarQueueSystemTweaksMod.EntryPrioritizeSelf.Value;
        bool prioritizeFriends = AvatarQueueSystemTweaksMod.EntryPrioritizeFriends.Value;
        bool loadByDistance = AvatarQueueSystemTweaksMod.EntryLoadByDistance.Value;

        bool foundFriend = false;
        float nearestDistance = float.MaxValue;
        AvatarQueueSystem.CoroutineHandle nextCoroutine = null;
        Vector3 playerPosition = PlayerSetup.Instance.activeCam.transform.position;

        foreach (AvatarQueueSystem.CoroutineHandle coroutine in __instance.activeCoroutines)
        {
            if (prioritizeSelf && coroutine.player == "_PLAYERLOCAL") // prioritize local player if setting is enabled
            {
                nextCoroutine = coroutine;
                break;
            }

            CVRPlayerManager.Instance.GetPlayerPuppetMaster(coroutine.player, out PuppetMaster puppetMaster);
            if (puppetMaster == null)
                continue;
            
            if (prioritizeFriends)
            {
                switch (foundFriend)
                {
                    // attempt find first friend
                    case false when Friends.FriendsWith(coroutine.player):
                        foundFriend = true;
                        nearestDistance = float.MaxValue;
                        nextCoroutine = coroutine;
                        if (!loadByDistance) break; // no reason to continue loop
                        continue;
                    // found a friend, filtering will now only respect friends for this iteration
                    case true when !Friends.FriendsWith(coroutine.player):
                        continue;
                }
            }
            
            // filtering by distance
            if (!loadByDistance) continue;
            
            float distance = Vector3.Distance(playerPosition, puppetMaster.transform.position);
            if (!(distance < nearestDistance)) continue; // update nearest coroutine if closer
            
            nearestDistance = distance;
            nextCoroutine = coroutine;
        }

        nextCoroutine?.RunManagedCoroutine();
        return false;
    }
}