using Avalonia.Input;
using Avalonia.Media;
using TraceCad.Core.Commands;
using TraceCad.Core.Geometry;
using TraceCad.Core.Model;

namespace TraceCad.App.Tools;

public sealed class Arc3PointTool : ISketchTool
{
    private readonly List<Point2> _points = new();
    private readonly List<SnapResult> _snaps = new();
    private Point2? _current;

    public string Name => "Arc 3P";

    public string Prompt => _points.Count switch
    {
        0 => "Arc 3P: choose start point",
        1 => "Arc 3P: choose end point",
        _ => "Arc 3P: choose arc height; hold Shift for tangent start"
    };

    public void PointerDown(ToolContext context, Point2 modelPoint)
    {
        var snap = context.SnapDetailed(modelPoint);
        _points.Add(snap.Point);
        _snaps.Add(snap);

        if (_points.Count == 3)
        {
            if (TryCreateArc(context, _points[2], out var arc))
            {
                context.Commands.Execute(
                    context.Document,
                    new AddEntityCommand(new ArcEntity(
                        Guid.NewGuid(),
                        Layer.Cut.Name,
                        arc.Center,
                        arc.Radius,
                        arc.StartAngleDeg,
                        arc.EndAngleDeg,
                        arc.IsClockwise)));
                context.SetStatus("Arc 3P: arc created");
            }
            else
            {
                context.SetStatus("Arc 3P: points are collinear");
            }

            _points.Clear();
            _snaps.Clear();
        }
        else
        {
            context.SetStatus(Prompt);
        }

        context.Invalidate();
    }

    public void PointerMove(ToolContext context, Point2 modelPoint)
    {
        _current = context.Snap(modelPoint);
        context.Invalidate();
    }

    public void PointerUp(ToolContext context, Point2 modelPoint)
    {
    }

    public void KeyDown(ToolContext context, Key key)
    {
    }

    public void Cancel(ToolContext context)
    {
        _points.Clear();
        _snaps.Clear();
        _current = null;
        context.SetStatus(Prompt);
        context.Invalidate();
    }

    public void RenderOverlay(DrawingContext context, ToolRenderContext renderContext)
    {
        foreach (var point in _points)
        {
            ToolDrawing.DrawPointMarker(context, renderContext.Transform, point);
        }

        if (_points.Count == 1 && _current is not null)
        {
            context.DrawLine(
                ToolDrawing.PreviewPen,
                renderContext.Transform.ModelToScreen(_points[0]),
                renderContext.Transform.ModelToScreen(_current.Value));
        }

        if (_points.Count == 2 && _current is not null)
        {
            var pen = TryCreateArc(renderContext, _current.Value, out var arc)
                ? ToolDrawing.PreviewPen
                : ToolDrawing.InvalidPen;

            if (arc.Radius > GeometryConstants.Epsilon)
            {
                ToolDrawing.DrawArc(
                    context,
                    renderContext.Transform,
                    arc.Center,
                    arc.Radius,
                    arc.StartAngleDeg,
                    arc.EndAngleDeg,
                    arc.IsClockwise,
                    pen);
            }
        }
    }

    private bool TryCreateArc(ToolContext context, Point2 pointOnArc, out ArcDefinition arc)
    {
        if (context.TangentConstraint &&
            _snaps.FirstOrDefault()?.Tangent is { Length: > GeometryConstants.Epsilon } tangent)
        {
            return GeometryHelpers.TryCreateArcFromStartTangentAndEndPoint(
                _points[0],
                tangent,
                _points[1],
                pointOnArc,
                out arc);
        }

        return GeometryHelpers.TryCreateArcFromThreePoints(_points[0], pointOnArc, _points[1], out arc);
    }

    private bool TryCreateArc(ToolRenderContext context, Point2 pointOnArc, out ArcDefinition arc)
    {
        if (context.TangentConstraint &&
            _snaps.FirstOrDefault()?.Tangent is { Length: > GeometryConstants.Epsilon } tangent)
        {
            return GeometryHelpers.TryCreateArcFromStartTangentAndEndPoint(
                _points[0],
                tangent,
                _points[1],
                pointOnArc,
                out arc);
        }

        return GeometryHelpers.TryCreateArcFromThreePoints(_points[0], pointOnArc, _points[1], out arc);
    }
}
