Code/UI/Components/VectorEdit.razor
@using Sandbox.UI
@inherits Panel
@implements IEdit
@implements IGraphStyle
@namespace Nodebox.UI

<root class="@(TypeLibrary.GetType(Type).Name.ToLower())">
    <div class="numeric-group" @ref=NumericGroupRef>
        @for (int i = 0; i < VectorSize; i++) {
            <NumericEdit Type=@BaseVectorType TooltipOverride=@($"{GetTooltipForIndex(i)} in {Tooltip}")/>
        }
    </div>
    @if (Type == typeof(Color)) {
        <div class="color-strip" Tooltip=@Tooltip @ref=ColorStripRef/>
    }
</root>

@code
{
    
    public Panel NumericGroupRef { get; protected set; }
    public Panel ColorStripRef { get; protected set; }

    [Change(nameof(OnValueChanged))]
    public object Value { get; set; }
    public object ValueImmediate =>  Type switch {
        var x when x == typeof(Vector2) =>
            new Vector2(GetValueImmediateAt<float>(0), GetValueImmediateAt<float>(1)),
        var x when x == typeof(Vector3) =>
            new Vector3(GetValueImmediateAt<float>(0), GetValueImmediateAt<float>(1), GetValueImmediateAt<float>(2)),
        var x when x == typeof(Vector4) =>
            new Vector4(GetValueImmediateAt<float>(0), GetValueImmediateAt<float>(1), GetValueImmediateAt<float>(2), GetValueImmediateAt<float>(3)),
        var x when x == typeof(Vector2Int) =>
            new Vector2Int(GetValueImmediateAt<int>(0), GetValueImmediateAt<int>(1)),
        var x when x == typeof(Vector3Int) =>
            new Vector3Int(GetValueImmediateAt<int>(0), GetValueImmediateAt<int>(1), GetValueImmediateAt<int>(2)),

        var x when x == typeof(Angles) =>
            new Angles(GetValueImmediateAt<float>(0), GetValueImmediateAt<float>(1), GetValueImmediateAt<float>(2)),
        var x when x == typeof(Color) =>
            new Color(GetValueImmediateAt<float>(0), GetValueImmediateAt<float>(1), GetValueImmediateAt<float>(2), GetValueImmediateAt<float>(3)),
        var x when x == typeof(Rotation) =>
            new Rotation(GetValueImmediateAt<float>(0), GetValueImmediateAt<float>(1), GetValueImmediateAt<float>(2), GetValueImmediateAt<float>(3)),
        _ => throw new NotImplementedException(),
    };

    public void OnValueChanged(object oldValue, object newValue) {
        if (newValue == null) { return; }
        Assert.AreEqual(newValue.GetType(), Type, $"{newValue.GetType()} =/= {Type}");

        switch (0) {
            case var _ when BaseVectorType == typeof(float):
                foreach (var pair in PackingExtensions.UnpackAnyFloat(newValue).Enumerate()) {
                    SetValueAt(pair.Index, pair.Item);
                }
                
                break;
            
            case var _ when BaseVectorType == typeof(int):
                foreach (var pair in PackingExtensions.UnpackAnyInt(newValue).Enumerate()) {
                    SetValueAt(pair.Index, pair.Item);
                }
                
                break;
        }
    }

    public object GetValueAt(int index) => ((NumericEdit)NumericGroupRef.Children.ElementAt(index)).Value;
    public T GetValueAt<T>(int index) => (T)GetValueAt(index);
    public void SetValueAt(int index, object value) {
        if (!NumericGroupRef.IsValid()) { return; }
        ((NumericEdit)NumericGroupRef.Children.ElementAt(index)).Value = value;
    }
    @* public void SetValueAt<T>(int index, T value) => SetValueAt(index, value); *@
    
    public object GetValueImmediateAt(int index) => ((NumericEdit)NumericGroupRef.Children.ElementAt(index)).ValueImmediate;
    public T GetValueImmediateAt<T>(int index) => (T)GetValueImmediateAt(index);

    public Type Type { get; set; }

    public int VectorSize => Type switch {
        var x when x == typeof(Vector2) => 2,
        var x when x == typeof(Vector3) => 3,
        var x when x == typeof(Vector4) => 4,
        var x when x == typeof(Vector2Int) => 2,
        var x when x == typeof(Vector3Int) => 3,

        var x when x == typeof(Angles) => 3,
        var x when x == typeof(Color) => 4,
        var x when x == typeof(Rotation) => 4,

        _ => throw new NotImplementedException(),
    };
    
    public Type BaseVectorType => Type switch {
        var x when x == typeof(Vector2) => typeof(float),
        var x when x == typeof(Vector3) => typeof(float),
        var x when x == typeof(Vector4) => typeof(float),
        var x when x == typeof(Vector2Int) => typeof(int),
        var x when x == typeof(Vector3Int) => typeof(int),
        
        var x when x == typeof(Angles) => typeof(float),
        var x when x == typeof(Color) => typeof(float),
        var x when x == typeof(Rotation) => typeof(float),

        _ => throw new NotImplementedException(),
    };
    
    public string GetTooltipForIndex(int index) => index switch {
        0 when Type == typeof(Vector2) || Type == typeof(Vector2Int) => "X",
        1 when Type == typeof(Vector2) || Type == typeof(Vector2Int) => "Y",

        0 when Type == typeof(Vector3) || Type == typeof(Vector3Int) => "X",
        1 when Type == typeof(Vector3) || Type == typeof(Vector3Int) => "Y",
        2 when Type == typeof(Vector3) || Type == typeof(Vector3Int) => "Z",
        
        0 when Type == typeof(Vector4) || Type == typeof(Rotation) => "X",
        1 when Type == typeof(Vector4) || Type == typeof(Rotation) => "Y",
        2 when Type == typeof(Vector4) || Type == typeof(Rotation) => "Z",
        3 when Type == typeof(Vector4) || Type == typeof(Rotation) => "W",

        0 when Type == typeof(Angles) => "Pitch",
        1 when Type == typeof(Angles) => "Yaw",
        2 when Type == typeof(Angles) => "Roll",

        0 when Type == typeof(Color) => "Red",
        1 when Type == typeof(Color) => "Green",
        2 when Type == typeof(Color) => "Blue",
        3 when Type == typeof(Color) => "Alpha",

        _ => throw new NotImplementedException(),
    };

    public static bool IsSupported(Type type) => type switch {
        var x when x == typeof(Vector2) => true,
        var x when x == typeof(Vector3) => true,
        var x when x == typeof(Vector4) => true,
        var x when x == typeof(Vector2Int) => true,
        var x when x == typeof(Vector3Int) => true,
        
        var x when x == typeof(Angles) => true,
        var x when x == typeof(Color) => true,
        var x when x == typeof(Rotation) => true,

        _ => false,
    };

    protected override void OnAfterTreeRender(bool firstTime) {
        if (firstTime) {
            Value ??= TypeLibrary.GetType(Type).CreateDefault();
        }

        Tooltip = this.ConstructTooltip();
        
        if (!firstTime) { return; }

        if (NumericGroupRef.IsValid()) {
            OnValueChanged(null, Value);

            foreach (var child in NumericGroupRef.Children) {
                var numericEdit = (NumericEdit)child;
                numericEdit.AddEventListener("onchange", (ev) => {
                    ev.StopPropagation();

                    CreateEvent("onchange", ValueImmediate);
                });
                
                numericEdit.AddEventListener("onsubmit", (ev) => {
                    ev.StopPropagation();

                    Value = Type switch {
                        var x when x == typeof(Vector2) =>
                            new Vector2(GetValueAt<float>(0), GetValueAt<float>(1)),
                        var x when x == typeof(Vector3) =>
                            new Vector3(GetValueAt<float>(0), GetValueAt<float>(1), GetValueAt<float>(2)),
                        var x when x == typeof(Vector4) =>
                            new Vector4(GetValueAt<float>(0), GetValueAt<float>(1), GetValueAt<float>(2), GetValueAt<float>(3)),
                        var x when x == typeof(Vector2Int) =>
                            new Vector2Int(GetValueAt<int>(0), GetValueAt<int>(1)),
                        var x when x == typeof(Vector3Int) =>
                            new Vector3Int(GetValueAt<int>(0), GetValueAt<int>(1), GetValueAt<int>(2)),

                        var x when x == typeof(Angles) =>
                            new Angles(GetValueAt<float>(0), GetValueAt<float>(1), GetValueAt<float>(2)),
                        var x when x == typeof(Color) =>
                            new Color(GetValueAt<float>(0), GetValueAt<float>(1), GetValueAt<float>(2), GetValueAt<float>(3)),
                        var x when x == typeof(Rotation) =>
                            new Rotation(GetValueAt<float>(0), GetValueAt<float>(1), GetValueAt<float>(2), GetValueAt<float>(3)),
                        _ => throw new NotImplementedException(),
                    };
                    
                    CreateEvent("onsubmit", Value);
                });

                @* numericEdit.AddEventListener("oncancel", (ev) => {
                    ev.StopPropagation();
                    
                    OnValueChanged(null, Value);
                    
                    CreateEvent("oncancel", Value);
                }); *@
            }
        }
    }


    [PanelEvent]
    protected virtual void OnChangeEvent(PanelEvent ev) {
        if (ColorStripRef.IsValid()) {
            var color = (Color)ValueImmediate;
            ColorStripRef.Style.Set("background-color", color.ToString(true, true));
        }
    }

    protected override int BuildHash() => System.HashCode.Combine( Type );
}