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

namespace TraceCad.App.Tools;

public abstract class EndpointEditTool(
    string name,
    string firstPrompt,
    string secondPrompt,
    string successMessage,
    Func<Entity, Point2, Entity, Point2, bool, Entity?> edit)
    : ISketchTool
{
    private Entity? _subject;
    private Point2? _subjectPick;
    private Entity? _preview;
    private Entity? _boundary;

    public string Name => name;

    public string Prompt => _subject is null ? firstPrompt : secondPrompt;

    public void PointerDown(ToolContext context, Point2 modelPoint)
    {
        if (_subject is null)
        {
            var snap = context.SnapDetailed(modelPoint);
            if (snap is not { Kind: SnapKind.Endpoint, Entity: LineEntity or ArcEntity })
            {
                context.SetStatus(firstPrompt);
                context.Invalidate();
                return;
            }

            _subject = snap.Entity;
            _subjectPick = snap.Point;
            context.SetSelected(snap.Entity);
            context.SetStatus(secondPrompt);
            context.Invalidate();
            return;
        }

        if (_subjectPick is null)
        {
            Reset();
            context.SetStatus(firstPrompt);
            context.Invalidate();
            return;
        }

        var replacement = _preview;
        if (replacement is null)
        {
            var hit = context.HitTest(modelPoint, 14.0);
            if (hit is not null && hit.Id != _subject.Id)
            {
                replacement = edit(_subject, _subjectPick.Value, hit, modelPoint, false);
            }
        }

        if (replacement is null)
        {
            context.SetStatus($"{name}: no valid boundary intersection");
            context.Invalidate();
            return;
        }

        context.Commands.Execute(context.Document, new ReplaceEntityCommand(_subject, replacement));
        context.SetSelected(replacement);
        context.SetStatus(successMessage);
        Reset();
        context.Invalidate();
    }

    public void PointerMove(ToolContext context, Point2 modelPoint)
    {
        _preview = null;
        _boundary = null;

        if (_subject is null || _subjectPick is null)
        {
            return;
        }

        var hit = context.HitTest(modelPoint, 14.0);
        if (hit is null || hit.Id == _subject.Id)
        {
            context.SetStatus(secondPrompt);
            context.Invalidate();
            return;
        }

        _preview = edit(_subject, _subjectPick.Value, hit, modelPoint, true);
        _boundary = hit;
        context.SetStatus(_preview is null
            ? $"{name}: hovered entity is not a valid boundary"
            : $"{name}: click to apply preview");
        context.Invalidate();
    }

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

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

    public void Cancel(ToolContext context)
    {
        Reset();
        context.SetStatus(firstPrompt);
        context.Invalidate();
    }

    public void RenderOverlay(DrawingContext context, ToolRenderContext renderContext)
    {
        if (_subjectPick is not null)
        {
            ToolDrawing.DrawPointMarker(context, renderContext.Transform, _subjectPick.Value);
        }

        if (_preview is not null)
        {
            DrawPreviewEntity(context, renderContext.Transform, _preview);
        }
    }

    private void Reset()
    {
        _subject = null;
        _subjectPick = null;
        _preview = null;
        _boundary = null;
    }

    private static void DrawPreviewEntity(DrawingContext context, ViewTransform transform, Entity entity)
    {
        switch (entity)
        {
            case LineEntity line:
                context.DrawLine(
                    ToolDrawing.TangentPen,
                    transform.ModelToScreen(line.Start),
                    transform.ModelToScreen(line.End));
                break;
            case ArcEntity arc:
                ToolDrawing.DrawArc(
                    context,
                    transform,
                    arc.Center,
                    arc.Radius,
                    arc.StartAngleDeg,
                    arc.EndAngleDeg,
                    arc.IsClockwise,
                    ToolDrawing.TangentPen);
                break;
        }
    }
}
