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

[ExecuteAlways]
public class GameManager : MonoBehaviour
{
    public Transform ConnectionParent;
    public Transform NodeParent;
    public GameObject NodePrefab;

    public static GameManager Instance { get; private set; }

    public bool regenerateOnChange = false;

    [HideInInspector] public float maxConnectionLength = 6;
    [HideInInspector] public int nodeCount = 100;
    [HideInInspector] public int hoverRadiusCon = 50;
    [HideInInspector] public int hoverRadiusNode = 50;

    [SerializeField]
    [HideInInspector] public List<Connection> connections = new List<Connection>();
    [HideInInspector] public List<Node> nodes = new List<Node>();

    [Serializable]
    public class Connection
    {
        public Node nodeA, nodeB;
        public bool allowed = true;
        public bool hovered = false;
        public LineRenderer lineRenderer;
    }

    void Awake()
    {
        if (Instance != null && Instance != this)
        {
            if (Application.isPlaying)
                Destroy(gameObject);
            return;
        }

        Instance = this;
        if (Application.isPlaying)
            DontDestroyOnLoad(gameObject);

    }

    public void GenerateAlongSphere()
    {
        connections.Clear();
        nodes.Clear();

        for (int i = NodeParent.childCount - 1; i >= 0; i--)
            DestroyImmediate(NodeParent.GetChild(i).gameObject);

        float radius = 20f;
        float goldenRatio = (1f + Mathf.Sqrt(5f)) / 2f;
        float angleIncrement = 2f * Mathf.PI * goldenRatio;

        for (int i = 0; i < nodeCount; i++)
        {
            float t = (float)i / nodeCount; // von 0 bis 1
            float inclination = Mathf.Acos(1f - 2f * t);
            float azimuth = angleIncrement * i;

            float x = Mathf.Sin(inclination) * Mathf.Cos(azimuth);
            float y = Mathf.Sin(inclination) * Mathf.Sin(azimuth);
            float z = Mathf.Cos(inclination);

            Vector3 pos = new Vector3(x, y, z) * radius;

            var auto = PrefabUtility.InstantiatePrefab(NodePrefab, NodeParent) as GameObject;
            auto.transform.localPosition = pos;
            nodes.Add(auto.GetComponent<Node>());
        }
    }

    public void GenerateConnections()
    {
        connections.Clear();

        foreach (LineRenderer line in ConnectionParent.GetComponentsInChildren<LineRenderer>())
            DestroyImmediate(line.gameObject);

        foreach (Node nodeA in nodes)
        {
            if (nodeA == null) continue;
            foreach (Node nodeB in nodes)
            {
                if (nodeB == null) continue;
                bool conExists = false;
                if (nodeA == nodeB || Math.Abs(Vector3.Distance(nodeA.transform.position, nodeB.transform.position)) > maxConnectionLength)
                    continue;

                foreach (Connection con in connections)
                {
                    if ((con.nodeA == nodeA && con.nodeB == nodeB) || (con.nodeA == nodeB && con.nodeB == nodeA))
                    {
                        conExists = true;
                        break;
                    }
                }
                if (!conExists)
                {
                    var dummy = new GameObject("dummy");
                    dummy.transform.SetParent(ConnectionParent);
                    var newCon = new Connection
                    {
                        nodeA = nodeA,
                        nodeB = nodeB,
                        lineRenderer = dummy.AddComponent<LineRenderer>()
                    };

                    newCon.lineRenderer.enabled = false;
                    newCon.lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
                    newCon.lineRenderer.positionCount = 3;

                    connections.Add(newCon);
                }
            }
                   
        }
    }

    private void Update()
    {
        foreach (var con in connections)
        {
            if(Application.isPlaying && !con.allowed)
            {
                con.lineRenderer.enabled = false;
                continue;
            }

            float width = (con.hovered ? 0.6f : 0.3f) * (con.allowed ? 1f : 0.3f);

            con.lineRenderer.startColor = con.allowed ? con.nodeA.transform.GetChild(0).GetComponent<Renderer>().sharedMaterial.color : new Color(0.2f, 0.2f, 0.2f);
            con.lineRenderer.endColor = con.allowed ? con.nodeB.transform.GetChild(0).GetComponent<Renderer>().sharedMaterial.color : new Color(0.2f, 0.2f, 0.2f);
            con.lineRenderer.startWidth = width;
            con.lineRenderer.endWidth = width;

            con.lineRenderer.SetGreatCircleArc(con.nodeA.transform.position, con.nodeB.transform.position, 5, 20.5f);
            con.lineRenderer.enabled = true;
        }
    }
}

#if UNITY_EDITOR
[CustomEditor(typeof(GameManager))]
public class GameManagerEditor : Editor
{
    int clickedConIdx = -1;

