using System;
using System.Numerics;
using Windows.Foundation;
using Windows.UI;

namespace Sonex.Client.Controls.SceneCanvas;

public sealed class SceneCanvasLineObject : SceneCanvasObject
{
    private const double CoordinatePrecision = 0.01d;
    private Vector2 _startOffsetMeters;
    private Vector2 _endOffsetMeters;
    private double _thicknessMeters;

    public SceneCanvasLineObject(
        Vector2 startPointMeters,
        Vector2 endPointMeters,
        double thicknessMeters,
        Color strokeColor)
    {
        StrokeColor = strokeColor;
        ThicknessMeters = thicknessMeters;
        CanMove = true;
        CanResize = false;
        CanRotate = false;

        SetPoints(startPointMeters, endPointMeters);
    }

    public Color StrokeColor { get; set; }

    public double ThicknessMeters
    {
        get => _thicknessMeters;
        set => _thicknessMeters = Math.Max(CoordinatePrecision, RoundTo2(value));
    }

    public Vector2 StartPointMeters
    {
        get => new(
            (float)RoundTo2(BoundsMeters.X + _startOffsetMeters.X),
            (float)RoundTo2(BoundsMeters.Y + _startOffsetMeters.Y));
        set => SetPoints(value, EndPointMeters);
    }

    public Vector2 EndPointMeters
    {
        get => new(
            (float)RoundTo2(BoundsMeters.X + _endOffsetMeters.X),
            (float)RoundTo2(BoundsMeters.Y + _endOffsetMeters.Y));
        set => SetPoints(StartPointMeters, value);
    }

    public double StartPointZMeters
    {
        get => RoundTo2(PositionZMeters);
        set => PositionZMeters = RoundTo2(value);
    }

    public double EndPointZMeters
    {
        get => RoundTo2(PositionZMeters + HeightMeters);
        set => HeightMeters = RoundTo2(value - PositionZMeters);
    }

    public override Rect SpatialBoundsMeters
    {
        get
        {
            var start = StartPointMeters;
            var end = EndPointMeters;

            var minX = Math.Min(start.X, end.X);
            var minY = Math.Min(start.Y, end.Y);
            var maxX = Math.Max(start.X, end.X);
            var maxY = Math.Max(start.Y, end.Y);

            var halfThickness = ThicknessMeters * 0.5d;
            return new Rect(
                minX - halfThickness,
                minY - halfThickness,
                Math.Max(0d, (maxX - minX) + (halfThickness * 2d)),
                Math.Max(0d, (maxY - minY) + (halfThickness * 2d)));
        }
    }

    public override bool HitTest(Vector2 worldPointMeters, float toleranceMeters = 0.05f)
    {
        var start = StartPointMeters;
        var end = EndPointMeters;
        var threshold = (float)Math.Max(0.0001d, (ThicknessMeters * 0.5d) + toleranceMeters);
        return DistancePointToSegment(worldPointMeters, start, end) <= threshold;
    }

    public override void OnDraw(SceneCanvasDrawContext context)
    {
        var strokeWidth = (float)Math.Max(0.0001d, ThicknessMeters);
        context.DrawingSession.DrawLine(StartPointMeters, EndPointMeters, StrokeColor, strokeWidth);
    }

    public override SceneCanvasObject Clone()
    {
        var clone = new SceneCanvasLineObject(StartPointMeters, EndPointMeters, ThicknessMeters, StrokeColor);
        CopyBasePropertiesTo(clone);
        return clone;
    }

    public void SetPoints(Vector2 startPointMeters, Vector2 endPointMeters)
    {
        var startX = RoundTo2(startPointMeters.X);
        var startY = RoundTo2(startPointMeters.Y);
        var endX = RoundTo2(endPointMeters.X);
        var endY = RoundTo2(endPointMeters.Y);

        var minX = Math.Min(startX, endX);
        var minY = Math.Min(startY, endY);
        var maxX = Math.Max(startX, endX);
        var maxY = Math.Max(startY, endY);

        minX = RoundTo2(minX);
        minY = RoundTo2(minY);
        maxX = RoundTo2(maxX);
        maxY = RoundTo2(maxY);

        BoundsMeters = new Rect(minX, minY, RoundTo2(Math.Max(0d, maxX - minX)), RoundTo2(Math.Max(0d, maxY - minY)));

        _startOffsetMeters = new Vector2((float)RoundTo2(startX - minX), (float)RoundTo2(startY - minY));
        _endOffsetMeters = new Vector2((float)RoundTo2(endX - minX), (float)RoundTo2(endY - minY));
    }

    private static float DistancePointToSegment(Vector2 point, Vector2 segmentStart, Vector2 segmentEnd)
    {
        var segment = segmentEnd - segmentStart;
        var lengthSquared = segment.LengthSquared();
        if (lengthSquared < 0.0000001f)
        {
            return Vector2.Distance(point, segmentStart);
        }

        var toPoint = point - segmentStart;
        var t = Vector2.Dot(toPoint, segment) / lengthSquared;
        t = Math.Clamp(t, 0f, 1f);

        var closest = segmentStart + (segment * t);
        return Vector2.Distance(point, closest);
    }

    private static double RoundTo2(double value)
    {
        return Math.Round(value, 2, MidpointRounding.AwayFromZero);
    }
}
