﻿using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(CapsuleCollider))]
public class RockClimbingController : MonoBehaviour
{
    private enum ClimbState { None, Climbing, AtTop }
    private ClimbState state = ClimbState.None;

    [Header("Climbing Settings")]
    [SerializeField] private float climbSpeed = 5f;
    [SerializeField] private float wallDetectionDistance = 2f;
    [SerializeField] private float wallSnapOffset = 0.25f;
    [SerializeField] private LayerMask climbableLayer;

    [Header("Wall Probing")]
    [SerializeField] private float chestHeight = 1f;
    [SerializeField] private float wallStickRadius = 0.45f;

    [Header("Top Stop")]
    [SerializeField] private float topStopMargin = 0.05f;
    [SerializeField] private float topEpsilon = 0.05f;
    [SerializeField] private int topStableFramesRequired = 2;

    [Header("Mantle")]
    [SerializeField] private float mantleForward = 1.2f;
    [SerializeField] private float mantleUpExtra = 0.8f;
    [SerializeField] private float mantleClearanceUp = 0.1f;

    [Header("Input (Old Input Manager)")]
    [SerializeField] private string attachButtonLB = "LB";
    [SerializeField] private string climbTriggerLT = "LT";
    [SerializeField] private string horizontalAxis = "Horizontal";
    [SerializeField] private string verticalAxis = "Vertical";

    [Range(0f, 1f)]
    [SerializeField] private float ltThreshold = 0.5f;

    [SerializeField] private bool ltIsMinusOneToOne = false;

    [Header("UI / Prompt Bools")]
    public bool showClimbPrompt;
    public bool showMantlePrompt;
    public bool isClimbing;

    private Rigidbody rb;
    private CapsuleCollider capsule;

    private Vector3 wallNormal;
    private Vector3 wallPointOnPlane;
    private Collider currentWall;
    private float wallTopY;

    private int topStableFrames;
    private float detachLockUntil;

    private Vector2 moveInput;
    private bool mantleConsumed;

    public bool IsClimbing => state != ClimbState.None;

    private void Start()
    {
        rb = GetComponent<Rigidbody>();
        capsule = GetComponent<CapsuleCollider>();
        rb.interpolation = RigidbodyInterpolation.Interpolate;
    }

    private void Update()
    {
        moveInput.x = Input.GetAxis(horizontalAxis);
        moveInput.y = Input.GetAxis(verticalAxis);

        UpdatePromptBools();

        if (Input.GetButtonDown(attachButtonLB))
        {
            if (state == ClimbState.None)
            {
                if (TryFindWall(out RaycastHit hit))
                    Attach(hit);
            }
            else if (Time.time >= detachLockUntil)
            {
                Detach(true);
            }
        }
    }

    private void FixedUpdate()
    {
        if (state == ClimbState.None) return;

        rb.MoveRotation(Quaternion.LookRotation(-wallNormal, Vector3.up));

        bool ltPressed = ReadLT();
        bool atTop = IsAtTop();

        topStableFrames = atTop ? topStableFrames + 1 : 0;

        showMantlePrompt = atTop && !mantleConsumed;

        if (atTop && ltPressed && !mantleConsumed)
        {
            mantleConsumed = true;
            showMantlePrompt = false;
            Mantle();
            return;
        }

        if (!ltPressed)
            mantleConsumed = false;

        if (topStableFrames >= topStableFramesRequired)
        {
            state = ClimbState.AtTop;
            FreezeAtTop();
            return;
        }

        state = ClimbState.Climbing;
        ClimbMove();
    }

    private void Attach(RaycastHit hit)
    {
        state = ClimbState.Climbing;
        topStableFrames = 0;
        mantleConsumed = false;

        rb.useGravity = false;
        rb.linearVelocity = Vector3.zero;
        rb.angularVelocity = Vector3.zero;

        wallNormal = hit.normal;
        wallPointOnPlane = hit.point;
        currentWall = hit.collider;
        wallTopY = currentWall.bounds.max.y;

        rb.position = SnapToWall(rb.position);
        rb.rotation = Quaternion.LookRotation(-wallNormal, Vector3.up);

        detachLockUntil = Time.time + 0.15f;

        showClimbPrompt = false;
        showMantlePrompt = false;
        isClimbing = true;
    }

