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

public class CameraController : MonoBehaviour
{
    [Header("Target")]
    [SerializeField] private Transform target;
    [SerializeField] private Vector3 targetOffset = new Vector3(0f, 1.5f, 0f);
    public Camera MainCamera;
    public Transform playerCamHome;

    [Header("Orbit")]
    [SerializeField] private float distance = 5f;
    [SerializeField] private float minDistance = 1f;
    [SerializeField] private float maxDistance = 8f;
    [SerializeField] private float rotationSpeed = 180f;
    [SerializeField] private float rotationOffset = 270f;
    [SerializeField] private float minPitch = -30f;
    [SerializeField] private float maxPitch = 75f;

    [Header("Zoom")]
    [SerializeField] private float zoomSpeed = 2f;
    [SerializeField] private float zoomSmoothness = 10f;

    [Header("Camera Collision")]
    [SerializeField] private LayerMask collisionLayers;
    [SerializeField] private float collisionRadius = 0.25f;
    [SerializeField] private float collisionOffset = 0.1f;
    [SerializeField] private float positionSmoothness = 15f;

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

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

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

    private float yaw;
    private float pitch = 20f;
    private float targetDistance;
    private Vector3 currentVelocity;

    public Vector3 Forward
    {
        get
        {
            Vector3 forward = MainCamera.transform.forward;
            forward.y = 0f;
            return forward.normalized;
        }
    }
    public Vector3 Right
    {
        get
        {
            Vector3 right = MainCamera.transform.right;
            right.y = 0f;
            return right.normalized;
        }
    }

    private void Start()
    {
        targetDistance = distance;

        Vector3 startingAngles = MainCamera.transform.eulerAngles;
        yaw = startingAngles.y + rotationOffset;
        pitch = startingAngles.x;

        if (pitch > 180f)
        {
            pitch -= 360f;
        }
    }

    private void LateUpdate()
    {
        if (target == null)
        {
            if (GameManager.Instance.PlayerController.DougBody != null)
                target = GameManager.Instance.PlayerController.DougBody.transform;
            else
                return;
        }

        UpdateOcclusion();
        UpdateFade();
        HandleRotation();
        HandleZoom();
        UpdateCameraPosition();
    }

    private void HandleRotation()
    {
        if (!Input.GetMouseButton(1))
        {
            UnityEngine.Cursor.visible = true;
            UnityEngine.Cursor.lockState = CursorLockMode.None;
            return;
        }

        UnityEngine.Cursor.visible = false;
        UnityEngine.Cursor.lockState = CursorLockMode.Locked;

        yaw += Input.GetAxis("Mouse X") * rotationSpeed * Time.deltaTime;
        pitch -= Input.GetAxis("Mouse Y") * rotationSpeed * Time.deltaTime;

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

    private void HandleZoom()
    {
        if (!Input.GetMouseButton(1))
        {
            return;
        }

        float scrollInput = Input.GetAxis("Mouse ScrollWheel");

        targetDistance -= scrollInput * zoomSpeed;
        targetDistance = Mathf.Clamp(targetDistance, minDistance, maxDistance);

        distance = Mathf.Lerp(distance, targetDistance, zoomSmoothness * Time.deltaTime);
    }

    private void UpdateCameraPosition()
    {
        Vector3 targetPosition = target.position + targetOffset;

        Quaternion orbitRotation = Quaternion.Euler(pitch, yaw, 0f);

        Vector3 desiredDirection = orbitRotation * Vector3.back;
        Vector3 desiredPosition = targetPosition + desiredDirection * distance;

        float correctedDistance = distance;

        if (Physics.SphereCast(targetPosition, collisionRadius, desiredDirection, out RaycastHit hit, distance, collisionLayers, QueryTriggerInteraction.Ignore))
        {
            correctedDistance = Mathf.Max(minDistance, hit.distance - collisionOffset);
        }

        Vector3 finalPosition = targetPosition + desiredDirection * correctedDistance;

        MainCamera.transform.position = Vector3.SmoothDamp(MainCamera.transform.position, finalPosition, ref currentVelocity, 1f / positionSmoothness);

        MainCamera.transform.rotation = Quaternion.LookRotation(targetPosition - MainCamera.transform.position, Vector3.up);
    }

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

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

        float dist = dir.magnitude;

        RaycastHit[] hits = Physics.RaycastAll(
            target.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
}
