using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using Sirenix.OdinInspector;
using Unity.Cinemachine;
using UnityEngine;
using UnityEngine.UIElements;
using Vector2 = UnityEngine.Vector2;
using Vector3 = UnityEngine.Vector3;

public class LockOnManager : MonoBehaviour{

    private CinemachineTargetGroup.Target playerTarget;

    // Lock On settings
    [Space(5)]
    public float lockOnRange = 40f;
    public float lockOnMaxAngle = 70f;
    
    // Lock On Tracking
    [Space(10)] public GameObject lockonGameObject; // Needed because nulling the Target below doesn't actually empty it out
    [ReadOnly] public CinemachineTargetGroup.Target lockonTarget;
    public CinemachineTargetGroup targetGroup;
    
    [Space(5)]
    public List<GameObject> lockOnTargets = new List<GameObject>(); 

    // UI
    [ShowInInspector]
    public UIDocument lockOnDocument;
    private Label elementLabelName;
    private VisualElement elementRoot; 
    
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start(){
        // Save the player target object to track later
        playerTarget = targetGroup.Targets[0];
        
        // Quick check for things in lock-on target that aren't lock-onable
        if (lockonGameObject != null && lockonTarget.Object.GetComponent<ILockOnTarget>() == null) {
            Debug.LogError($"Game Object {lockonTarget.Object.name} does not implement the ILockOnTarget interface!");
        }
        
        elementRoot = lockOnDocument.rootVisualElement.Query<VisualElement>("LockOnGroup");
        elementLabelName = lockOnDocument.rootVisualElement.Query<Label>("LockOnName").First();
        
        // Add all nearby game objects to lock-on eligible list
        GameObject[] allGameObjects = GameObject.FindObjectsByType<GameObject>(0, 0);
        
        foreach (GameObject thisObject in allGameObjects)
        {
            if (Vector3.Distance(transform.position, thisObject.transform.position) < lockOnRange) {
                if (thisObject.GetComponent<ILockOnTarget>() != null) {
                    lockOnTargets.Add(thisObject);
                }
            }
        }
    }

    void Update(){
        if (lockonGameObject && lockonTarget.Object.GetComponent<ILockOnTarget>() == null) {
            Debug.LogError($"Game Object {lockonTarget.Object.name} does not implement the ILockOnTarget interface!");
        }
        
        // Find the current lock-on target and increase it's weight to the .15f max slowly
        // They start at 0 weight when the lock-on adds them to the group
        if (lockonGameObject) {
            CinemachineTargetGroup.Target currentTarget = targetGroup.Targets.Find(target => target == lockonTarget);
            currentTarget.Weight = Mathf.MoveTowards(currentTarget.Weight, .15f, .5f * Time.deltaTime);
        }

        // If a target is not the current lock on target, lower their targeting weight. When low enough to not cause a sharp jitter, remove them.
        for (int i = 1; i < targetGroup.Targets.Count; i++) {
            if (targetGroup.Targets[i] == lockonTarget || targetGroup.Targets[i] == playerTarget){
                continue;
            }
            
            if (targetGroup.Targets[i].Weight < 0.001f) {
                StartCoroutine(RemoveFromTargetAtFrameEnd(targetGroup.Targets[i]));
                continue;
            }

            targetGroup.Targets[i].Weight = Mathf.MoveTowards(targetGroup.Targets[i].Weight, 0f, 1f * Time.deltaTime);
        }
    }

    IEnumerator RemoveFromTargetAtFrameEnd(CinemachineTargetGroup.Target indexOf){
        yield return new WaitForEndOfFrame();
        targetGroup.Targets.Remove(indexOf);
    }

    public void ChangeLockOnTarget(){
        Transform cameraTransform = Camera.main.transform;
    
        // If there is no target, simply find the closest to the center of the camera
        GameObject closestTarget = null;
        float lowestDistanceToCenter = Mathf.Infinity;
        
        foreach (GameObject target in lockOnTargets) {
            // Skip the current target if one exists
            if (lockonGameObject != null && lockonTarget.Object.gameObject == target) {
                continue;
            }
            
            // Skip targets currently behind objects.
            Physics.Raycast(cameraTransform.position,
                cameraTransform.position.DirectionTo(target.transform.position), out RaycastHit hit);

            if (hit.transform != target.transform) {
                continue;
            }

            // Skip targets outside lock on angle 
            float angleFromCameraForward = Vector3.Angle(cameraTransform.forward, cameraTransform.position.DirectionTo(target.transform.position));
            if (angleFromCameraForward > lockOnMaxAngle) {
                continue;
            }                

            // Find how close this target is from the center of the screen
            Vector3 targetScreenPoint = Camera.main.WorldToScreenPoint(target.transform.position);
            float distanceFromScreenCenter = targetScreenPoint.Flatten(null, null, 0f).magnitude - new Vector3(Screen.width, Screen.height, 0f).magnitude / 2f;
            distanceFromScreenCenter = Mathf.Abs(distanceFromScreenCenter);

            // Debug.Log($"{target.name}: {distanceFromScreenCenter} pixels, {angleFromCameraForward} degrees");

            // Set the new target to closest to screen
            if (distanceFromScreenCenter < lowestDistanceToCenter) {
                lowestDistanceToCenter = distanceFromScreenCenter;
                closestTarget = target;
            }
        }

        // Catch exception from nothing being found
        if (!closestTarget) {
            Debug.LogWarning("Lock-on attempted, but no lock on target was found viable.");
            return;
        }
        
        // Create a new Target for the Target Group
        var newTarget = new CinemachineTargetGroup.Target{
            Object = closestTarget.transform,
            Radius = 1f,
            Weight = 0f
        };
        
        // Set the new target variables
        lockonTarget = newTarget;
        lockonGameObject = closestTarget.gameObject;

        targetGroup.Targets.Add(newTarget);
    }

    public void RemoveLockOnTarget(){
        lockonTarget = null;
        lockonGameObject = null;
    }

    void LateUpdate(){
        if (lockonGameObject) {
            // This is just test logic to get an image above a lock on.
            // TODO: Replace with something less silly
            Vector2 screenPos = RuntimePanelUtils.CameraTransformWorldToPanel(
                lockOnDocument.rootVisualElement.panel,
                lockonTarget.Object.GetComponent<ILockOnTarget>().GetReticlePosition(),
                Camera.main
            );

            // Set name
            elementLabelName.text = lockonTarget.Object.name;

            // Set position (add the width/height of the element)
            elementRoot.style.top = new StyleLength(screenPos.y - 25f); // Was elementRoot.resolvedStyle.height * .7f
            elementRoot.style.left = new StyleLength(screenPos.x - elementRoot.resolvedStyle.width / 2f);

            // Set enabled
            elementRoot.style.display = new StyleEnum<DisplayStyle>(DisplayStyle.Flex);
        } else {
            elementRoot.style.display = new StyleEnum<DisplayStyle>(DisplayStyle.None);
        }
    }
}
