using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
using static Unity.Cinemachine.CinemachineTargetGroup;
using static UnityEngine.GraphicsBuffer;

public class CameraController : MonoBehaviour
{
    public float camTargetRotation { get; private set; } = 0;
    public CameraControlMode CamControlMode { get; private set; } = CameraControlMode.SnapRotate;
    public Camera MainCamera;

    private bool isCamRotating = false;

    [Header("Free Cam Settings")]
    public float mouseSensitivity = 200f;
    public float minPitch = -30f;
    public float maxPitch = 60f;
    public float distance = 5f;
    public float heightOffset = -0.5f;
    public float followSpeed = 10f;
    public float maxDistance = 6f;
    public float minDistance = 1f;
    public float zoomSpeed = 5f;
    private float yaw;
    private float pitch;
    private float currentDistance;
    private float distanceVelocity;

    [Header("Occlusion Settings")]
    public LayerMask obstructionMask;
    public float fadedAlpha = 0.25f;
    public float fadeSpeed = 5f;

    private Dictionary<Renderer, float> currentAlpha =
        new Dictionary<Renderer, float>();

    private Dictionary<Renderer, float> targetAlpha =
        new Dictionary<Renderer, float>();

    public Transform playerCamHome;
    Transform dougBody;

    private void Update()
    {
        UpdateOcclusion();
        UpdateFade();

        if (Input.GetMouseButton(1))
        {
            HandleFreeCamRotation();
        }
    }

    void Start()
    {
        Vector3 angles = transform.eulerAngles;

        yaw = angles.y;
        pitch = angles.x;
        currentDistance = distance;
    }

    /// <summary>
    /// Trigger cam rotation
    /// </summary>
    public void RotateCam()
    {
        if (!isCamRotating)
        {
            float currentY = playerCamHome.transform.rotation.eulerAngles.y;
            camTargetRotation = currentY + 90f;
            isCamRotating = true;
        }
    }

    /// <summary>
    /// Set cam rotation immediately
    /// </summary>
    void SnapToRotation(float rotation)
    {
        if (CamControlMode == CameraControlMode.SnapRotate)
        {
            playerCamHome.RotateAround(
                transform.position,
                Vector3.up,
                rotation
            );

            camTargetRotation = rotation;
        }
    }

    /// <summary>
    /// Move cam to desired rotation (every frame)
    /// </summary>
    void HandleSnapCamRotation()
    {
        float currentY = playerCamHome.transform.rotation.eulerAngles.y;
        float step = 200 * Time.deltaTime;
        float newY = Mathf.MoveTowardsAngle(currentY, camTargetRotation, step);

        playerCamHome.RotateAround(
            transform.position,
            Vector3.up,
            newY - currentY
        );

        if (Mathf.Abs(Mathf.DeltaAngle(newY, camTargetRotation)) < 0.01f)
        {
            isCamRotating = false;
        }
    }

    /// <summary>
    /// Move cam to desired free rotation (every frame)
    /// </summary>
    void HandleFreeCamRotation()
    {
        if (dougBody == null)
        {
            if (GameManager.Instance.PlayerController.DougBody == null)
                return;

            dougBody = GameManager.Instance.PlayerController.DougBody.transform;
        }

        Vector3 target = new Vector3(dougBody.position.x, dougBody.position.y + heightOffset, dougBody.position.z);

        // Mouse Input
        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = Input.GetAxis("Mouse Y");

        yaw += mouseX * mouseSensitivity * Time.deltaTime;
        pitch -= mouseY * mouseSensitivity * Time.deltaTime;

        pitch = Mathf.Clamp(pitch, minPitch, maxPitch);

        // Zoom
        float scroll = Input.GetAxis("Mouse ScrollWheel");

        if (Mathf.Abs(scroll) > 0.0001f)
        {
            distance -= scroll * zoomSpeed;
            distance = Mathf.Clamp(distance, minDistance, maxDistance);
        }

        currentDistance = Mathf.SmoothDamp(
            currentDistance,
            distance,
            ref distanceVelocity,
            0.05f);

        // Rotation
        Quaternion rotation = Quaternion.Euler(pitch, yaw, 0);

        // Desired Position
        Vector3 offset = rotation * Vector3.forward * -currentDistance;
        Vector3 desiredPosition = target + offset;

        // Smooth Follow
        playerCamHome.transform.position = Vector3.Lerp(
            playerCamHome.transform.position,
            desiredPosition,
            followSpeed * Time.deltaTime
        );

        // Look at player
        playerCamHome.transform.LookAt(target + Vector3.up);
    }

    void UpdateOcclusion()
    {
        if (dougBody == null)
        {
            if (GameManager.Instance.PlayerController.DougBody == null)
                return;

            dougBody = GameManager.Instance.PlayerController.DougBody.transform;
        }

        if (dougBody == null) return;

        // Reset all renderers to visible
        foreach (Renderer r in new List<Renderer>(targetAlpha.Keys))
        {
            targetAlpha[r] = 1f;
        }

        Vector3 dir =
            MainCamera.transform.position - dougBody.position;

        float dist = dir.magnitude;

        RaycastHit[] hits = Physics.RaycastAll(
            dougBody.position,
            dir.normalized,
            dist,
            obstructionMask,
            QueryTriggerInteraction.Ignore);

        foreach (RaycastHit hit in hits)
        {
            Renderer r = hit.collider.GetComponent<Renderer>();

            if (r == null)
                continue;

            if (!currentAlpha.ContainsKey(r))
            {
                currentAlpha[r] = 1f;
                targetAlpha[r] = 1f;
            }

            targetAlpha[r] = fadedAlpha;
        }
    }

    void UpdateFade()
    {
        List<Renderer> renderers =
            new List<Renderer>(currentAlpha.Keys);

        foreach (Renderer r in renderers)
        {
            float current = currentAlpha[r];
            float target = targetAlpha[r];

            current = Mathf.Lerp(
                current,
                target,
                Time.deltaTime * fadeSpeed);

            currentAlpha[r] = current;

            foreach (Material mat in r.materials)
            {
                Color c = mat.color;
                c.a = current;

                if (Mathf.Abs(current - targetAlpha[r]) < 0.05f)
                {
                    c.a = targetAlpha[r];
                }

                mat.color = c;
            }

            // Cleanup once fully restored
            if (Mathf.Abs(current - 1f) < 0.01f &&
                Mathf.Abs(target - 1f) < 0.01f)
            {
                currentAlpha.Remove(r);
                targetAlpha.Remove(r);
            }
        }
    }
}

public enum CameraControlMode
{
    SnapRotate,
    FreeRotate
}