    public override void OnInspectorGUI()
    { 
        DrawDefaultInspector(); 

        GameManager gm = (GameManager)target;

        GUILayout.Label("Generation Parameters", EditorStyles.boldLabel);

        EditorGUI.BeginChangeCheck();
        gm.nodeCount = EditorGUILayout.IntSlider("Node Count", gm.nodeCount, 0, 200);
        gm.maxConnectionLength = EditorGUILayout.Slider("Max Connection Length", gm.maxConnectionLength, 0, 100);
        if (EditorGUI.EndChangeCheck() && gm.regenerateOnChange)
        {
            gm.GenerateAlongSphere();
            gm.GenerateConnections();
            SceneView.RepaintAll();
        }
        

        GUILayout.Space(10);
        GUILayout.Label("Connection Gizmos", EditorStyles.boldLabel);
        
        gm.hoverRadiusCon = EditorGUILayout.IntSlider("Hover Radius (Connection)", gm.hoverRadiusCon, 0, 100);
        gm.hoverRadiusNode = EditorGUILayout.IntSlider("Hover Radius (Node)", gm.hoverRadiusNode, 0, 100);




        GUILayout.BeginHorizontal();

        if (GUILayout.Button("Generate Nodes"))
        {
            gm.GenerateAlongSphere();
            SceneView.RepaintAll();
        }

        if (GUILayout.Button("Generate Connections"))
        {
            gm.GenerateConnections();
            SceneView.RepaintAll();
        }
        
        if (GUILayout.Button("Generate Both")) 
        {
            gm.GenerateAlongSphere(); 
            gm.GenerateConnections();
            SceneView.RepaintAll();
        }

        GUILayout.EndHorizontal();
        
        GUILayout.BeginHorizontal();


        if (GUILayout.Button("Fetch all Nodes"))
        {
            Node[] allNodes = gm.NodeParent.GetComponentsInChildren<Node>();

            Debug.Log("Added " + (allNodes.Length - gm.nodeCount) + " new nodes");

            gm.nodes = allNodes.ToList();
            gm.nodeCount = allNodes.Length;
        }

        GUILayout.EndHorizontal();

    }

    private void OnSceneGUI()
    {
        Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;

        GameManager gm = FindFirstObjectByType<GameManager>();
        if (gm == null) return;

        GameObject selected = Selection.activeGameObject;
        bool allowHover = false;

        if (selected == gm.gameObject)
            allowHover = true;
        else
        {
            Node node = selected?.GetComponent<Node>();
            if (node != null && gm.nodes.Contains(node))
                allowHover = true;
        }

        if (!allowHover) return;

        if (gm.connections == null || gm.connections.Count == 0) return;
        Camera cam = SceneView.lastActiveSceneView.camera;
        if (cam == null) return;

        Vector2 mouse = Event.current.mousePosition;
        mouse.y = cam.pixelHeight - mouse.y;
        Vector3 mousePos = new Vector3(mouse.x, mouse.y, 0);

        int closestIdx = -1;
        float closestDist = float.MaxValue;
        bool cancelHover = false;

        // Hover-Ermittlung Connections
        for (int i = 0; i < gm.connections.Count; i++)
        {
            gm.connections[i].hovered = false;

            if (cancelHover)
                continue;
            
            var con = gm.connections[i];
            Vector3 a = cam.WorldToScreenPoint(con.nodeA.transform.position);
            Vector3 b = cam.WorldToScreenPoint(con.nodeB.transform.position);

            Vector3 ab = b - a;
            Vector3 am = mousePos - a;
            float t = Mathf.Clamp01(Vector3.Dot(am, ab) / ab.sqrMagnitude);
            Vector3 proj = a + t * ab;
            float dist = Vector3.Distance(mousePos, proj);

            if (Vector3.Distance(a, mousePos) <= gm.hoverRadiusNode || Vector3.Distance(b, mousePos) <= gm.hoverRadiusNode)
                cancelHover = true;

            if (dist < closestDist && dist < gm.hoverRadiusCon)
            {
                closestDist = dist;
                closestIdx = i;
            }
        }

        if (closestIdx < 0)
            cancelHover = true;


        if (!cancelHover)
            gm.connections[closestIdx].hovered = true;


        // Klick-Behandlung
        Event e = Event.current;


        if (e.type == EventType.MouseDown && e.button == 0 && !cancelHover) 
        {
            clickedConIdx = closestIdx;
        }

        if (e.type == EventType.MouseUp && e.button == 0)
        {
            if (closestIdx == clickedConIdx && !cancelHover)
            {
                gm.connections[closestIdx].allowed = !gm.connections[closestIdx].allowed;
                e.Use(); // Event als verarbeitet markieren
            }

            clickedConIdx = -1;
        }

        // SceneView kontinuierlich aktualisieren
        SceneView.RepaintAll();
    }
}
#endif


public static class LineRendererExtensions
{
    // Zeichnet eine Quadratic Bezier Kurve (Start, Control, End) mit N Punkten
    public static void SetQuadraticBezier(this LineRenderer line, Vector3 start, Vector3 control, Vector3 end, int segments)
    {
        line.positionCount = segments + 1;

        for (int i = 0; i <= segments; i++)
        {
            float t = i / (float)segments;
            // Quadratic Bezier Formel
            Vector3 point = (1 - t) * (1 - t) * start +
                            2 * (1 - t) * t * control +
                            t * t * end;

            line.SetPosition(i, point);
        }
    }

    public static void SetGreatCircleArc(this LineRenderer line, Vector3 start, Vector3 end, int segments, float radius)
    {
        // Normalize to sphere radius
        Vector3 startNorm = start.normalized * radius;
        Vector3 endNorm = end.normalized * radius;

        line.positionCount = segments + 1;

        for (int i = 0; i <= segments; i++)
        {
            float t = i / (float)segments;
            // spherical linear interpolation (slerp) between start and end
            Vector3 point = Vector3.Slerp(startNorm, endNorm, t);
            line.SetPosition(i, point);
        }
    }
}