[Header("Movement")]
public float moveSpeed = 5f;
public float jumpForce = 10f;
public float airControl = 0.4f;

[Header("Ground Check")]
public float groundCheckDistance = 0.3f;
public LayerMask groundLayer;
[Header("Jump Grace")]
public float coyoteTime = 0.15f;
private float coyoteTimer;

[Header("Rotation")]
public float rotationSpeed = 10f;

private Rigidbody2D rb;
private GrapplingHook grapple;

private bool isGrounded;
private Vector2 groundNormal;
private float moveInput;
private bool jumpPressed;
private bool jumpedThisFrame;

void Awake()
{
    rb = GetComponent<Rigidbody2D>();
    grapple = GetComponent<GrapplingHook>();
}

void Update()
{
    moveInput = Input.GetAxis("Horizontal");

    if (Input.GetButtonDown("Jump"))
        jumpPressed = true;
}

void FixedUpdate()
{
    GroundCheck();
    HandleRotation();

    jumpedThisFrame = false;

    if (jumpPressed && coyoteTimer > 0f)
    {
        Jump();
        coyoteTimer = 0f;
        jumpedThisFrame = true;
    }

    if (!jumpedThisFrame && (grapple == null || !grapple.isGrappling))
        HandleMovement();

    jumpPressed = false;
}

// ================= CORE =================

void GroundCheck()
{
    Vector2 gravityDir = -transform.up;

    RaycastHit2D hit = Physics2D.Raycast(
        rb.position,
        gravityDir,
        groundCheckDistance,
        groundLayer
    );

    if (hit.collider != null)
    {
        isGrounded = true;
        groundNormal = hit.normal;
        coyoteTimer = coyoteTime; // reset grace timer
    }
    else
    {
        isGrounded = false;
        coyoteTimer -= Time.fixedDeltaTime;
        groundNormal = -gravityDir;
    }
}

void HandleMovement()
{
    Vector2 gravityDir = -transform.up;
    Vector2 tangent = new Vector2(-gravityDir.y, gravityDir.x);

    float control = isGrounded ? 1f : airControl;

    Vector2 tangentVelocity = tangent * moveInput * moveSpeed * control;

    // Preserve gravity-direction velocity
    Vector2 gravityVelocity =
        (Vector2)Vector3.Project(rb.linearVelocity, gravityDir);

    rb.linearVelocity = tangentVelocity + gravityVelocity;
}

void Jump()
{
    Vector2 jumpDir = -(-transform.up); // same as transform.up
    rb.linearVelocity = Vector2.zero;
    rb.AddForce(jumpDir * jumpForce, ForceMode2D.Impulse);
}

void HandleRotation()
{
    Vector2 upDir = -(-transform.up); // face away from gravity

    float targetAngle =
        Mathf.Atan2(upDir.y, upDir.x) * Mathf.Rad2Deg - 90f;

    float newAngle = Mathf.LerpAngle(
        rb.rotation,
        targetAngle,
        rotationSpeed * Time.fixedDeltaTime
    );

    rb.MoveRotation(newAngle);
}