using TraceCad.Core.Model;

namespace TraceCad.Core.Geometry;

public static class SketchConstraintSolver
{
    public static void Solve(SketchDocument document, int iterations = 3)
    {
        for (var iteration = 0; iteration < iterations; iteration++)
        {
            foreach (var constraint in document.Constraints.ToArray())
            {
                ApplyConstraint(document, constraint);
            }
        }
    }

    private static void ApplyConstraint(SketchDocument document, SketchConstraint constraint)
    {
        switch (constraint.Type)
        {
            case ConstraintType.Parallel:
                ApplyParallel(document, constraint);
                break;
            case ConstraintType.Tangent:
                ApplyTangent(document, constraint);
                break;
            case ConstraintType.Distance:
                ApplyDistance(document, constraint);
                break;
            case ConstraintType.Radius:
                ApplyRadius(document, constraint);
                break;
        }
    }

    private static void ApplyParallel(SketchDocument document, SketchConstraint constraint)
    {
        if (constraint.EntityIds.Count < 2 ||
            FindEntity<LineEntity>(document, constraint.EntityIds[0]) is not { } reference ||
            FindEntity<LineEntity>(document, constraint.EntityIds[1]) is not { } target ||
            !ConstraintGeometry.TryMakeParallel(reference, target, out var replacement))
        {
            return;
        }

        document.ReplaceEntity(replacement);
    }

    private static void ApplyDistance(SketchDocument document, SketchConstraint constraint)
    {
        if (constraint.ValueMm is not { } distance ||
            constraint.EntityIds.Count < 2 ||
            FindEntity<LineEntity>(document, constraint.EntityIds[0]) is not { } reference ||
            FindEntity<LineEntity>(document, constraint.EntityIds[1]) is not { } target ||
            !ConstraintGeometry.TrySetParallelDistance(reference, target, distance, out var replacement))
        {
            return;
        }

        document.ReplaceEntity(replacement);
    }

    private static void ApplyTangent(SketchDocument document, SketchConstraint constraint)
    {
        if (constraint.EntityIds.Count < 2)
        {
            return;
        }

        var first = FindEntity<Entity>(document, constraint.EntityIds[0]);
        var second = FindEntity<Entity>(document, constraint.EntityIds[1]);
        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))
        {
            return;
        }

        document.ReplaceEntity(replacement);
    }

    private static void ApplyRadius(SketchDocument document, SketchConstraint constraint)
    {
        if (constraint.ValueMm is not { } radius ||
            constraint.EntityIds.Count < 1 ||
            FindEntity<ArcEntity>(document, constraint.EntityIds[0]) is not { } arc)
        {
            return;
        }

        document.ReplaceEntity(ConstraintGeometry.SetArcRadius(arc, radius));
    }

    private static T? FindEntity<T>(SketchDocument document, Guid id)
        where T : Entity =>
        document.Entities.FirstOrDefault(entity => entity.Id == id) as T;
}
