using UnityEngine;
using UnityEngine.InputSystem;

/// <summary>
/// GAME 1303 Tier 7 — 3D first-person walking and looking. Store Simulator; also Apep's
/// Trial's 3D sections.
///
/// This is the only 3D script in the kit, so a few things differ from everything else:
/// CharacterController instead of Rigidbody2D, Vector3 instead of Vector2, and the camera
/// is a child that pitches while the body yaws.
///
/// SETUP
///  1. Player: an empty GameObject with a CharacterController (Height 1.8, Radius 0.3,
///     Center Y 0.9) + this script + PlayerInput (Send Messages). Tag "Player".
///  2. Child "CameraHolder" at Y 1.6 with the Main Camera inside it. Drag CameraHolder
///     into Camera Pivot.
///  3. Input: the default actions asset already has Move and Look. Look binds to Mouse
///     Delta and the right stick.
///  4. Cursor Lock hides and locks the mouse — press Escape to release it (handy in the
///     Editor; wire GameManager's pause to UnlockCursor).
///  5. Mouse Sensitivity is per-player taste — expose it in an options menu.
///
/// Deliberately simple: walk, look, crouch, jump. No head bob, no sprint stamina, no
/// slope handling beyond what CharacterController gives free. Sanad's game needs a body
/// to carry boxes with, not a shooter.
/// </summary>
[RequireComponent(typeof(CharacterController))]
public class FirstPersonController : MonoBehaviour
{
    [Header("Movement")]
    public float walkSpeed = 3.5f;
    public float sprintSpeed = 6f;
    public float crouchSpeed = 1.8f;
    public float acceleration = 12f;
    public float gravity = -20f;
    public float jumpHeight = 1.1f;

    [Header("Look")]
    public Transform cameraPivot;
    public float mouseSensitivity = 0.12f;
    public float stickSensitivity = 120f;
    public float minPitch = -80f;
    public float maxPitch = 80f;
    public bool invertY = false;

    [Header("Crouch")]
    public float standHeight = 1.8f;
    public float crouchHeight = 1.1f;
    public float crouchSpeedChange = 8f;

    [Header("Cursor")]
    public bool lockCursor = true;

    public bool InputEnabled { get; set; } = true;
    public bool IsGrounded => controller.isGrounded;

    private CharacterController controller;
    private Vector2 moveInput, lookInput;
    private Vector3 velocity;
    private float pitch;
    private bool sprinting, crouching, jumpQueued;
    private bool usingMouse = true;

    void Awake()
    {
        controller = GetComponent<CharacterController>();
        if (cameraPivot == null && Camera.main != null) cameraPivot = Camera.main.transform;
    }

    void Start() { if (lockCursor) LockCursor(); }

    // ---- Input System messages ----
    void OnMove(InputValue v)   { moveInput = InputEnabled ? v.Get<Vector2>() : Vector2.zero; }
    void OnLook(InputValue v)   { lookInput = v.Get<Vector2>(); usingMouse = Mouse.current != null && Mouse.current.delta.ReadValue().sqrMagnitude > 0.01f; }
    void OnSprint(InputValue v) { sprinting = v.isPressed; }
    void OnCrouch(InputValue v) { crouching = v.isPressed; }
    void OnJump(InputValue v)   { if (v.isPressed) jumpQueued = true; }

    void Update()
    {
        Look();
        Move();
        Crouch();

        if (Keyboard.current != null && Keyboard.current.escapeKey.wasPressedThisFrame) UnlockCursor();
    }

    void Look()
    {
        if (!InputEnabled || cameraPivot == null) return;

        float sens = usingMouse ? mouseSensitivity : stickSensitivity * Time.deltaTime;
        float yaw = lookInput.x * sens;
        float pitchDelta = lookInput.y * sens * (invertY ? 1f : -1f);

        transform.Rotate(Vector3.up * yaw);
        pitch = Mathf.Clamp(pitch + pitchDelta, minPitch, maxPitch);
        cameraPivot.localRotation = Quaternion.Euler(pitch, 0f, 0f);
    }

    void Move()
    {
        float speed = crouching ? crouchSpeed : (sprinting ? sprintSpeed : walkSpeed);
        Vector3 wish = InputEnabled
            ? (transform.right * moveInput.x + transform.forward * moveInput.y).normalized * speed
            : Vector3.zero;

        Vector3 horizontal = new Vector3(velocity.x, 0f, velocity.z);
        horizontal = Vector3.Lerp(horizontal, wish, acceleration * Time.deltaTime);
        velocity.x = horizontal.x;
        velocity.z = horizontal.z;

        if (controller.isGrounded)
        {
            if (velocity.y < 0f) velocity.y = -2f;   // keep it pinned to the floor
            if (jumpQueued && InputEnabled && !crouching)
                velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
        }
        jumpQueued = false;

        velocity.y += gravity * Time.deltaTime;
        controller.Move(velocity * Time.deltaTime);
    }

    void Crouch()
    {
        float target = crouching ? crouchHeight : standHeight;
        if (Mathf.Approximately(controller.height, target)) return;

        controller.height = Mathf.MoveTowards(controller.height, target, crouchSpeedChange * Time.deltaTime);
        controller.center = new Vector3(0f, controller.height * 0.5f, 0f);
        if (cameraPivot != null)
            cameraPivot.localPosition = new Vector3(0f, controller.height - 0.2f, 0f);
    }

    public void LockCursor()   { Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; }
    public void UnlockCursor() { Cursor.lockState = CursorLockMode.None;   Cursor.visible = true; }
}