    private void Detach(bool push)
    {
        state = ClimbState.None;

        rb.useGravity = true;
        rb.linearVelocity = Vector3.zero;
        rb.angularVelocity = Vector3.zero;

        if (push)
            rb.AddForce(wallNormal * 2.5f, ForceMode.Impulse);

        currentWall = null;
        showMantlePrompt = false;
        isClimbing = false;
    }

    private void ClimbMove()
    {
        Vector3 right = Vector3.Cross(wallNormal, Vector3.up).normalized;
        Vector3 up = Vector3.Cross(right, wallNormal).normalized;

        Vector3 movement =
            (moveInput.x * right + moveInput.y * up) *
            climbSpeed * Time.fixedDeltaTime;

        Vector3 desired = rb.position + movement;

        float stopY = GetStopCenterY();
        if (!float.IsInfinity(stopY) && desired.y > stopY)
            desired.y = stopY;

        rb.MovePosition(SnapToWall(desired));
    }

    private void FreezeAtTop()
    {
        rb.linearVelocity = Vector3.zero;
        rb.angularVelocity = Vector3.zero;

        Vector3 pos = rb.position;
        float stopY = GetStopCenterY();
        if (!float.IsInfinity(stopY))
            pos.y = stopY;

        rb.MovePosition(SnapToWall(pos));
    }

    private void Mantle()
    {
        float halfHeight = GetCapsuleHalfHeight();

        Vector3 outward = new Vector3(wallNormal.x, 0f, wallNormal.z).normalized;

        float targetY = wallTopY + halfHeight + mantleClearanceUp + mantleUpExtra;

        Vector3 target =
            new Vector3(rb.position.x, targetY, rb.position.z) +
            outward * mantleForward;

        rb.linearVelocity = Vector3.zero;
        rb.angularVelocity = Vector3.zero;
        rb.MovePosition(target);

        Detach(false);
    }

    private bool ReadLT()
    {
        float lt = Input.GetAxis(climbTriggerLT);
        if (ltIsMinusOneToOne)
            lt = Mathf.InverseLerp(-1f, 1f, lt);

        return lt >= ltThreshold;
    }

    private bool IsAtTop()
    {
        if (currentWall == null) return false;

        float stopY = wallTopY - topStopMargin;
        return GetCapsuleTopY() >= stopY - topEpsilon;
    }

    private float GetStopCenterY()
    {
        if (currentWall == null) return float.PositiveInfinity;

        float stopY = wallTopY - topStopMargin;
        return stopY - GetCapsuleHalfHeight();
    }

    private Vector3 SnapToWall(Vector3 pos)
    {
        float dist = Vector3.Dot(pos - wallPointOnPlane, wallNormal);
        Vector3 corrected = pos - wallNormal * (dist - wallSnapOffset);
        corrected.y = pos.y;
        return corrected;
    }

    private bool TryFindWall(out RaycastHit hit)
    {
        Vector3 origin = rb.position + Vector3.up * chestHeight;

        return Physics.SphereCast(
            origin,
            wallStickRadius,
            transform.forward,
            out hit,
            wallDetectionDistance,
            climbableLayer,
            QueryTriggerInteraction.Ignore
        );
    }

    private float GetCapsuleHalfHeight()
    {
        float height = capsule.height * Mathf.Abs(transform.lossyScale.y);
        float radius = capsule.radius * Mathf.Abs(transform.lossyScale.y);
        return Mathf.Max(height * 0.5f, radius);
    }

    private float GetCapsuleTopY()
    {
        return transform.TransformPoint(capsule.center).y + GetCapsuleHalfHeight();
    }

    private void UpdatePromptBools()
    {
        isClimbing = state != ClimbState.None;

        if (state == ClimbState.None)
        {
            showMantlePrompt = false;
            showClimbPrompt = TryFindWall(out _);
        }
        else
        {
            showClimbPrompt = false;
            showMantlePrompt = IsAtTop() && !mantleConsumed;
        }
    }
}
