using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(BoxCollider))]
public class PlayerMovement : MonoBehaviour
{
    public float moveSpeed = 6f;
    public float acceleration = 10f;
    public float deceleration = 10f;

    public float jumpForce = 6f;

    private float crouchHeight = 0.6f;
    private float normalHeight = 1.4f;
    private float crouchSmooth = 7.5f;

    [SerializeField] private Transform groundCheck;
    [SerializeField] private Transform ceilingCheck;
    [SerializeField] private float groundCheckRadius = 0.25f;
    [SerializeField] private float ceilingCheckRadius = 0.25f;
    [SerializeField] private LayerMask groundLayer;
    [SerializeField] private LayerMask ceilingLayer;

    private bool isGrounded;
    private bool isCrouching;
    //private bool isOnCrouch;

    public Transform cameraHolder;
    public float lookSensitivity = 2f;
    public float maxLookX = 88f;
    public float minLookX = -88f;
    //public float bodyTurnSpeed = 5f;

    private float uncrouchMassLimit = 19;
    private float upwardForceLimit = 8;

    public KeyCode forwardKey = KeyCode.W;
    public KeyCode backKey = KeyCode.S;
    public KeyCode leftKey = KeyCode.A;
    public KeyCode rightKey = KeyCode.D;
    public KeyCode jumpKey = KeyCode.Space;
    public KeyCode crouchKey = KeyCode.LeftControl;

    [SerializeField] private float groundedTimeThreshold = 0.1f;
    [SerializeField] private float jumpCooldown = 0.2f;

    private float lastGroundedTime;
    private float lastJumpTime;
    private Rigidbody rb;
    private BoxCollider col;
    private Vector3 moveInput;

    private float rotX;
    //private float currentBodyYaw;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        col = GetComponent<BoxCollider>();

        rb.freezeRotation = true;

        Cursor.lockState = CursorLockMode.Locked;
    }

    void Update()
    {
        HandleInput();
        HandleCrouch();
        HandleCamera();
        if (canJump)
        {
            HandleJump();
        }
    }

    void FixedUpdate()
    {
        MovePlayer();
        GroundCheck();
        MovePlayer();
    }

    void MovePlayer()
    {
        Vector3 targetVelocity = transform.TransformDirection(moveInput) * moveSpeed;
        Vector3 velocityChange = targetVelocity - new Vector3(rb.linearVelocity.x, 0, rb.linearVelocity.z);
        velocityChange = Vector3.ClampMagnitude(velocityChange, acceleration);

        rb.AddForce(velocityChange, ForceMode.VelocityChange);
    }

    // ---------------------------------------------
    bool canJump = true;

    void HandleCrouch()
    {
        bool wantsCrouch = Input.GetKey(crouchKey);
        bool ceilingBlocked = CeilingCheck();
        bool hasHeavyAbove = CheckHeavyObjectAbove();

        if (wantsCrouch || ceilingBlocked || hasHeavyAbove)
        {
            isCrouching = true;
            canJump = false;
        }
        else if (!wantsCrouch && !ceilingBlocked && !hasHeavyAbove)
        {
            isCrouching = false;
            canJump = true;
        }

        float targetHeight = isCrouching ? crouchHeight : normalHeight;
        Vector3 size = col.size;
        size.y = Mathf.Lerp(size.y, targetHeight, Time.deltaTime * crouchSmooth);
        col.size = size;
    }

    void HandleInput()
    {
        float x = 0f;
        float z = 0f;

        if (Input.GetKey(forwardKey)) z += 1;
        if (Input.GetKey(backKey)) z -= 1;
        if (Input.GetKey(rightKey)) x += 1;
        if (Input.GetKey(leftKey)) x -= 1;

        Vector3 targetInput = new Vector3(x, 0, z).normalized;
        moveInput = Vector3.Lerp(moveInput, targetInput, Time.deltaTime * acceleration);
    }

    void HandleJump()
    {
        if (Input.GetKey(jumpKey) &&
            isGrounded &&
            (Time.time - lastGroundedTime > groundedTimeThreshold) &&
            (Time.time - lastJumpTime > jumpCooldown) &&
            !isCrouching)
        {
            Vector3 vel = rb.linearVelocity;
            vel.y = 0;
            rb.linearVelocity = vel;

            rb.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange);
            lastJumpTime = Time.time;
        }
    }

    // ---------------------------------------------
    void HandleCamera()
    {
        if (!cameraHolder) return;

        float mouseX = Input.GetAxis("Mouse X") * lookSensitivity;
        float mouseY = Input.GetAxis("Mouse Y") * lookSensitivity;

        transform.Rotate(Vector3.up * mouseX);
        rotX -= mouseY;
        rotX = Mathf.Clamp(rotX, minLookX, maxLookX);

        cameraHolder.localRotation = Quaternion.Euler(rotX, 0, 0);
    }

    void GroundCheck()
    {
        Vector3 groundPos = transform.position + Vector3.down * (col.size.y * 0.5f - 0.05f);
        Collider[] hits = Physics.OverlapSphere(groundPos, groundCheckRadius, groundLayer);

        bool wasGrounded = isGrounded;
        isGrounded = false;

        foreach (Collider hit in hits)
        {
            if (hit.attachedRigidbody)
            {
                if (hit.attachedRigidbody.linearVelocity.y < -0.2f)
                    continue;
            }

            isGrounded = true;
            break;
        }

        if (isGrounded && !wasGrounded)
            lastGroundedTime = Time.time;

        Debug.DrawRay(groundPos, Vector3.down * 0.1f, isGrounded ? Color.green : Color.red);
    }

    bool CheckHeavyObjectAbove()
    {
        Vector3 ceilingPos = transform.position + Vector3.up * (col.size.y * 0.5f - 0.05f);

        Collider[] hits = Physics.OverlapSphere(ceilingPos, ceilingCheckRadius, ceilingLayer);

        foreach (var hit in hits)
        {
            if (hit == null || hit.transform == transform)
                continue;

            Rigidbody rbHit = hit.attachedRigidbody;
            if (rbHit != null)
            {
                float downVelocity = Vector3.Dot(rbHit.linearVelocity, Vector3.down);

                if (rbHit.mass > uncrouchMassLimit || downVelocity > upwardForceLimit)
                    return true;
            }
            else
            {
                return true;
            }
        }

        return false;
    }

    bool CeilingCheck()
    {
        Vector3 ceilingPos = transform.position + Vector3.up * (col.size.y * 0.5f - 0.05f);
        bool ceilingBlocked = Physics.CheckSphere(ceilingPos, ceilingCheckRadius, ceilingLayer);
        Debug.DrawRay(ceilingPos, Vector3.up, ceilingBlocked ? Color.red : Color.cyan);

        if (ceilingBlocked && !isCrouching)
            return true;

        return false;
    }
}