using System;
using UnityEngine;
using UnityEngine.Assertions;

public enum ArcOrientation
{
    HORIZONTAL,
    VERTICAL,
}

[RequireComponent(typeof(LineRenderer))]
public class EditableArc : MonoBehaviour
{
    [SerializeField] private ArcOrientation orientation = ArcOrientation.HORIZONTAL;
    [SerializeField, Range(5, 50)] private int samples = 15;
    [SerializeField] private float visualRadius = 1f;
    [SerializeField] private SliderKnob knob;
    [SerializeField] private float knobSensitiviy = 1f;

    [SerializeField] private Vector2 rotationMinMax = new Vector2(-30f, 30f);
    private LineRenderer lineRenderer;

    public Observer<float> Value { get; private set; } = new(0);
    public Vector2 RotationMinMax => rotationMinMax;

    public Vector3 normal => orientation == ArcOrientation.HORIZONTAL ? transform.up : transform.forward;
    public Vector3 tangent => orientation == ArcOrientation.HORIZONTAL ? transform.forward : transform.up;

    public Vector3 ToKnobVector => Quaternion.AngleAxis(Value.Value, normal) * tangent;

    private void Awake()
    {
        lineRenderer = GetComponent<LineRenderer>();

        Value.AddListener(UpdateArc);
        Value.AddListener(UpdateKnobPosition);

        // Set default rotation to middle if outside
        if (Value.Value < rotationMinMax.x || Value.Value > rotationMinMax.y)
            Value.Value = (rotationMinMax.x + rotationMinMax.y) / 2f;

        Assert.IsNotNull(knob, $"No knob on {this}");
        knob.OnDrag += PointerDraggedOnKnob;

        // Initial
        UpdateKnobPosition(Value);
    }

    // Moves the knob to the right position based on the angle on the arc
    private void UpdateKnobPosition(float angle)
    {
        Vector3 dir = Quaternion.AngleAxis(angle, normal) * tangent;
        Vector3 knobPos = transform.position + dir;

        knob.transform.position = knobPos;
    }

    private void OnDestroy()
    {
        Value.RemoveListener(UpdateArc);
        Value.RemoveListener(UpdateKnobPosition);
        knob.OnDrag -= PointerDraggedOnKnob;
    }

    private void PointerDraggedOnKnob(SliderKnob knob)
    {
        // Amount mouse have moved since last frame - ie. mouse velocity
        Vector2 mouseMovement = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));

        Vector3 start = Quaternion.AngleAxis(rotationMinMax.x, normal) * tangent;
        Vector3 end = Quaternion.AngleAxis(rotationMinMax.y, normal) * tangent;

        // Use calculus to calculate differentialkvotient tangent at angle
        Vector3 v1 = SamplePointOnArcAngle(start, end, visualRadius, Value.Value);
        Vector3 v2 = SamplePointOnArcAngle(start, end, visualRadius, Value.Value - 1f);
        Vector3 arcTangent3D = (v2 - v1).normalized;
        Vector2 arcTangent = orientation == ArcOrientation.HORIZONTAL ? new Vector2(arcTangent3D.x, arcTangent3D.z) : new Vector2(arcTangent3D.x, arcTangent3D.y);

        // i have no idea why but somehow this needs to be here idk
        // if (orientation == ArcOrientation.VERTICAL)
        //     arcTangent.x *= -1f;

        Debug.DrawRay(knob.transform.position, arcTangent3D, Color.yellow, 5f);
        Vector3 screenRay = Camera.main.ScreenPointToRay(Input.mousePosition).direction;
        Vector3 barreltToAim = Quaternion.AngleAxis(Value.Value, normal) * tangent;
        float dot = Vector3.Dot(transform.forward, Camera.main.transform.forward) * -1f;

        float sign = orientation == ArcOrientation.HORIZONTAL ? 1f : -1f;
        mouseMovement.y *= sign * Mathf.Sin(dot);
        float delta = Vector2.Dot(arcTangent, mouseMovement) * knobSensitiviy * Mathf.Sign(dot);

        float newAngle = ClampAngle(Value.Value + delta, rotationMinMax.x, rotationMinMax.y);
        Value.Value = newAngle;
    }

    private void Update()
    {
        UpdateArc(Value.Value);
    }

    private void UpdateArc(float rotation)
    {
        float angle = rotationMinMax.y - rotationMinMax.x;
        float v = (Mathf.PI - Value * Mathf.Deg2Rad) / 2f;
        Vector3 start = Quaternion.AngleAxis(rotationMinMax.x, normal) * tangent;
        Vector3 end = Quaternion.AngleAxis(rotationMinMax.y, normal) * tangent;

        // Sample LineRenderer points
        lineRenderer.positionCount = samples + 1;
        Vector3[] positions = new Vector3[samples + 1];
        float stepSize = 1f / samples;
        float t = 0;
        for (int i = 0; i <= samples; i++)
        {
            positions[i] = SamplePointOnArc(start, end, visualRadius, t);
            // Debug.Log($"t: = {t}, pos: {positions[i]}");
            t += stepSize;
        }

        lineRenderer.SetPositions(positions);

        // Set looop
        if (angle >= 360)
            lineRenderer.loop = true;
        else
            lineRenderer.loop = false;
    }

    public Vector3 SamplePointOnArc(Vector3 startPoint, Vector3 endPoint, float radius, float t)
    {
        float angle = Mathf.Lerp(rotationMinMax.x, rotationMinMax.y, t);
        Vector3 dir = Quaternion.AngleAxis(angle, normal) * tangent;
        return transform.position + dir.normalized * radius;
    }

    public Vector3 SamplePointOnArcAngle(Vector3 startPoint, Vector3 endPoint, float radius, float angle)
    {
        Vector3 dir = Quaternion.AngleAxis(angle, normal) * tangent;
        return transform.position + dir.normalized * radius;
    }

    public static float ClampAngle(float current, float min, float max)
    {
        float dtAngle = Mathf.Abs(((min - max) + 180) % 360 - 180);
        float hdtAngle = dtAngle * 0.5f;
        float midAngle = min + hdtAngle;

        float offset = Mathf.Abs(Mathf.DeltaAngle(current, midAngle)) - hdtAngle;
        if (offset > 0)
            current = Mathf.MoveTowardsAngle(current, midAngle, offset);
        return current;
    }
}