using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Action = System.Action;

namespace eagle {
    /// <summary>
    /// A looping weapon-clash animation displayed on the strategic map
    /// between a defender province and an attacker's origin province.
    /// Uses UI-space Image components (unlike MeleeAnimator which uses SpriteRenderer).
    /// </summary>
    public class BattleInProgressEffect : MonoBehaviour {
        [Header("Weapon Settings")]
        public Sprite weapon1Sprite;
        public Sprite weapon2Sprite;

        public float weaponScale = 30f;
        public float weaponOffset = 20f;
        public float thrustDistance = 12f;

        [Header("Animation Settings")]
        public float clashDuration = 0.4f;
        public int clashCount = 2;
        public float pauseBetweenClashes = 0.5f;
        public float swingArc = 60f;
        public float shakeIntensity = 2f;

        [Header("Arrow Settings")]
        public Sprite arrowSprite;
        public float arrowScale = 15f;
        public float arrowSpriteRotationOffset = -135f;
        public int arrowsPerVolley = 6;
        public float arrowFlightDuration = 0.6f;
        public float arrowArcHeight = 0.15f;
        public float arrowSourceSpread = 5f;
        public float arrowTargetSpread = 8f;
        public float arrowLaunchSpread = 0.08f;
        public float arrowVolleyInterval = 3f;

        /// <summary>
        /// Rotation angle (degrees) set by the controller based on the direction
        /// between the two province centroids.
        /// </summary>
        [HideInInspector]
        public float axisAngle;

        /// <summary>
        /// Per-weapon rotation offsets set by the controller based on sprite orientation.
        /// Sword (blade points down) needs +90; mace (head points up) needs -90.
        /// </summary>
        [HideInInspector]
        public float weapon1RotationOffset;

        [HideInInspector]
        public float weapon2RotationOffset;

        /// <summary>
        /// Half the screen distance between the two province centroids.
        /// Set by the controller so arrows can fly the full distance.
        /// </summary>
        [HideInInspector]
        public float halfDistance;

        /// <summary>
        /// Callbacks invoked when sounds should play.
        /// The controller handles actual playback on its own AudioSource.
        /// </summary>
        public Action onClash;

        public Action onArcheryVolley;

        private GameObject _weapon1;
        private GameObject _weapon2;
        private Coroutine _clashCoroutine;
        private Coroutine _arrowCoroutine;

        void Start() {
            CreateWeapons();
            _clashCoroutine = StartCoroutine(AnimateLoop());

            if (arrowSprite != null) { _arrowCoroutine = StartCoroutine(ArrowVolleyLoop()); }
        }

        private void CreateWeapons() {
            _weapon1 =
                    CreateWeaponImage("Weapon1", weapon1Sprite, axisAngle + weapon1RotationOffset);
            _weapon2 = CreateWeaponImage(
                    "Weapon2",
                    weapon2Sprite,
                    axisAngle + 180f + weapon2RotationOffset);
        }

        private GameObject CreateWeaponImage(string name, Sprite sprite, float angle) {
            var go = new GameObject(name);
            go.transform.SetParent(transform, false);

            var image = go.AddComponent<Image>();
            image.sprite = sprite;
            image.preserveAspect = true;
            image.raycastTarget = false;

            var rt = go.GetComponent<RectTransform>();
            rt.sizeDelta = new Vector2(weaponScale, weaponScale);
            rt.localRotation = Quaternion.Euler(0, 0, angle);

            return go;
        }

        private IEnumerator AnimateLoop() {
            while (true) {
                yield return StartCoroutine(AnimateClashCycle());
                yield return new WaitForSeconds(pauseBetweenClashes);
            }
        }

