using UnityEngine;

namespace eagle {
    /// <summary>
    /// Spawns an animated 3D dragon that alternates between flying (circling at altitude)
    /// and grounded (wandering near province center) modes with a full animation suite.
    /// </summary>
    [RequireComponent(typeof(RectTransform))]
    public class DragonEffect : MonoBehaviour {
        [Header("Dragon Prefab")]
        public GameObject dragonPrefab;

        [Header("Animator")]
        public RuntimeAnimatorController animatorController;

        [Header("Scale & Motion")]
        public float dragonScale = 10f;
        public float circleRadius = 20f;
        public float circleSpeed = 0.6f;
        public float flyHeight = 15f;
        public float verticalBob = 3f;
        public float verticalBobSpeed = 1.5f;
        public float turnSpeed = 5f;
        [System.NonSerialized]
        public bool clipToViewport = true;

        [Header("Ground Wander")]
        public float groundWanderRadius = 15f;
        public float groundMoveSpeed = 12f;

        [Header("Mode Timing")]
        public float minFlyDuration = 12f;
        public float maxFlyDuration = 20f;
        public float minGroundDuration = 4f;
        public float maxGroundDuration = 8f;
        public float transitionDuration = 1f;

        [Header("Action Timing")]
        public float minActionInterval = 2f;
        public float maxActionInterval = 5f;
        [Tooltip("Most imported 3D beast models face out their local -Z axis.")]
        public bool modelFrontFacesNegativeZ = true;

        private enum DragonMode { Flying, Landing, Grounded, TakingOff }

        private GameObject _dragonInstance;
        private Animator _animator;
        private DragonMode _mode;
        private float _angle;
        private float _modeTimer;
        private float _actionTimer;
        private float _transitionProgress;
        private Vector3 _groundTarget;
        private bool _inAction;

        private static readonly int FlyHash = Animator.StringToHash("fly");
        private static readonly int WalkHash = Animator.StringToHash("walk");
        private static readonly int RunHash = Animator.StringToHash("run");
        private static readonly int IdleHash = Animator.StringToHash("idle");
        private static readonly int StandHash = Animator.StringToHash("stand");
        private static readonly int Attack1Hash = Animator.StringToHash("attack1");
        private static readonly int Attack2Hash = Animator.StringToHash("attack2");
        private static readonly int BreathFireHash = Animator.StringToHash("breathFire");
        private static readonly int WhipTailHash = Animator.StringToHash("whipTail");
        private static readonly int Hit1Hash = Animator.StringToHash("hit1");
        private static readonly int Hit2Hash = Animator.StringToHash("hit2");
        private static readonly int FlyAttackHash = Animator.StringToHash("flyAttack");
        private static readonly int FlyBreathFireHash = Animator.StringToHash("flyBreathFire");
        private static readonly int FlyFastHash = Animator.StringToHash("flyFast");
        private static readonly int FlyHitHash = Animator.StringToHash("flyHit");
        private const string BeastSortingLayerName = "UI Base Overlay";
        private const int BeastCreatureSortingOrder = 103;

        void Awake() {
            SpawnDragon();
            _mode = DragonMode.Flying;
            _angle = Random.Range(0f, Mathf.PI * 2f);
            _modeTimer = Random.Range(minFlyDuration, maxFlyDuration);
            _actionTimer = Random.Range(minActionInterval, maxActionInterval);
        }

        private void SpawnDragon() {
            _dragonInstance = Instantiate(dragonPrefab, transform);
            _dragonInstance.name = "Dragon";
            _dragonInstance.transform.localScale =
                    new Vector3(dragonScale, dragonScale, dragonScale);
            FaceCameraAtSpawn(_dragonInstance.transform);

            foreach (var renderer in _dragonInstance.GetComponentsInChildren<Renderer>()) {
                renderer.sortingLayerName = BeastSortingLayerName;
                renderer.sortingOrder = BeastCreatureSortingOrder;
                BeastMaterialCompatibility.ConfigureRenderer(renderer);
            }
            if (clipToViewport) { RendererViewportClipper.Attach(_dragonInstance); }

            _animator = _dragonInstance.GetComponentInChildren<Animator>();
            if (_animator == null) {
                Debug.LogWarning($"DragonEffect: '{dragonPrefab.name}' has no Animator component.");
                return;
            }
            if (animatorController != null) {
                _animator.runtimeAnimatorController = animatorController;
            }
            _animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
            _animator.Play(FlyHash);
        }

