// Assets/_Project/Scripts/Combat/Projectile.cs

using Unity.Netcode;
using UnityEngine;
using TD.Core;
using TD.Gameplay;

namespace TD.Combat
{
    /// <summary>
    /// A traveling projectile spawned by <see cref="TowerCombat"/> when a tower has
    /// a non-null <c>ProjectilePrefab</c> configured.
    /// </summary>
    /// <remarks>
    /// <b>Authority:</b> Movement and hit detection run server-only.
    /// <c>NetworkTransform</c> (required on the prefab) replicates position to clients
    /// so the projectile is visible on all peers.
    ///
    /// <b>Initialization:</b> Call <see cref="InitializeServer"/> after
    /// <c>Instantiate</c> and before <c>NetworkObject.Spawn()</c>.
    ///
    /// <b>Kill attribution:</b> <paramref name="sourceOwner"/> is the
    /// <see cref="PlayerSlot"/> of the firing tower. It is passed to
    /// <see cref="EnemyHealth.TakeDamage"/> and <see cref="EnemyStatus.ApplyEffect"/>
    /// so kill gold credits the correct player even for projectile kills.
    ///
    /// <b>Chain + Projectile:</b> <see cref="TargetType.Chain"/> is hitscan-only.
    /// A projectile with Chain type will hit the primary target only and log a warning.
    ///
    /// <b>Prefab requirements:</b> <c>NetworkObject</c>, <c>NetworkTransform</c>,
    /// and this component at the root.
    /// </remarks>
    [RequireComponent(typeof(NetworkObject))]
    public class Projectile : NetworkBehaviour
    {
        private const float HitThresholdSq = 0.09f; // 0.3 world units

        private EnemyHealth target;
        private float damage;
        private DamageType damageType;
        private TargetType targetType;
        private float splashRadius;
        private float slowFactor;
        private float dotDamagePerSecond;
        private float effectDuration;
        private float speed;
        private LayerMask enemyLayerMask;
        private PlayerSlot sourceOwner;
        private bool initialized;

        // Paint tint. Set pre-spawn (pendingTint), committed to the replicated
        // NetworkVariable in OnNetworkSpawn on the server, and applied as a mesh tint
        // on every client so a painted tower's projectiles match its color.
        private PaintColor pendingTint;

        private readonly NetworkVariable<PaintColor> tintColor =
            new(
                PaintColor.None,
                NetworkVariableReadPermission.Everyone,
                NetworkVariableWritePermission.Server);

        private MaterialPropertyBlock tintBlock;
        private static readonly int ColorPropertyId = Shader.PropertyToID("_Color");
        private static readonly int BaseColorPropertyId = Shader.PropertyToID("_BaseColor");

        private static readonly Collider[] s_overlapBuffer = new Collider[32];

        // ----- Pre-spawn init ---------------------------------------------

        /// <summary>
        /// Called by <see cref="TowerCombat"/> after <c>Instantiate</c> and before
        /// <c>NetworkObject.Spawn()</c>. <paramref name="sourceOwner"/> is the firing
        /// tower's <see cref="PlayerSlot"/> for kill-gold attribution.
        /// </summary>
        public void InitializeServer(
            EnemyHealth target,
            float damage,
            DamageType damageType,
            TargetType targetType,
            float splashRadius,
            float slowFactor,
            float dotDamagePerSecond,
            float effectDuration,
            float speed,
            LayerMask enemyLayerMask,
            PlayerSlot sourceOwner,
            PaintColor tint = PaintColor.None)
        {
            this.target = target;
            this.damage = damage;
            this.damageType = damageType;
            this.targetType = targetType;
            this.splashRadius = splashRadius;
            this.slowFactor = slowFactor;
            this.dotDamagePerSecond = dotDamagePerSecond;
            this.effectDuration = effectDuration;
            this.speed = speed;
            this.enemyLayerMask = enemyLayerMask;
            this.sourceOwner = sourceOwner;
            this.pendingTint = tint;
            initialized = true;

            if (targetType == TargetType.Chain)
                Debug.LogWarning("[Projectile] TargetType.Chain is hitscan-only. " +
                                 "This projectile will hit the primary target only.");
        }

        // ----- Spawn lifecycle (tint) -------------------------------------

        public override void OnNetworkSpawn()
        {
            // Commit the pre-spawn tint on the server; NGO includes this write in the
            // initial sync so every client reads the right value in its OnNetworkSpawn.
            if (IsServer)
                tintColor.Value = pendingTint;

            tintColor.OnValueChanged += HandleTintChanged;
            ApplyTint();
        }

        public override void OnNetworkDespawn()
        {
            tintColor.OnValueChanged -= HandleTintChanged;
        }

        private void HandleTintChanged(PaintColor previous, PaintColor current) => ApplyTint();

        // Tints all renderers to the paint color via a MaterialPropertyBlock. No-op when
        // unpainted so the prefab's authored projectile material shows through.
        private void ApplyTint()
        {
            if (tintColor.Value == PaintColor.None) return;

            Color c = PaintColors.Get(tintColor.Value);
            c.a = 1f;

            tintBlock ??= new MaterialPropertyBlock();
            tintBlock.SetColor(ColorPropertyId, c);
            tintBlock.SetColor(BaseColorPropertyId, c);

            foreach (var rend in GetComponentsInChildren<Renderer>())
                rend.SetPropertyBlock(tintBlock);
        }

        // ----- Server movement + hit detection ----------------------------

        private void Update()
        {
            if (!IsServer || !initialized) return;

            if (target == null || target.IsDead || (target as UnityEngine.Object) == null)
            {
                NetworkObject.Despawn();
                return;
            }

            Vector3 toTarget = target.transform.position - transform.position;

            if (toTarget.sqrMagnitude <= HitThresholdSq)
            {
                ApplyHit();
                NetworkObject.Despawn();
                return;
            }

            transform.rotation = Quaternion.LookRotation(toTarget);
            transform.position += toTarget.normalized * (speed * Time.deltaTime);
        }

        // ----- Hit application --------------------------------------------

        private void ApplyHit()
        {
            switch (targetType)
            {
                case TargetType.Single:
                case TargetType.Chain:
                    HitEnemy(target);
                    break;

                case TargetType.Splash:
                    HitEnemy(target);
                    if (splashRadius > 0f)
                    {
                        int count = Physics.OverlapSphereNonAlloc(
                            transform.position, splashRadius,
                            s_overlapBuffer, enemyLayerMask);

                        for (int i = 0; i < count; i++)
                        {
                            var eh = s_overlapBuffer[i].GetComponent<EnemyHealth>();
                            if (eh == null || eh.IsDead || (object)eh == (object)target) continue;
                            HitEnemy(eh);
                        }
                    }

                    break;
            }
        }

        private void HitEnemy(EnemyHealth eh)
        {
            eh.TakeDamage(damage, damageType, sourceOwner);

            if (effectDuration > 0f)
            {
                float magnitude = damageType switch
                {
                    DamageType.Cold => slowFactor,
                    DamageType.Fire => dotDamagePerSecond,
                    DamageType.Poison => dotDamagePerSecond,
                    _ => 0f,
                };

                if (magnitude > 0f)
                    eh.GetComponent<EnemyStatus>()
                        ?.ApplyEffect(damageType, magnitude, effectDuration, sourceOwner);
            }
        }
    }
}