using UnityEngine;
using UnityEngine.InputSystem;

public class PlatformMovement : MonoBehaviour
{
    [Header("Input Reference")]
    [SerializeField]
    private InputActionReference moveAction;
    [SerializeField]
    private InputActionReference jumpAction;

    [Header("Walk Settings")]
    [SerializeField]
    private float walkSpeed = 5;
    [SerializeField]
    private float acceleration = 10f;
    [SerializeField]
    private float deceleration = 20f;

    [Header("Jump Settings")]
    [SerializeField]
    private float jumpHeight = 2.5f;
    [SerializeField]
    private LayerMask groundLayer;
    [SerializeField]
    private float inAirControl = 0.3f;
    [SerializeField]
    private float jumpCancelDivider = 2f;
    [SerializeField]
    private float coyoteTime = 0.3f;

    [Header("Wall Slide Settings")]
    [SerializeField]
    private float defaultMaxFallSpeed = 30f;
    [SerializeField]
    private float wallSlideVelocity = 1f;
    private bool _isWallSliding;

    [Header("Wall Jump Settings")]
    [SerializeField]
    private bool wallJumpEnabled = true;
    private bool _isTouchingWall;
    private Vector2 _moveInput;
    private Rigidbody2D _rigidbody2D;
    private Collider2D _collider2D;
    private bool _isGrounded;
    private float _coyoteTimer;
    private bool _coyoteTimePassed;

    private void Awake()
    {
        _rigidbody2D = GetComponent<Rigidbody2D>();
        _collider2D = GetComponent<Collider2D>();
    }

    private void OnEnable()
    {
        moveAction.action.performed += HandleWalkInput;
        moveAction.action.canceled += HandleWalkInput;
        jumpAction.action.performed += HandleJumpInput;
        jumpAction.action.canceled += HandleJumpInput;
        jumpAction.action.canceled += HandleJumpCancelled;
    }

    private void OnDisable()
    {
        moveAction.action.performed -= HandleWalkInput;
        moveAction.action.canceled -= HandleWalkInput;
        jumpAction.action.performed -= HandleJumpInput;
        jumpAction.action.canceled -= HandleJumpInput;
        jumpAction.action.canceled -= HandleJumpCancelled;
    }

    private void Update()
    {
        _isTouchingWall = WallCheck();
        _isGrounded = GroundCheck();
        HandleWalking();
        HandleWallSlide();
        UpdateCoyoteTime();
        if (_isGrounded && _rigidbody2D.linearVelocity.y <= 0)
        {
            ResetCoyoteTime();
        }
    }

    private void HandleWalkInput(InputAction.CallbackContext context)
    {
        _moveInput = context.ReadValue<Vector2>();
    }

    private void HandleJumpInput(InputAction.CallbackContext context)
    {
        if (_isTouchingWall && !_isGrounded && _isWallSliding)
        {
            wallJumpEnabled = true;
        }
        else
        {
            wallJumpEnabled = false;
        }
        if (!_coyoteTimePassed || (wallJumpEnabled && _isTouchingWall))
        {
            HandleJumping();
        }
    }

    private void HandleJumpCancelled(InputAction.CallbackContext context)
    {
        if (_rigidbody2D.linearVelocity.y > 0)
        {
            _rigidbody2D.linearVelocity.y /= jumpCancelDivider;
        }
    }

    private void HandleWalking()
    {
        var control = _isGrounded ? 1 : inAirControl;
        var change = _moveInput.x != 0 ? acceleration : deceleration;
        _rigidbody2D.linearVelocity.x = Mathf.Lerp(_rigidbody2D.linearVelocity.x, _moveInput.x * walkSpeed, Time.deltaTime * change * control);
    }

    private void HandleJumping()
    {
        var actualGravity = Mathf.Abs(Physics2D.gravity.y) * _rigidbody2D.gravityScale;
        _rigidbody2D.linearVelocity.y = Mathf.Sqrt(2 * jumpHeight * actualGravity);
    }

    private void HandleWallSlide()
    {
        if (_isTouchingWall && _rigidbody2D.linearVelocity.y < 0)
        {
            _rigidbody2D.linearVelocity.y = Mathf.Max(_rigidbody2D.linearVelocity.y, -wallSlideVelocity);
            _isWallSliding = true;
        }
        else
        {
            _isWallSliding = false;
        }
    }

    private bool GroundCheck()
    {
        var colliderBottomCenter = new Vector2(_collider2D.bounds.center.x, _collider2D.bounds.min.y);
        return Physics2D.OverlapBox(colliderBottomCenter, new Vector2(_collider2D.bounds.size.x - 0.1f, 0.1f), 0, groundLayer);
    }

    private bool WallCheck()
    {
        float rayDistance = _collider2D.bounds.extents.x + 0.05f;
        Vector2 origin = _collider2D.bounds.center;
        RaycastHit2D hitRight = Physics2D.Raycast(origin, Vector2.right, rayDistance, groundLayer);
        RaycastHit2D hitLeft = Physics2D.Raycast(origin, Vector2.left, rayDistance, groundLayer);
        return hitLeft || hitRight;
    }

    private void UpdateCoyoteTime()
    {
        if (!_isGrounded && !_coyoteTimePassed)
        {
            _coyoteTimer += Time.deltaTime;
        }
    }

    private void ResetCoyoteTime()
    {
        _coyoteTimer = 0;
    }

    private void Start()
    {
        _coyoteTimePassed = false;
    }
}