        private void FaceCameraAtSpawn(Transform instanceTransform) {
            var worldDirection = Camera.main != null ? instanceTransform.position -
                                                               Camera.main.transform.position
                                                     : transform.forward;
            var localDirection = transform.InverseTransformDirection(worldDirection);
            localDirection.y = 0f;
            if (localDirection.sqrMagnitude < 0.01f) { localDirection = Vector3.forward; }
            if (modelFrontFacesNegativeZ) { localDirection = -localDirection; }

            instanceTransform.localRotation = Quaternion.LookRotation(localDirection, Vector3.up);
        }

        void Update() {
            if (_dragonInstance == null) { return; }

            switch (_mode) {
                case DragonMode.Flying: UpdateFlying(); break;
                case DragonMode.Landing: UpdateLanding(); break;
                case DragonMode.Grounded: UpdateGrounded(); break;
                case DragonMode.TakingOff: UpdateTakingOff(); break;
            }
        }

        private void UpdateFlying() {
            _angle += circleSpeed * Time.deltaTime;
            if (_angle > Mathf.PI * 2f) { _angle -= Mathf.PI * 2f; }

            var x = Mathf.Cos(_angle) * circleRadius;
            var y = Mathf.Sin(_angle) * circleRadius;
            var bob = Mathf.Sin(Time.time * verticalBobSpeed) * verticalBob;
            _dragonInstance.transform.localPosition = new Vector3(x, y + flyHeight + bob, 0f);

            UpdateFlyFacing();
            UpdateActionTimer(true);

            _modeTimer -= Time.deltaTime;
            if (_modeTimer <= 0f) {
                _mode = DragonMode.Landing;
                _transitionProgress = 0f;
                _inAction = false;
            }
        }

        private void UpdateLanding() {
            _transitionProgress += Time.deltaTime / transitionDuration;

            _angle += circleSpeed * Time.deltaTime * (1f - _transitionProgress);

            var x = Mathf.Cos(_angle) * Mathf.Lerp(circleRadius, 0f, _transitionProgress);
            var y = Mathf.Sin(_angle) * Mathf.Lerp(circleRadius, 0f, _transitionProgress);
            var height = Mathf.Lerp(flyHeight, 0f, _transitionProgress);
            _dragonInstance.transform.localPosition = new Vector3(x, y + height, 0f);

            UpdateFlyFacing();

            if (_transitionProgress >= 1f) {
                _mode = DragonMode.Grounded;
                _modeTimer = Random.Range(minGroundDuration, maxGroundDuration);
                _actionTimer = Random.Range(minActionInterval, maxActionInterval);
                _groundTarget = _dragonInstance.transform.localPosition;
                _inAction = false;
                _animator?.Play(WalkHash);
                PickGroundTarget();
            }
        }

        private void UpdateGrounded() {
            var pos = _dragonInstance.transform.localPosition;
            var step = groundMoveSpeed * Time.deltaTime;
            _dragonInstance.transform.localPosition = Vector3.MoveTowards(pos, _groundTarget, step);

            UpdateGroundFacing();

            if (Vector3.Distance(_dragonInstance.transform.localPosition, _groundTarget) < 0.5f) {
                PickGroundTarget();
            }

            UpdateActionTimer(false);

            _modeTimer -= Time.deltaTime;
            if (_modeTimer <= 0f) {
                _mode = DragonMode.TakingOff;
                _transitionProgress = 0f;
                _inAction = false;
                _animator?.Play(FlyHash);
            }
        }

        private void UpdateTakingOff() {
            _transitionProgress += Time.deltaTime / transitionDuration;

            _angle += circleSpeed * Time.deltaTime * _transitionProgress;

            var x = Mathf.Cos(_angle) * Mathf.Lerp(0f, circleRadius, _transitionProgress);
            var y = Mathf.Sin(_angle) * Mathf.Lerp(0f, circleRadius, _transitionProgress);
            var height = Mathf.Lerp(0f, flyHeight, _transitionProgress);
            _dragonInstance.transform.localPosition = new Vector3(x, y + height, 0f);

            UpdateFlyFacing();

            if (_transitionProgress >= 1f) {
                _mode = DragonMode.Flying;
                _modeTimer = Random.Range(minFlyDuration, maxFlyDuration);
                _actionTimer = Random.Range(minActionInterval, maxActionInterval);
                _inAction = false;
            }
        }

        private void UpdateFlyFacing() {
            var tangentX = -Mathf.Sin(_angle);
            var tangentY = Mathf.Cos(_angle);
            var targetAngle = Mathf.Atan2(tangentX, tangentY) * Mathf.Rad2Deg;
            var targetRotation = Quaternion.Euler(0f, targetAngle, 0f);
            _dragonInstance.transform.localRotation = Quaternion.Slerp(
                    _dragonInstance.transform.localRotation,
                    targetRotation,
                    turnSpeed * Time.deltaTime);
        }

