@inject IJSRuntime JSRuntime

<g @ref="Ref" visibility="hidden">
    <defs>
        <marker id="arrowhead" markerWidth="5" markerHeight="4" refX="5" refY="2" orient="auto">
            <line x1="0" y1="0" x2="5" y2="2" style="stroke: #ff0000; stroke-width: 1px;" />
            <line x1="5" y1="2" x2="0" y2="4" style="stroke: #ff0000; stroke-width: 1px;" />
        </marker>
    </defs>
    <path @ref="PathRef" />
    <line @ref="SubPath1Ref" x1="@StartNode.X" x2="@SubStartNode.X" y1="@StartNode.Y" y2="@SubStartNode.Y" stroke="#ff0000" stroke-width="0.05" marker-end="url(#arrowhead)" />
    <line @ref="SubPath2Ref" x1="@EndNode.X" x2="@SubEndNode.X" y1="@EndNode.Y" y2="@SubEndNode.Y" stroke="#ff0000" stroke-width="0.05" marker-end="url(#arrowhead)" />
    <circle cx="@StartNode.X" cy="@StartNode.Y" />
    <circle cx="@EndNode.X" cy="@EndNode.Y" />
</g>

@code {
    private ElementReference Ref;
    private ElementReference PathRef;
    private ElementReference SubPath1Ref;
    private ElementReference SubPath2Ref;

    private (double X, double Y) StartNode;
    private (double X, double Y) SubStartNode;
    private (double X, double Y) EndNode;
    private (double X, double Y) SubEndNode;

    private TrajectoryDegree Degree;

    private (double X, double Y) ControlPoint1;
    private (double X, double Y) ControlPoint2;

    public CreateCurveEdgeStep CreateStep { get; set; } = CreateCurveEdgeStep.Hidden;

    public async Task Update(double x, double y)
    {
        if (CreateStep == CreateCurveEdgeStep.CreateSubStartEdge)
        {
            SubStartNode.X = x;
            SubStartNode.Y = y;
        }
        else if (CreateStep == CreateCurveEdgeStep.FinishCreateSubStartEdge)
        {
            EndNode.X = x;
            EndNode.Y = y;
            await CreateTangentPath();
        }
        else if (CreateStep == CreateCurveEdgeStep.CreateSubEndEdge)
        {
            SubEndNode.X = x;
            SubEndNode.Y = y;
            await CreateTangentPath();
        }

        StateHasChanged();
    }

    public async Task CreateSubEdgeAsync(double x, double y, TrajectoryDegree degree = TrajectoryDegree.Two)
    {
        Degree = degree;
        if (CreateStep == CreateCurveEdgeStep.Hidden)
        {
            await Visible();
            StartNode.X = x;
            StartNode.Y = y;
            SubStartNode.X = x;
            SubStartNode.Y = y;
            CreateStep = CreateCurveEdgeStep.CreateSubStartEdge;
        }
        else if (CreateStep == CreateCurveEdgeStep.FinishCreateSubStartEdge)
        {
            EndNode.X = x;
            EndNode.Y = y;
            SubEndNode.X = x;
            SubEndNode.Y = y;
            CreateStep = CreateCurveEdgeStep.CreateSubEndEdge;
            await JSRuntime.InvokeVoidAsync("ElementSetAttribute", SubPath2Ref, "visibility", "visible");
        }
        StateHasChanged();
    }

    public async Task FinishSubEdgeAsync(double x, double y)
    {
        if (CreateStep == CreateCurveEdgeStep.CreateSubStartEdge)
        {
            SubStartNode.X = x;
            SubStartNode.Y = y;

            EndNode.X = x;
            EndNode.Y = y;

            CreateStep = CreateCurveEdgeStep.FinishCreateSubStartEdge;
            await CreateTangentPath();
        }
        else if (CreateStep == CreateCurveEdgeStep.CreateSubEndEdge) CreateStep = CreateCurveEdgeStep.Finish;
    }

    public EdgeCreateModel GetEdgeCreateModel()
    {
        return new()
            {
                X1 = StartNode.X,
                Y1 = StartNode.Y,
                X2 = EndNode.X,
                Y2 = EndNode.Y,
                ControlPoint1X = ControlPoint1.X,
                ControlPoint1Y = ControlPoint1.Y,
                ControlPoint2X = ControlPoint2.X,
                ControlPoint2Y = ControlPoint2.Y,
                TrajectoryDegree = Degree,
            };
    }

    private async Task CreateTangentPath()
    {
        var data = $"M {StartNode.X} {StartNode.Y}";
        if(Degree == TrajectoryDegree.Two)
        {
            var cp = MapEditorHelper.CaculateIntersectionPoint(SubStartNode.X, SubStartNode.Y, StartNode.X, StartNode.Y, EndNode.X, EndNode.Y, SubEndNode.X, SubEndNode.Y);
            if (cp is null || CreateStep == CreateCurveEdgeStep.FinishCreateSubStartEdge) cp = MapEditorHelper.CaculateControlPoint(StartNode.X, StartNode.Y, EndNode.X, EndNode.Y, 45, 1 / Math.Sqrt(2));
            data = $"{data} Q {cp.Value.X} {cp.Value.Y} {EndNode.X} {EndNode.Y}";
            ControlPoint1 = (cp.Value.X, cp.Value.Y);
        }
        else if(Degree == TrajectoryDegree.Three)
        {
            if (CreateStep == CreateCurveEdgeStep.FinishCreateSubStartEdge)
            {
                ControlPoint1 = MapEditorHelper.CaculateControlPoint(StartNode.X, StartNode.Y, EndNode.X, EndNode.Y, 45, 1 / Math.Sqrt(2));
                ControlPoint2 = MapEditorHelper.CaculateControlPoint(StartNode.X, StartNode.Y, EndNode.X, EndNode.Y, -45, 1 / Math.Sqrt(2));
            }
            else (ControlPoint1.X, ControlPoint1.Y, ControlPoint2.X, ControlPoint2.Y) = MapEditorHelper.CaculateDoubleControlPoint(SubStartNode.X, SubStartNode.Y, StartNode.X, StartNode.Y, EndNode.X, EndNode.Y, SubEndNode.X, SubEndNode.Y);
            data = $"{data} C {ControlPoint1.X} {ControlPoint1.Y}, {ControlPoint2.X} {ControlPoint2.Y}, {EndNode.X} {EndNode.Y}";
        }
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", PathRef, "d", data);
        StateHasChanged();
    }

    public async Task Visible()
    {
        EndNode = (0, 0);
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", PathRef, "d", "");
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", SubPath1Ref, "visibility", "visible");
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", SubPath2Ref, "visibility", "hidden");
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", Ref, "visibility", "visible");
    }

    public async Task Hidden() 
    {
        CreateStep = CreateCurveEdgeStep.Hidden;
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", SubPath1Ref, "visibility", "hidden");
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", SubPath2Ref, "visibility", "hidden");
        await JSRuntime.InvokeVoidAsync("ElementSetAttribute", Ref, "visibility", "hidden");
    }

    public enum CreateCurveEdgeStep
    {
        Hidden,
        CreateSubStartEdge,
        FinishCreateSubStartEdge,
        CreateSubEndEdge,
        Finish,
    }
}