        private IEnumerator AnimateClashCycle() {
            if (_weapon1 == null || _weapon2 == null) yield break;

            float rad = axisAngle * Mathf.Deg2Rad;
            Vector2 direction = new Vector2(Mathf.Cos(rad), Mathf.Sin(rad));

            Vector2 w1Start = -direction * weaponOffset;
            Vector2 w2Start = direction * weaponOffset;
            Vector2 w1Clash = -direction * (weaponOffset - thrustDistance);
            Vector2 w2Clash = direction * (weaponOffset - thrustDistance);

            float w1BaseAngle = axisAngle + weapon1RotationOffset;
            float w2BaseAngle = axisAngle + 180f + weapon2RotationOffset;

            _weapon1.GetComponent<RectTransform>().anchoredPosition = w1Start;
            _weapon2.GetComponent<RectTransform>().anchoredPosition = w2Start;

            for (int i = 0; i < clashCount; i++) {
                float swingDir = (i % 2 == 0) ? 1f : -1f;

                // Swing toward clash
                yield return StartCoroutine(SwingWeapon(
                        _weapon1,
                        w1Start,
                        w1Clash,
                        w1BaseAngle,
                        -swingArc * swingDir,
                        0f,
                        clashDuration / 2f,
                        true));
                yield return StartCoroutine(SwingWeapon(
                        _weapon2,
                        w2Start,
                        w2Clash,
                        w2BaseAngle,
                        swingArc * swingDir,
                        0f,
                        clashDuration / 2f,
                        true));

                // Play clash sound at impact
                onClash?.Invoke();

                // Impact shake
                yield return StartCoroutine(ImpactShake(w1Clash, w2Clash, 0.05f));

                // Swing back
                yield return StartCoroutine(SwingWeapon(
                        _weapon1,
                        w1Clash,
                        w1Start,
                        w1BaseAngle,
                        0f,
                        -swingArc * swingDir,
                        clashDuration / 2f,
                        false));
                yield return StartCoroutine(SwingWeapon(
                        _weapon2,
                        w2Clash,
                        w2Start,
                        w2BaseAngle,
                        0f,
                        swingArc * swingDir,
                        clashDuration / 2f,
                        false));
            }
        }

        private IEnumerator SwingWeapon(
                GameObject weapon,
                Vector2 fromPos,
                Vector2 toPos,
                float baseAngle,
                float fromAngleOffset,
                float toAngleOffset,
                float duration,
                bool isAttacking) {
            if (weapon == null) yield break;

            var rt = weapon.GetComponent<RectTransform>();
            float elapsed = 0f;

            while (elapsed < duration) {
                if (weapon == null) yield break;

                float t = elapsed / duration;
                float smoothT = isAttacking ? t * t * (3f - 2f * t) : t * (2f - t);

                rt.anchoredPosition = Vector2.Lerp(fromPos, toPos, smoothT);

                float currentAngle =
                        Mathf.Lerp(baseAngle + fromAngleOffset, baseAngle + toAngleOffset, smoothT);
                rt.localRotation = Quaternion.Euler(0, 0, currentAngle);

                elapsed += Time.deltaTime;
                yield return null;
            }

            if (weapon != null) {
                rt.anchoredPosition = toPos;
                rt.localRotation = Quaternion.Euler(0, 0, baseAngle + toAngleOffset);
            }
        }

        private IEnumerator ImpactShake(Vector2 pos1, Vector2 pos2, float duration) {
            if (_weapon1 == null || _weapon2 == null) yield break;

            var rt1 = _weapon1.GetComponent<RectTransform>();
            var rt2 = _weapon2.GetComponent<RectTransform>();
            float elapsed = 0f;
            float intensity = shakeIntensity * 2f;

            while (elapsed < duration) {
                if (_weapon1 == null || _weapon2 == null) yield break;

                Vector2 shake1 = new Vector2(
                        Random.Range(-intensity, intensity),
                        Random.Range(-intensity, intensity));
                Vector2 shake2 = new Vector2(
                        Random.Range(-intensity, intensity),
                        Random.Range(-intensity, intensity));

                rt1.anchoredPosition = pos1 + shake1;
                rt2.anchoredPosition = pos2 + shake2;

                elapsed += Time.deltaTime;
                yield return null;
            }
        }

        // ── Arrow Volleys ──────────────────────────────────────────────

