using System.Collections;
using TMPro;
using Unity.VisualScripting.Antlr3.Runtime;
using UnityEngine;
using static UnityEngine.GraphicsBuffer;

public class PlayerController : MonoBehaviour
{
    public GameObject DougBody;

    public Tool[] tools;
    float toolUseTimestamp = 0;

    public float walkSpeed = 2;
    public float sprintMultiplier = 1.5f;
    public float gravity = 0.05f;
    public float rotationSpeed = 40;
    public float jumpHeight = 200f;

    Interactable nearestInteractable;
    public CameraController cameraController;
    private CharacterController characterController;
    private Vector3 moveDir;
    private float verticalVelocity;
    private bool hasControl = true; // set this to false if we want to stop reading player inputs
    private bool canUseTools = true; // set this to false if we want to stop reading player tool use
    private bool isSprinting = false;
    private bool paused = false;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        characterController = GetComponent<CharacterController>();
        cameraController = GetComponent<CameraController>();
        SwitchTools(GameManager.Instance.PlayerManager.CurrentToolIndex);
    }

    // Update is called once per frame
    void Update()
    {
        moveDir = Vector3.zero;

        // TODO: Move input detection somewhere else
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            TogglePause();
        }

        if (hasControl)
        {
            if (!tools[GameManager.Instance.PlayerManager.CurrentToolIndex].inUse)
            {
                RotatePlayerTowardMouse();
                TryInteract();
            }

            CheckWeaponChange();
            ApplyWalk();
            TryJump();
        }

        if (canUseTools)
        {
            ToolUseDetector();
        }

        CheckSprint();
        ApplySpeed();
        ApplyGravity();
        DoMovement();
    }

    public void TogglePause()
    {
        if (!paused)
        {
            Cursor.lockState = CursorLockMode.None;
            Time.timeScale = 0;
            canUseTools = false;
            hasControl = false;
            GameManager.Instance.MenuController.ShowScreen("Pause");
        }
        else
        {
            Time.timeScale = 1;
            canUseTools = true;
            hasControl = true;
            GameManager.Instance.MenuController.HideAllScreens();
        }

        paused = !paused;
    }

    #region Movement Calculation/Input Processing
    /// <summary>
    /// If player presses space, jump
    /// </summary>
    private void TryJump()
    {
        if (characterController.isGrounded && Input.GetKeyDown(KeyCode.Space))
        {
            verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * -gravity);
        }
    }

    /// <summary>
    /// Checks if shift is being held, isSprinting is true if it is
    /// </summary>
    private void CheckSprint()
    {
        if (Input.GetKey(KeyCode.LeftShift))
        {
            isSprinting = true;
        }
        else
        {
            isSprinting = false;
        }
    }

    /// <summary>
    /// Checks if weapon is being switched by scroll wheel
    /// </summary>
    private void CheckWeaponChange()
    {
        if (!Input.GetMouseButton(1))
        {
            if (Input.mouseScrollDelta.y > 0)
            {
                SwitchTools(true);
            }
            else if (Input.mouseScrollDelta.y < 0)
            {
                SwitchTools(false);
            }
        }
    }

    /// <summary>
    /// Apply walking inputs to moveDir
    /// </summary>
    void ApplyWalk()
    {
        float verticalMovement = 0;
        float horizontalMovement = 0;

        if (!tools[GameManager.Instance.PlayerManager.CurrentToolIndex].inUse)
        {
            verticalMovement = Input.GetAxisRaw("Vertical");
            horizontalMovement = Input.GetAxisRaw("Horizontal");
        }
        Vector3 input = Vector3.ClampMagnitude(new Vector3(horizontalMovement, 0f, verticalMovement), 1f);

        moveDir = cameraController.Forward * input.z + cameraController.Right * input.x;
    }

    /// <summary>
    /// Apply speed to moveDir
    /// </summary>
    void ApplySpeed()
    {
        if (isSprinting)
            moveDir *= (walkSpeed * sprintMultiplier);
        else
            moveDir *= walkSpeed;
    }

    /// <summary>
    /// Apply gravity to moveDir
    /// </summary>
    void ApplyGravity()
    {
        // Makes sure we're grounded (built-in character controller is wonky)
        if (characterController.isGrounded && verticalVelocity < 0)
        {
            verticalVelocity = -2f;
        }

        verticalVelocity += -gravity;
    }

    /// <summary>
    /// Detect input for tool use
    /// </summary>
    void ToolUseDetector()
    {
        if (!tools[GameManager.Instance.PlayerManager.CurrentToolIndex].inUse)
        {
            if (Input.GetMouseButtonDown(0))
            {
                tools[GameManager.Instance.PlayerManager.CurrentToolIndex].Use();
                tools[GameManager.Instance.PlayerManager.CurrentToolIndex].inUse = true;
                toolUseTimestamp = Time.time + tools[GameManager.Instance.PlayerManager.CurrentToolIndex].useTimeSec; // fuck coroutines fr fr
            }
            else if (Input.GetMouseButtonDown(1))
            {
                tools[GameManager.Instance.PlayerManager.CurrentToolIndex].UseAlt();
                tools[GameManager.Instance.PlayerManager.CurrentToolIndex].inUse = true;
                toolUseTimestamp = Time.time + tools[GameManager.Instance.PlayerManager.CurrentToolIndex].altUseTimeSec;
            }
        }
        else
        {
            if (Time.time > toolUseTimestamp)
            {
                tools[GameManager.Instance.PlayerManager.CurrentToolIndex].inUse = false;
            }
        }
    }

    /// <summary>
    /// Face character graphic toward mouse
    /// </summary>
    void RotatePlayerTowardMouse()
    {
        /*Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Plane plane = new Plane(cameraController.MainCamera.transform.forward,DougBody.transform.position);

        if (plane.Raycast(ray, out var distance))
        {
            Vector3 hitPoint = ray.GetPoint(distance);
            Vector3 direction = hitPoint - DougBody.transform.position;
            direction.y = 0f;

            ///DougBody.transform.LookAt(new Vector3(hitPoint.x, DougBody.transform.position.y, hitPoint.z));
            if (direction.sqrMagnitude > 0.001f)
            {
                Quaternion targetRotation =
                    Quaternion.LookRotation(direction);

                DougBody.transform.rotation = Quaternion.Slerp(
                    DougBody.transform.rotation,
                    targetRotation,
                    rotationSpeed * Time.deltaTime
                );
            }
        }*/

        // When rotating the cam with right mouse, look forward
        if (Input.GetMouseButton(1))
        {
            Quaternion targetRotation = Quaternion.LookRotation(cameraController.Forward);

            DougBody.transform.rotation = Quaternion.Slerp(
                DougBody.transform.rotation,
                targetRotation,
                rotationSpeed * Time.deltaTime
            );
        }
        else
        {
            Camera cam = cameraController.MainCamera;

            Ray ray = cam.ScreenPointToRay(Input.mousePosition);

            Vector3 targetPoint;

            // 1. Try real world hit first (most stable)
            if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
            {
                targetPoint = hit.point;
            }
            else
            {
                // 2. Fallback to plane at player height
                Plane plane = new Plane(Vector3.up, DougBody.transform.position);

                if (!plane.Raycast(ray, out float enter))
                    return;

                targetPoint = ray.GetPoint(enter);
            }

            // 3. Flatten direction so we only rotate on Y axis
            Vector3 direction = targetPoint - DougBody.transform.position;
            direction.y = 0f;

            if (direction.sqrMagnitude < 0.001f)
                return;

            // 4. Convert to rotation
            Quaternion targetRotation = Quaternion.LookRotation(direction.normalized);

            // 5. Smooth rotation
            DougBody.transform.rotation = Quaternion.Slerp(
                DougBody.transform.rotation,
                targetRotation,
                rotationSpeed * Time.deltaTime
            );
        }
    }

    /// <summary>
    /// Try to interact by detecting nearest interactable
    /// </summary>
    void TryInteract()
    {
        if (Input.GetKeyDown(KeyCode.E) && nearestInteractable != null)
        {
            nearestInteractable.Interact();
        }
    }

    /// <summary>
    /// Move character with moveDir 
    /// </summary>
    void DoMovement()
    {
        moveDir.y = verticalVelocity;
        characterController.Move(moveDir * Time.deltaTime);
    }

    /// <summary>
    /// Switches the tools in hand by taking an index
    /// 
    /// <param name="toolIndex">Index of the tool to switch to</param>
    /// <param name="defaultToUp">
    /// If true, try the next tool if the tool at toolIndex is not unlocked yet. If false,
    /// try the previous tool if the tool at toolIndex is not unlocked.
    /// </param>
    /// </summary>
    private void SwitchTools(int toolIndex, bool defaultToUp = true)
    {
        if (toolIndex < 0 || toolIndex >= tools.Length)
        {
            toolIndex = 0;
        }

        // disable all tools
        foreach (Tool tool in tools)
        {
            tool.gameObject.SetActive(false);
        }

        if (GameManager.Instance.PlayerManager.IsToolUnlocked(tools[toolIndex].ToolType))
        {
            // load new tool
            GameManager.Instance.PlayerManager.CurrentToolIndex = toolIndex;
            tools[GameManager.Instance.PlayerManager.CurrentToolIndex].gameObject.SetActive(true);
        }
        else
        {
            // try next index
            if (defaultToUp)
                SwitchTools(toolIndex + 1, defaultToUp);
            else
                SwitchTools(toolIndex - 1, defaultToUp);
        }
    }

    /// <summary>
    /// Switches the tools in hand by moving up or down the list
    /// 
    /// <param name="up">Are we moving up (-1) the list?</param>
    /// </summary>
    private void SwitchTools(bool up)
    {
        // calculate index of next tool
        int newIndex = up ? GameManager.Instance.PlayerManager.CurrentToolIndex + 1 : GameManager.Instance.PlayerManager.CurrentToolIndex - 1;

        if (newIndex < 0)
        {
            newIndex = tools.Length - 1;
        }
        else if (newIndex >= tools.Length)
        {
            newIndex = newIndex % tools.Length;
        }

        // disable current tool
        tools[GameManager.Instance.PlayerManager.CurrentToolIndex].gameObject.SetActive(false);

        if (GameManager.Instance.PlayerManager.IsToolUnlocked(tools[newIndex].ToolType))
        {
            // load new tool
            GameManager.Instance.PlayerManager.CurrentToolIndex = newIndex;
            tools[GameManager.Instance.PlayerManager.CurrentToolIndex].gameObject.SetActive(true);
        }
        else
        {
            // try next tool 
            if (up) SwitchTools(newIndex + 1);
            else SwitchTools(newIndex - 1, false);
        }
    }
    #endregion

    #region External Controls
    /// <summary>
    /// Pass movement into character controller, lets us expose function without exposing
    /// whole char controller
    /// </summary>
    public void CharacterControllerMove(Vector3 movement)
    {
        characterController.Move(movement);
    }

    /// <summary>
    /// Set the character controller's position
    /// </summary>
    public void CharacterControllerSetPosition(Vector3 pos)
    {
        characterController.enabled = false;
        transform.position = pos;
        characterController.enabled = true;
    }

    /// <summary>
    /// Auto-walk the character in the specified direction. Uses character's walk
    /// speed and camera rotation (per-frame)
    /// </summary>
    public void WalkInDirection(Vector3 direction)
    {
        Vector3 forwardDir = direction;
        forwardDir.y = 0; // don't move on the y axis

        forwardDir *= walkSpeed;

        characterController.Move(forwardDir * Time.deltaTime);
    }

    /// <summary>
    /// Sets the character's "hasControl" bool. All input is ignored when false
    /// </summary>
    public void SetCharacterControl(bool hasControl)
    {
        this.hasControl = hasControl;
    }

    /// <summary>
    /// Sets the character's "canUseTools" bool. Tool use input is ignored when false
    /// </summary>
    public void SetCharacterCanUseTools(bool canUseTools)
    {
        this.canUseTools = canUseTools;
    }

    /// <summary>
    /// Gets the character's "hasControl" bool. All input is ignored when false
    /// </summary>
    public bool GetCharacterHasControl()
    {
        return hasControl;
    }

    /// <summary>
    /// Expose the character controller's velocity
    /// </summary>
    public Vector3 GetVelocity()
    {
        return characterController.velocity;
    }

    /// <summary>
    /// Bounce off of object (upward), reverses downward velocity and multiplies it by
    /// the bounciness
    /// <param name="bounciness">How bouncy the surface is</param>
    /// </summary>
    public void Bounce(float bounciness)
    {
        if (verticalVelocity < -3f)
        {
            float bounceForce = -verticalVelocity * bounciness;
            verticalVelocity = bounceForce;
            moveDir.y = bounceForce;
        }
    }
    #endregion

    private void OnTriggerEnter(Collider other)
    {
        Interactable interactable = other.GetComponent<Interactable>();

        // store nearest interactable if it exists
        if (interactable != null)
        {
            nearestInteractable = interactable;
            interactable.MoveInsideRange();
        }
    }

    private void OnTriggerExit(Collider other)
    {
        // just null out interactables when we leave an interactable trigger (we cant and shouldn't overlap interactables)
        Interactable interactable = other.GetComponent<Interactable>();

        // store nearest interactable if it exists
        if (interactable != null && nearestInteractable != null)
        {
            nearestInteractable.MoveOutsideRange();
            nearestInteractable = null;
        }
    }

    private void OnControllerColliderHit(ControllerColliderHit hit)
    {
        BouncePad bouncePad = hit.gameObject.GetComponent<BouncePad>();
        if (bouncePad != null)
        {
            Bounce(bouncePad.bounciness);
        }
    }
}