        private void UpdateGroundFacing() {
            var direction = _groundTarget - _dragonInstance.transform.localPosition;
            if (direction.sqrMagnitude < 0.01f) { return; }

            var targetAngle = Mathf.Atan2(direction.x, direction.y) * Mathf.Rad2Deg;
            var targetRotation = Quaternion.Euler(0f, targetAngle, 0f);
            _dragonInstance.transform.localRotation = Quaternion.Slerp(
                    _dragonInstance.transform.localRotation,
                    targetRotation,
                    turnSpeed * Time.deltaTime);
        }

        private void PickGroundTarget() {
            var angle = Random.Range(0f, Mathf.PI * 2f);
            var dist = Random.Range(0f, groundWanderRadius);
            _groundTarget = new Vector3(Mathf.Cos(angle) * dist, Mathf.Sin(angle) * dist, 0f);
        }

        private void UpdateActionTimer(bool flying) {
            if (_animator == null) { return; }
            if (_inAction) {
                if (_animator.IsInTransition(0)) { return; }
                var stateInfo = _animator.GetCurrentAnimatorStateInfo(0);
                if (stateInfo.normalizedTime >= 1f) {
                    _inAction = false;
                    _actionTimer = Random.Range(minActionInterval, maxActionInterval);
                    _animator.Play(flying ? FlyHash : WalkHash);
                }
                return;
            }

            _actionTimer -= Time.deltaTime;
            if (_actionTimer <= 0f) { PerformAction(flying); }
        }

        private void PerformAction(bool flying) {
            _inAction = true;
            if (flying) {
                PerformFlyAction();
            } else {
                PerformGroundAction();
            }
        }

        private void PerformFlyAction() {
            var roll = Random.value;
            if (roll < 0.25f) {
                _animator.Play(FlyAttackHash);
            } else if (roll < 0.55f) {
                _animator.Play(FlyBreathFireHash);
            } else if (roll < 0.80f) {
                _animator.Play(FlyFastHash);
            } else {
                _animator.Play(FlyHitHash);
            }
        }

        private void PerformGroundAction() {
            var roll = Random.value;
            if (roll < 0.15f) {
                _animator.Play(Attack1Hash);
            } else if (roll < 0.30f) {
                _animator.Play(Attack2Hash);
            } else if (roll < 0.50f) {
                _animator.Play(BreathFireHash);
            } else if (roll < 0.65f) {
                _animator.Play(WhipTailHash);
            } else if (roll < 0.75f) {
                _animator.Play(Hit1Hash);
            } else if (roll < 0.85f) {
                _animator.Play(Hit2Hash);
            } else if (roll < 0.925f) {
                _animator.Play(StandHash);
            } else {
                _animator.Play(IdleHash);
            }
        }

        [ContextMenu("Test: Fly")]
        public void TestFly() { PlayTest(FlyHash); }

        [ContextMenu("Test: Walk")]
        public void TestWalk() { PlayTest(WalkHash); }

        [ContextMenu("Test: Run")]
        public void TestRun() { PlayTest(RunHash); }

        [ContextMenu("Test: Idle")]
        public void TestIdle() { PlayTest(IdleHash); }

        [ContextMenu("Test: Attack1")]
        public void TestAttack1() { PlayTest(Attack1Hash); }

        [ContextMenu("Test: Attack2")]
        public void TestAttack2() { PlayTest(Attack2Hash); }

        [ContextMenu("Test: Breath Fire")]
        public void TestBreathFire() { PlayTest(BreathFireHash); }

        [ContextMenu("Test: Whip Tail")]
        public void TestWhipTail() { PlayTest(WhipTailHash); }

        [ContextMenu("Test: Fly Attack")]
        public void TestFlyAttack() { PlayTest(FlyAttackHash); }

        [ContextMenu("Test: Fly Breath Fire")]
        public void TestFlyBreathFire() { PlayTest(FlyBreathFireHash); }

        [ContextMenu("Test: Fly Fast")]
        public void TestFlyFast() { PlayTest(FlyFastHash); }

        [ContextMenu("Test: Fly Hit")]
        public void TestFlyHit() { PlayTest(FlyHitHash); }

        private void PlayTest(int stateHash) {
            if (_animator == null) { return; }
            _inAction = false;
            _animator.Play(stateHash);
        }
    }
}
