using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;

namespace Gyvr.Mythril2D
{
    [RequireComponent(typeof(Rigidbody2D))]
    public class MultiHitProjectile : Projectile
    {
        [Header("MultiHit Parameters")]
        [SerializeField, Min(1)] private int m_numberOfTargets = 1;

        // Cache: unique targets we have already damaged
        private readonly HashSet<CharacterBase> m_hitTargets = new HashSet<CharacterBase>();
        private int m_successfulHits = 0;

        // Reflection cache for OEM-private members
        private static FieldInfo FI_m_operating;
        private static FieldInfo FI_m_source;
        private static FieldInfo FI_m_baseEffects;
        private static FieldInfo FI_m_direction;
        private static FieldInfo FI_m_collisionSound;
        private static MethodInfo MI_Terminate; // Projectile.Terminate(CharacterBase)
        private static int s_hitboxLayer = -1;

        private void Awake()
        {
            // Resolve reflection targets once
            BindingFlags nf = BindingFlags.NonPublic | BindingFlags.Instance;

            if (FI_m_operating == null) FI_m_operating = typeof(Projectile).GetField("m_operating", nf);
            if (FI_m_source == null) FI_m_source = typeof(Projectile).GetField("m_source", nf);
            if (FI_m_baseEffects == null) FI_m_baseEffects = typeof(Projectile).GetField("m_baseEffects", nf);
            if (FI_m_direction == null) FI_m_direction = typeof(Projectile).GetField("m_direction", nf);
            if (FI_m_collisionSound == null) FI_m_collisionSound = typeof(Projectile).GetField("m_collisionSound", nf);

            if (MI_Terminate == null)
            {
                // private void Terminate(CharacterBase primaryTarget = null)
                MI_Terminate = typeof(Projectile).GetMethod("Terminate", nf, null, new Type[] { typeof(CharacterBase) }, null);
            }

            // Cache hitbox layer id
            if (s_hitboxLayer == -1 && GameManager.Config != null)
            {
                s_hitboxLayer = LayerMask.NameToLayer(GameManager.Config.hitboxLayer);
            }
        }

        private bool IsOperating()
        {
            return FI_m_operating != null && (bool)FI_m_operating.GetValue(this);
        }

        private CharacterBase GetSource()
        {
            return FI_m_source != null ? (CharacterBase)FI_m_source.GetValue(this) : null;
        }

        private List<IEffect> GetBaseEffects()
        {
            return FI_m_baseEffects != null ? (List<IEffect>)FI_m_baseEffects.GetValue(this) : null;
        }

        private Vector2 GetDirection()
        {
            return FI_m_direction != null ? (Vector2)FI_m_direction.GetValue(this) : Vector2.zero;
        }

        private AudioClipResolver GetCollisionSound()
        {
            return FI_m_collisionSound != null ? (AudioClipResolver)FI_m_collisionSound.GetValue(this) : null;
        }

        private void TerminateViaOEM(CharacterBase primaryTarget)
        {
            MI_Terminate?.Invoke(this, new object[] { primaryTarget });
        }

        private static bool IsLayerInMask(int layer, int mask)
        {
            return (mask & (1 << layer)) != 0;
        }

        private void TryHandleContact(GameObject other)
        {
            if (!IsOperating()) return;


            if (s_hitboxLayer >= 0 && other.layer == s_hitboxLayer && other != gameObject)
            {
                CharacterBase character = other.GetComponentInParent<CharacterBase>();
                if (character == null)
                {

                }
                else
                {
                    if (m_hitTargets.Contains(character)) return;

                    var source = GetSource();
                    var effects = GetBaseEffects();
                    var dir = GetDirection();

                    if (source != null && effects != null)
                    {
                        EffectApplicationResult result = EffectDispatcher.Apply(
                            source,
                            new[] { character },
                            effects,
                            new()
                            {
                                impactDataType = EEffectImpactDataType.Velocity,
                                impactData = dir
                            }
                        );

                        bool anyApplicable = result.feed.Any(r => r != EEffectInteractionResult.NotApplicable);
                        if (anyApplicable)
                        {
                            var clip = GetCollisionSound();
                            if (clip != null)
                                GameManager.NotificationSystem.audioPlaybackRequested.Invoke(clip);

                            m_hitTargets.Add(character);
                            m_successfulHits++;

                            if (m_successfulHits >= m_numberOfTargets)
                            {
                                TerminateViaOEM(character);
                            }
                        }
                    }
                    return;
                }
            }

            int solidMask = GameManager.Config != null ? GameManager.Config.collisionContactFilter.layerMask : 0;
            if (IsLayerInMask(other.layer, solidMask))
            {
                var clip = GetCollisionSound();
                if (clip != null)
                    GameManager.NotificationSystem.audioPlaybackRequested.Invoke(clip);

                TerminateViaOEM(null);
                return;
            }
        }

        private void OnTriggerEnter2D(Collider2D collision)
        {
            TryHandleContact(collision.gameObject);
        }

        private void OnCollisionEnter2D(Collision2D collision)
        {
            TryHandleContact(collision.gameObject);
        }
    }
}
