@implements IDisposable
@inject IJSRuntime JSRuntime

<circle @ref="circleErrorRef" style="fill: red; opacity: 0.6; r: 0.3px;"/>
<circle @ref="circleRef" @ondblclick="@(async () =>  await DoubleClick.InvokeAsync(Model))" class="@(IsSetting ? "setting" : "")" />
<text @ref="textRef" visibility="@(ShowName ? "visible" : "hidden")" class="@(IsSetting ? "setting" : "")">@Model?.Name</text>

@code {
    [Parameter, EditorRequired]
    public NodeModel Model { get; set; } = null!;

    [Parameter]
    public EventCallback<NodeModel> DoubleClick { get; set; }

    [Parameter]
    public EventCallback<NodeModel?> OnClick { get; set; }

    [CascadingParameter]
    protected bool MapIsActive { get; set; }

    [Parameter]
    public bool ShowName { get; set; }

    [Parameter]
    public EditorState EditorState { get; set; }

    private ElementReference circleRef;
    private ElementReference textRef;
    private ElementReference circleErrorRef;
    private DotNetObjectReference<Node> DotNetObj = null!;
    private bool IsError = false;

    private bool IsSetting => EditorState == EditorState.NavigationEdit || EditorState == EditorState.CreateStraighEdge || EditorState == EditorState.CreateCurveEdge || 
                              EditorState == EditorState.CreateDoubleCurveEdge || EditorState == EditorState.View;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);
        if (!firstRender) return;

        DotNetObj = DotNetObjectReference.Create(this);
        await JSRuntime.InvokeVoidAsync("AddMouseDownEventListener", DotNetObj, circleRef, nameof(OnMouseDown), false);
        await UpdatePosition(Model.X, Model.Y); 
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", circleErrorRef, "visibility", "hidden");
    }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        bool updateNode = false;
        if (parameters.TryGetValue(nameof(Model), out NodeModel? model) && ((Model?.Id ?? Guid.Empty) != (model?.Id ?? Guid.Empty)))
        {
            if (Model != null)
            {
                Model.PositionChanged -= Model_PositionChanged;
                Model.ActivedChanged -= ActivedChanged;
                Model.ErrorChanged -= ErrorChanged;
            }

            if (model != null)
            {
                updateNode = true;
                model.PositionChanged += Model_PositionChanged;
                model.ActivedChanged += ActivedChanged;
                model.ErrorChanged += ErrorChanged;
            }
        }
        await base.SetParametersAsync(parameters);
        if (updateNode && model != null) await UpdatePosition(model.X, model.Y);
    }

    private async void Model_PositionChanged() => await UpdatePosition(Model.X, Model.Y);

    [JSInvokable]
    public async Task OnMouseDown(int button, bool altKey, bool ctrlKey, bool shiftKey) => await OnClick.InvokeAsync(Model);

    private async Task UpdatePosition(double x, double y)
    {
        await JSRuntime.InvokeVoidAsync("SetNodePosition", circleRef, textRef, x, y);
        if (IsError) await JSRuntime.InvokeVoidAsync("SetNodePosition", circleErrorRef, textRef, x, y);
    }

    private async void ActivedChanged(bool state) => await JSRuntime.InvokeVoidAsync(state ? "AddSelected" : "RemoveSelected", circleRef);

    private async void ErrorChanged(bool state)
    {
        IsError = state;
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", circleErrorRef, "visibility", state ? "visible" : "hidden");
        if (IsError) await JSRuntime.InvokeVoidAsync("SetNodePosition", circleErrorRef, textRef, Model.X, Model.Y);
    }

    public void Dispose()
    {
        if (Model != null)
        {
            Model.PositionChanged -= Model_PositionChanged;
            Model.ActivedChanged -= ActivedChanged;
            Model.ErrorChanged -= ErrorChanged;
        }
        if (DotNetObj != null) DotNetObj.Dispose();
        GC.SuppressFinalize(this);
    }
}