        private IEnumerator ArrowVolleyLoop() {
            while (true) {
                yield return new WaitForSeconds(
                        Random.Range(arrowVolleyInterval * 0.5f, arrowVolleyInterval * 1.5f));

                bool fromDefender = Random.value > 0.5f;
                yield return StartCoroutine(SpawnArrowVolley(fromDefender));
            }
        }

        private IEnumerator SpawnArrowVolley(bool fromDefender) {
            float rad = axisAngle * Mathf.Deg2Rad;
            Vector2 direction = new Vector2(Mathf.Cos(rad), Mathf.Sin(rad));

            Vector2 defenderPos = -direction * halfDistance;
            Vector2 originPos = direction * halfDistance;

            Vector2 sourcePos = fromDefender ? defenderPos : originPos;
            Vector2 targetPos = fromDefender ? originPos : defenderPos;

            var arrows = new List<GameObject>();

            onArcheryVolley?.Invoke();

            for (int i = 0; i < arrowsPerVolley; i++) {
                Vector2 srcOffset = Random.insideUnitCircle * arrowSourceSpread;
                Vector2 tgtOffset = Random.insideUnitCircle * arrowTargetSpread;

                Vector2 arrowSrc = sourcePos + srcOffset;
                Vector2 arrowTgt = targetPos + tgtOffset;

                var arrow = CreateArrowImage(arrowSrc, arrowTgt);
                arrows.Add(arrow);

                float delay = Random.Range(0, arrowLaunchSpread);
                StartCoroutine(AnimateArrow(arrow, arrowSrc, arrowTgt, delay));
            }

            yield return new WaitForSeconds(arrowFlightDuration + arrowLaunchSpread + 0.1f);

            foreach (var arrow in arrows) {
                if (arrow != null) { Destroy(arrow); }
            }
        }

        private GameObject CreateArrowImage(Vector2 sourcePos, Vector2 targetPos) {
            var go = new GameObject("Arrow");
            go.transform.SetParent(transform, false);

            var image = go.AddComponent<Image>();
            image.sprite = arrowSprite;
            image.preserveAspect = true;
            image.raycastTarget = false;

            var rt = go.GetComponent<RectTransform>();
            rt.sizeDelta = new Vector2(arrowScale, arrowScale);
            rt.anchoredPosition = sourcePos;

            Vector2 dir = (targetPos - sourcePos).normalized;
            float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg + arrowSpriteRotationOffset;
            rt.localRotation = Quaternion.Euler(0, 0, angle);

            return go;
        }

        private IEnumerator
        AnimateArrow(GameObject arrow, Vector2 start, Vector2 end, float delay) {
            if (delay > 0) { yield return new WaitForSeconds(delay); }
            if (arrow == null) { yield break; }

            var rt = arrow.GetComponent<RectTransform>();
            float elapsed = 0f;
            float distance = Vector2.Distance(start, end);
            float peakHeight = distance * arrowArcHeight;

            // Perpendicular direction for arc (rotate travel direction 90° CCW)
            Vector2 travelDir = (end - start).normalized;
            Vector2 arcDir = new Vector2(-travelDir.y, travelDir.x);

            while (elapsed < arrowFlightDuration) {
                if (arrow == null) { yield break; }

                float t = elapsed / arrowFlightDuration;
                Vector2 currentPos = Vector2.Lerp(start, end, t);
                float arcOffset = 4 * peakHeight * t * (1 - t);
                currentPos += arcDir * arcOffset;

                rt.anchoredPosition = currentPos;

                elapsed += Time.deltaTime;
                yield return null;
            }

            if (arrow != null) { Destroy(arrow); }
        }

        // ── Cleanup ────────────────────────────────────────────────────

        void OnDestroy() {
            if (_clashCoroutine != null) { StopCoroutine(_clashCoroutine); }
            if (_arrowCoroutine != null) { StopCoroutine(_arrowCoroutine); }
            if (_weapon1 != null) { Destroy(_weapon1); }
            if (_weapon2 != null) { Destroy(_weapon2); }
        }
    }
}
