using Boxfriend.Extensions;
using Boxfriend.Input;
using KinematicCharacterController;
using UnityEngine;

namespace Boxfriend.Player
{
    [RequireComponent(typeof(InputDispatcher))]
    [RequireComponent(typeof(KinematicCharacterMotor))]
    public class PlayerMotor : MonoBehaviour, ICharacterController
    {
        [SerializeField, InspectorOnly] private KinematicCharacterMotor _characterMotor;
        [SerializeField, InspectorOnly] private InputDispatcher _input;
        [SerializeField, InspectorOnly] private Animator _animator;

        private bool _isSprinting;
        private float _yRotation;
        private float _gravityVelocity;
        private Vector3 _moveDirection;
        private Vector3 _groundNormal = Vector3.up;

        [SerializeField, InspectorOnly] private float _moveSpeed;
        [SerializeField, InspectorOnly] private float _defaultGravity;
        [SerializeField, InspectorOnly] private float _jumpForce;


        private void Awake ()
        {
            _input.OnLook += OnLook;
            _input.OnMove += OnMove;
            _input.OnJump += OnJump;
            _input.OnSprint += OnSprint;

            _characterMotor.CharacterController = this;
        }

        private void OnLook (Vector2 delta) => _yRotation = Mathf.Repeat(_yRotation + delta.x, 360);
        private void OnMove (Vector2 inputDir) => _moveDirection = inputDir.To3D();

        private void OnSprint (bool sprinting) => _isSprinting = sprinting;
        private void OnJump ()
        {
            if (!_characterMotor.GroundingStatus.IsStableOnGround)
                return;


            _characterMotor.ForceUnground();
            _groundNormal = Vector3.up;
            _gravityVelocity = _jumpForce;
        }

        public void UpdateRotation (ref Quaternion currentRotation, float deltaTime) => currentRotation = Quaternion.Euler(0, _yRotation, 0);

        public void UpdateVelocity (ref Vector3 currentVelocity, float deltaTime)
        {
            if (!_characterMotor.GroundingStatus.IsStableOnGround)
            {
                _gravityVelocity += _defaultGravity * deltaTime;
                currentVelocity.y = _gravityVelocity;
                return;
            }

            _gravityVelocity = 0;

            var moveDir = transform.TransformDirection(_moveDirection);
            var direction = _characterMotor.GetDirectionTangentToSurface(moveDir, _groundNormal);
            direction *= _isSprinting ? _moveSpeed * 1.5f : _moveSpeed;
            direction.y += _gravityVelocity;

            currentVelocity = direction;
        }

        public void BeforeCharacterUpdate (float deltaTime) { }

        public void PostGroundingUpdate (float deltaTime) { }

        public void AfterCharacterUpdate (float deltaTime) 
        {
            _animator.SetFloat("Speed", _moveDirection.z);
            _animator.SetFloat("Turns", _moveDirection.x);
        }

        public bool IsColliderValidForCollisions (Collider coll) => coll != _characterMotor.Capsule;

        public void OnGroundHit (Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
            ref HitStabilityReport hitStabilityReport) => _groundNormal = hitNormal;

        public void OnMovementHit (Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
            ref HitStabilityReport hitStabilityReport)
        { }

        public void ProcessHitStabilityReport (Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition,
            Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
        { }

        public void OnDiscreteCollisionDetected (Collider hitCollider) { }

        private void Reset ()
        {
            _characterMotor = GetComponent<KinematicCharacterMotor>();
            _input = GetComponent<InputDispatcher>();
        }
    }
}
