using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class FPSController : MonoBehaviour
{
    [Header("References")]
    public Camera playerCamera;
    public Transform weaponHolder;

    [Header("Movement")]
    public float walkSpeed = 6f;
    public float sprintSpeed = 8.5f;
    public float jumpHeight = 1.2f;
    public float gravity = -22f;

    [Header("Look")]
    public float mouseSensitivity = 260f;
    public float maxLookAngle = 85f;

    [Header("Recoil")]
    public float recoilReturnSpeed = 12f;
    public float recoilSnappiness = 18f;

    [Header("Weapon Bob")]
    public float bobFrequency = 9f;
    public float bobHorizontalAmount = 0.018f;
    public float bobVerticalAmount = 0.014f;
    public float bobSmoothing = 10f;

    private CharacterController _controller;
    private Vector2 _lookAngles;
    private Vector2 _recoilTarget;
    private Vector2 _recoilCurrent;
    private float _verticalVelocity;
    private Vector3 _weaponHolderBaseLocalPosition;
    private float _bobTime;
    private bool _lookEnabled = true;

    public void Initialize(Camera controlledCamera, Transform controlledWeaponHolder)
    {
        playerCamera = controlledCamera;
        weaponHolder = controlledWeaponHolder;
    }

    private void Awake()
    {
        _controller = GetComponent<CharacterController>();
        if (playerCamera == null)
        {
            playerCamera = GetComponentInChildren<Camera>(true);
        }

        if (weaponHolder == null && playerCamera != null)
        {
            Transform foundHolder = playerCamera.transform.Find("WeaponHolder");
            if (foundHolder != null)
            {
                weaponHolder = foundHolder;
            }
        }
    }

    private void Start()
    {
        if (playerCamera != null)
        {
            _lookAngles.x = transform.eulerAngles.y;
            _lookAngles.y = playerCamera.transform.localEulerAngles.x;
            if (_lookAngles.y > 180f)
            {
                _lookAngles.y -= 360f;
            }
        }

        if (weaponHolder != null)
        {
            _weaponHolderBaseLocalPosition = weaponHolder.localPosition;
        }

        SetCursorLocked(true);
    }

    private void Update()
    {
        if (!_lookEnabled || playerCamera == null)
        {
            return;
        }

        UpdateLook();
        UpdateMovement();
        UpdateWeaponBob();
    }

    public void SetLookEnabled(bool enabled)
    {
        _lookEnabled = enabled;
        if (!enabled)
        {
            _recoilCurrent = Vector2.zero;
            _recoilTarget = Vector2.zero;
        }
    }

    public void SetCursorLocked(bool locked)
    {
        Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None;
        Cursor.visible = !locked;
    }

    public void ApplyRecoil(float verticalKick, float horizontalKick)
    {
        _recoilTarget += new Vector2(verticalKick, Random.Range(-horizontalKick, horizontalKick));
    }

    private void UpdateLook()
    {
        float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;

        _lookAngles.x += mouseX;
        _lookAngles.y = Mathf.Clamp(_lookAngles.y - mouseY, -maxLookAngle, maxLookAngle);

        _recoilTarget = Vector2.Lerp(_recoilTarget, Vector2.zero, recoilReturnSpeed * Time.deltaTime);
        _recoilCurrent = Vector2.Lerp(_recoilCurrent, _recoilTarget, recoilSnappiness * Time.deltaTime);

        transform.rotation = Quaternion.Euler(0f, _lookAngles.x + _recoilCurrent.y, 0f);
        playerCamera.transform.localRotation = Quaternion.Euler(_lookAngles.y - _recoilCurrent.x, 0f, 0f);
    }

    private void UpdateMovement()
    {
        Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
        input = Vector3.ClampMagnitude(input, 1f);

        float moveSpeed = Input.GetKey(KeyCode.LeftShift) ? sprintSpeed : walkSpeed;
        Vector3 move = transform.TransformDirection(input) * moveSpeed;

        if (_controller.isGrounded && _verticalVelocity < 0f)
        {
            _verticalVelocity = -2f;
        }

        if (_controller.isGrounded && Input.GetButtonDown("Jump"))
        {
            _verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
        }

        _verticalVelocity += gravity * Time.deltaTime;
        move.y = _verticalVelocity;

        _controller.Move(move * Time.deltaTime);
    }

    private void UpdateWeaponBob()
    {
        if (weaponHolder == null)
        {
            return;
        }

        Vector3 horizontalVelocity = _controller.velocity;
        horizontalVelocity.y = 0f;
        float speed = horizontalVelocity.magnitude;

        if (_controller.isGrounded && speed > 0.15f)
        {
            _bobTime += Time.deltaTime * bobFrequency * (speed / walkSpeed);
            Vector3 bobOffset = new Vector3(
                Mathf.Cos(_bobTime * 0.5f) * bobHorizontalAmount,
                Mathf.Sin(_bobTime) * bobVerticalAmount,
                0f);

            weaponHolder.localPosition = Vector3.Lerp(
                weaponHolder.localPosition,
                _weaponHolderBaseLocalPosition + bobOffset,
                bobSmoothing * Time.deltaTime);
        }
        else
        {
            weaponHolder.localPosition = Vector3.Lerp(
                weaponHolder.localPosition,
                _weaponHolderBaseLocalPosition,
                bobSmoothing * Time.deltaTime);
        }
    }
}
