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 LineTool : ISketchTool
{
    private Point2? _start;
    private Point2? _current;
    private SnapResult? _startSnap;

    public string Name => "Line";

    public string Prompt => _start is null
        ? "Line Tool: choose start point"
        : "Line Tool: choose end point; hold Shift for tangent";

    public void PointerDown(ToolContext context, Point2 modelPoint)
    {
        var snap = context.SnapDetailed(modelPoint);
        var point = ConstrainEndPoint(context, snap.Point);
        if (_start is null)
        {
            _start = point;
            _startSnap = snap;
            _current = point;
            context.SetStatus(Prompt);
            context.Invalidate();
            return;
        }

        if (_start.Value.DistanceTo(point) > GeometryConstants.Epsilon)
        {
            context.Commands.Execute(
                context.Document,
                new AddEntityCommand(new LineEntity(Guid.NewGuid(), Layer.Cut.Name, _start.Value, point)));
        }

        _start = null;
        _startSnap = null;
        _current = null;
        context.SetStatus(Prompt);
        context.Invalidate();
    }

    public void PointerMove(ToolContext context, Point2 modelPoint)
    {
        var snap = context.SnapDetailed(modelPoint);
        _current = ConstrainEndPoint(context, snap.Point);
        context.Invalidate();
    }

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

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

    public void Cancel(ToolContext context)
    {
        _start = null;
        _startSnap = null;
        _current = null;
        context.SetStatus(Prompt);
        context.Invalidate();
    }

    public void RenderOverlay(DrawingContext context, ToolRenderContext renderContext)
    {
        if (_start is null)
        {
            return;
        }

        ToolDrawing.DrawPointMarker(context, renderContext.Transform, _start.Value);
        if (_current is not null)
        {
            context.DrawLine(
                ToolDrawing.PreviewPen,
                renderContext.Transform.ModelToScreen(_start.Value),
                renderContext.Transform.ModelToScreen(_current.Value));
        }
    }

    private Point2 ConstrainEndPoint(ToolContext context, Point2 point)
    {
        if (_start is null ||
            !context.TangentConstraint ||
            _startSnap?.Tangent is not { Length: > GeometryConstants.Epsilon } tangent)
        {
            return point;
        }

        var direction = tangent.Normalized();
        var distance = (point - _start.Value).Dot(direction);
        return _start.Value + (direction * distance);
    }
}
