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 TangentConstraintTool : ISketchTool
{
    private Entity? _first;

    public string Name => "Tangent";

    public string Prompt => _first is null
        ? "Tangent: choose line or arc"
        : "Tangent: choose the other line/arc";

    public void PointerDown(ToolContext context, Point2 modelPoint)
    {
        var hit = context.HitTest(modelPoint, 10.0);
        if (_first is null)
        {
            if (hit is not (LineEntity or ArcEntity))
            {
                context.SetStatus("Tangent: choose a line or arc");
                return;
            }

            _first = hit;
            context.SetSelected(hit);
            context.SetStatus(Prompt);
            context.Invalidate();
            return;
        }

        if (hit is null || hit.Id == _first.Id)
        {
            context.SetStatus("Tangent: choose a different line or arc");
            return;
        }

        if (!TryBuildTangentCommand(_first, hit, out var command, out var selected))
        {
            context.SetStatus("Tangent: currently supports a line tangent to an arc");
            return;
        }

        context.Commands.Execute(context.Document, command);
        context.SetSelected(selected);
        context.SetStatus("Tangent constraint applied");
        _first = null;
        context.Invalidate();
    }

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

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

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

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

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

        switch (_first)
        {
            case LineEntity line:
                context.DrawLine(
                    ToolDrawing.TangentPen,
                    renderContext.Transform.ModelToScreen(line.Start),
                    renderContext.Transform.ModelToScreen(line.End));
                break;
            case ArcEntity arc:
                ToolDrawing.DrawArc(
                    context,
                    renderContext.Transform,
                    arc.Center,
                    arc.Radius,
                    arc.StartAngleDeg,
                    arc.EndAngleDeg,
                    arc.IsClockwise,
                    ToolDrawing.TangentPen);
                break;
        }
    }

    private static bool TryBuildTangentCommand(
        Entity first,
        Entity second,
        out ConstraintEditCommand command,
        out Entity selected)
    {
        LineEntity? line = null;
        ArcEntity? arc = null;

        if (first is LineEntity firstLine && second is ArcEntity secondArc)
        {
            line = firstLine;
            arc = secondArc;
        }
        else if (first is ArcEntity firstArc && second is LineEntity secondLine)
        {
            line = secondLine;
            arc = firstArc;
        }

        if (line is null || arc is null ||
            !ConstraintGeometry.TryMakeLineTangentToArc(line, arc, out var replacement))
        {
            command = null!;
            selected = null!;
            return false;
        }

        var constraint = new SketchConstraint(
            Guid.NewGuid(),
            ConstraintType.Tangent,
            new[] { line.Id, arc.Id });
        command = new ConstraintEditCommand(
            new[] { new EntityReplacement(line, replacement) },
            constraint);
        selected = replacement;
        return true;
    }
}
