using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Numerics;
using Windows.Foundation;
using Windows.System;
using Windows.UI.Core;
using Windows.UI;

namespace Sonex.Client.Controls.SceneCanvas;

public sealed partial class SceneCanvas : UserControl
{
    public enum SelectionAlignment
    {
        Left,
        Center,
        Right,
        Top,
        Middle,
        Bottom
    }

    public enum SelectionLayout
    {
        PackLeftToRight,
        PackRightToLeft,
        DistributeHorizontal,
        PackTopToBottom,
        PackBottomToTop,
        DistributeVertical,
        CenterActiveHorizontal,
        CenterActiveVertical
    }

    private enum InteractionMode
    {
        None,
        Panning,
        AreaSelection,
        MovingObject,
        MovingGuideLine,
        MovingLinePoint,
        ResizingObject,
        RotatingObject
    }

    private enum ResizeHandle
    {
        None,
        LineStart,
        LineEnd,
        Left,
        Right,
        Top,
        Bottom,
        TopLeft,
        TopRight,
        BottomLeft,
        BottomRight,
        Rotate
    }

    private const float RulerTopHeightPx = 28f;
    private const float RulerLeftWidthPx = 28f;
    private const int RulerMinorDivisionCount = 10;
    private const float RulerMajorTickLengthPx = 11f;
    private const float RulerMidTickLengthPx = 8f;
    private const float RulerMinorTickLengthPx = 5f;
    private const float RulerMajorTickStrokePx = 1.25f;
    private const float RulerMidTickStrokePx = 1.1f;
    private const float RulerMinorTickStrokePx = 1f;
    private const float RulerTextPaddingPx = 2f;
    private const float LeftRulerLabelOffsetXPx = -5f;
    private const float VerticalGuideHeaderHandleOffsetYPx = 8f;
    private const float HorizontalGuideHeaderHandleOffsetXPx = 8f;
    private const float ScrollMarginMeters = 10f;
    private const float MinimumExtentMeters = 20f;
    private const float MinZoomPixelsPerMeter = 2f;
    private const float MaxZoomPixelsPerMeter = 4096f;
    private const float DefaultZoomPixelsPerMeter = 40f;
    private const float WheelZoomFactor = 1.12f;
    private const float ResizeHandleSizePx = 10f;
    private const float RotateHandleDistancePx = 20f;
    private const float RotateHandleRadiusPx = 6f;
    private const float MinObjectSizeMeters = 0.1f;
    private const float DefaultMagnetDistancePixels = 10f;
    private const float MeasureSnapDistancePixels = 14f;
    private const double RotationSnapStepRadians = Math.PI / 12d;
    private const double RulerAngleSnapStepRadians = Math.PI / 4d;
    private const float InactiveLayerOpacity = 0.18f;
    private const double DefaultPrecisionMeters = 0.01d;
    private const int MaxUndoHistoryEntries = 30;
    private const float GuideLineStrokePx = 1.1f;
    private const float GuideHitDistancePixels = 8f;
    private const float GuideHeaderHandleRadiusPx = 7f;
    private const float GuideHeaderHandleHitRadiusPx = 10f;
    private const float GuidePointSnapCursorDistancePixels = 48f;
    private const float SelectionFrameStrokePixels = 3f;
    private const float NavigationPinStemLengthPx = 13f;
    private const float NavigationPinRadiusPx = 5.5f;

    private static readonly CanvasTextFormat RulerTextFormat = new()
    {
        FontFamily = "Consolas",
        FontSize = 10f,
        HorizontalAlignment = CanvasHorizontalAlignment.Center,
        VerticalAlignment = CanvasVerticalAlignment.Center,
        WordWrapping = CanvasWordWrapping.NoWrap,
        TrimmingGranularity = CanvasTextTrimmingGranularity.None
    };

    private static readonly CanvasTextFormat OverlayCenteredTextFormat = new()
    {
        FontFamily = "Consolas",
        FontSize = 11f,
        HorizontalAlignment = CanvasHorizontalAlignment.Center,
        VerticalAlignment = CanvasVerticalAlignment.Center,
        WordWrapping = CanvasWordWrapping.NoWrap,
        TrimmingGranularity = CanvasTextTrimmingGranularity.None
    };

    private readonly List<SceneCanvasObject> _objects = [];
    private readonly List<SceneCanvasObject> _visibleObjects = [];
    private readonly List<SceneCanvasObject> _hitTestObjects = [];
    private readonly List<SceneCanvasObject> _snapCandidates = [];
    private readonly List<Vector2> _magnetPointBuffer = [];
    private readonly List<Vector2> _movingMagnetPointBuffer = [];
    private readonly List<SceneCanvasObject> _selectedObjects = [];
    private readonly List<SceneCanvasObject> _areaSelectionCandidates = [];
    private readonly List<(SceneCanvasObject SceneObject, Rect StartBounds)> _movingObjects = [];
    private readonly List<SceneGuideLine> _guideLines = [];
    private readonly SceneSpatialIndex _spatialIndex = new(24f);
    private readonly Dictionary<SceneCanvasObject, long> _objectDrawOrder = [];
    private readonly Dictionary<int, int> _layoutIndexById = [];
    private readonly List<SceneCanvasHistorySnapshot> _undoHistory = [];
    private readonly List<SceneCanvasLayoutInfo> _layouts = [];
    private readonly List<int> _magnetLayoutIds = [];

    private bool _sceneBoundsDirty = true;
    private Rect _sceneBoundsMeters = new Rect(-10, -10, 20, 20);
    private Vector2 _viewOriginMeters = new(-10f, -10f);
    private float _zoomPixelsPerMeter = DefaultZoomPixelsPerMeter;
    private int _queryToken = 1;

    private InteractionMode _interactionMode;
    private ResizeHandle _activeResizeHandle;
    private SceneCanvasObject? _activeObject;
    private Point _panStartPointerPixels;
    private Vector2 _panStartViewOriginMeters;
    private Vector2 _manipulationStartPointerWorld;
    private Point _manipulationStartPointerPixels;
    private Rect _manipulationStartBounds;
    private double _manipulationStartRotation;
    private Vector2 _manipulationPivotWorld;
    private Point _areaSelectionStartPixels;
    private Point _areaSelectionCurrentPixels;
    private bool _areaSelectionAdditive;
    private int _activeLayer;
    private double _precisionMeters = DefaultPrecisionMeters;
    private bool _isEditingEnabled = true;
    private bool _isRulerModeEnabled;
    private bool _magnetActiveLayoutOnly = true;
    private bool _magnetEnabled = true;
    private bool _isGridVisible = true;
    private bool _areRulersVisible = true;

    private bool _isScrollSyncInProgress;

    private bool _isReleased;
    private bool _isRenderRequestQueued;
    private bool _hasPendingRenderRequest;
    private bool _isApplyingHistory;
    private long _nextObjectDrawOrderId = 1L;
    private int _nextLayoutId = 1;
    private bool _hasMagnetGuide;
    private Vector2 _magnetGuideSourceMeters;
    private Vector2 _magnetGuideTargetMeters;
    private bool _hasMeasureStart;
    private bool _hasMeasureEnd;
    private bool _hasMeasureHover;
    private bool _isMeasureHoverSnapped;
    private Vector2 _measureStartMeters;
    private Vector2 _measureEndMeters;
    private Vector2 _measureHoverMeters;
    private SceneCanvasHistorySnapshot? _interactionHistoryStart;
    private bool _interactionChanged;
    private int _activeGuideId = -1;
    private bool _activeGuideIsVertical;
    private int _nextGuideId = 1;
    private bool _hasNavigationPin;
    private Vector2 _navigationPinMeters;
    private SceneCanvasObject? _externalDragPreviewObject;
    private InputSystemCursorShape _cursorShape = InputSystemCursorShape.Arrow;
    private readonly Dictionary<InputSystemCursorShape, InputCursor> _cursorCache = [];

    private Color _surfaceBackgroundColor;
    private Color _sceneBackgroundColor;
    private Color _rulerBackgroundColor;
    private Color _rulerTextColor;
    private Color _rulerTickMajorColor;
    private Color _rulerTickMidColor;
    private Color _rulerTickMinorColor;
    private Color _gridMajorColor;
    private Color _gridMinorColor;
    private Color _selectionColor;
    private Color _borderColor;
    private Color _magnetGuideColor;
    private Color _guideLineColor;

    public SceneCanvas()
    {
        InitializeComponent();
        EnsureDefaultLayouts();
        RefreshThemeResources();

        Loaded += SceneCanvas_Loaded;
        Unloaded += SceneCanvas_Unloaded;
        ActualThemeChanged += SceneCanvas_ActualThemeChanged;

        _Canvas.PointerPressed += Canvas_PointerPressed;
        _Canvas.PointerReleased += Canvas_PointerReleased;
        _Canvas.PointerMoved += Canvas_PointerMoved;
        _Canvas.PointerWheelChanged += Canvas_PointerWheelChanged;
        _Canvas.PointerExited += Canvas_PointerExited;
        _Canvas.DoubleTapped += Canvas_DoubleTapped;
        _Canvas.PreviewKeyDown += Canvas_PreviewKeyDown;
    }

    public event EventHandler<SceneCanvasObjectClickedEventArgs>? SceneObjectClicked;

    public event EventHandler<SceneCanvasObject?>? SelectedObjectChanged;

    public event EventHandler? SelectionChanged;

    public event EventHandler? SelectionGeometryChanged;

    public event EventHandler? CopyRequested;

    public event EventHandler? CutRequested;

    public event EventHandler? PasteRequested;

    public event EventHandler? DeleteRequested;

    public event EventHandler? CloneRequested;

    public event EventHandler<int>? RenameLayoutRequested;

    public event EventHandler<int>? ActiveLayoutChanged;

    public event EventHandler? LayoutsChanged;

    public event EventHandler<bool>? MagnetEnabledChanged;

    public event EventHandler<bool>? EditModeChanged;

    public event EventHandler<bool>? RulerModeChanged;

    public event EventHandler<bool>? GridVisibilityChanged;

    public event EventHandler<bool>? RulersVisibilityChanged;

    public SceneCanvasObject? SelectedObject { get; private set; }

    public IReadOnlyList<SceneCanvasObject> Objects => _objects;

    public IReadOnlyList<SceneCanvasObject> SelectedObjects => _selectedObjects;

    public float ZoomPixelsPerMeter => _zoomPixelsPerMeter;

    public double PrecisionMeters
    {
        get => _precisionMeters;
        set => _precisionMeters = Math.Max(0.001d, value);
    }

    public int ActiveLayer
    {
        get => _activeLayer;
        set
        {
            var nextLayer = ResolveExistingLayoutId(value);
            if (_activeLayer == nextLayer)
            {
                return;
            }

            _activeLayer = nextLayer;

            ClearSelection();
            NormalizeMagnetLayoutFilter();

            RequestRender();
            ActiveLayoutChanged?.Invoke(this, _activeLayer);
        }
    }

    public int ActiveLayoutId
    {
        get => ActiveLayer;
        set => ActiveLayer = value;
    }

    public IReadOnlyList<SceneCanvasLayoutInfo> GetLayouts()
    {
        var snapshot = new List<SceneCanvasLayoutInfo>(_layouts.Count);
        foreach (var layout in _layouts)
        {
            snapshot.Add(new SceneCanvasLayoutInfo(layout.Id, layout.Name, layout.Description, layout.IsVisible));
        }

        return snapshot;
    }

    public SceneCanvasLayoutInfo? GetLayout(int layoutId)
    {
        var layout = FindLayout(layoutId);
        return layout == null
            ? null
            : new SceneCanvasLayoutInfo(layout.Id, layout.Name, layout.Description, layout.IsVisible);
    }

    public int AddLayout(string? layoutName = null)
    {
        if (!CanEditObjects())
        {
            return -1;
        }

        var history = CaptureHistorySnapshot();
        var name = CreateUniqueLayoutName(layoutName, _nextLayoutId);
        var layout = new SceneCanvasLayoutInfo(_nextLayoutId, name, string.Empty, true);
        _nextLayoutId++;
        _layouts.Add(layout);
        RebuildLayoutIndexCache();
        PushUndoSnapshot(history, true);
        LayoutsChanged?.Invoke(this, EventArgs.Empty);
        return layout.Id;
    }

    public bool RenameLayout(int layoutId, string? layoutName)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        var layout = FindLayout(layoutId);
        if (layout == null)
        {
            return false;
        }

        var normalized = CreateUniqueLayoutName(layoutName, layoutId, layoutId);
        if (string.Equals(layout.Name, normalized, StringComparison.Ordinal))
        {
            return false;
        }

        var history = CaptureHistorySnapshot();
        layout.Name = normalized;
        PushUndoSnapshot(history, true);
        LayoutsChanged?.Invoke(this, EventArgs.Empty);
        return true;
    }

    public bool UpdateLayout(int layoutId, string? layoutName, string? layoutDescription)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        var layout = FindLayout(layoutId);
        if (layout == null)
        {
            return false;
        }

        var normalizedName = CreateUniqueLayoutName(layoutName, layoutId, layoutId);
        var normalizedDescription = NormalizeLayoutDescription(layoutDescription);
        if (string.Equals(layout.Name, normalizedName, StringComparison.Ordinal)
            && string.Equals(layout.Description, normalizedDescription, StringComparison.Ordinal))
        {
            return false;
        }

        var history = CaptureHistorySnapshot();
        layout.Name = normalizedName;
        layout.Description = normalizedDescription;
        PushUndoSnapshot(history, true);
        LayoutsChanged?.Invoke(this, EventArgs.Empty);
        return true;
    }

    public bool SetLayoutVisibility(int layoutId, bool isVisible)
    {
        var layout = FindLayout(layoutId);
        if (layout == null || layout.IsVisible == isVisible)
        {
            return false;
        }

        var history = CaptureHistorySnapshot();
        layout.IsVisible = isVisible;

        var selectionChanged = _selectedObjects.RemoveAll(sceneObject => sceneObject.Layer == layoutId) > 0;
        if (selectionChanged)
        {
            SyncSelectedObject();
            SelectionChanged?.Invoke(this, EventArgs.Empty);
            RaiseSelectionGeometryChanged();
        }

        RequestRender();
        PushUndoSnapshot(history, true);
        LayoutsChanged?.Invoke(this, EventArgs.Empty);
        return true;
    }

    public bool RemoveLayout(int layoutId)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        var layout = FindLayout(layoutId);
        if (layout == null || _layouts.Count <= 1)
        {
            return false;
        }

        var replacement = FindReplacementLayoutId(layoutId);
        var history = CaptureHistorySnapshot();

        var moved = false;
        foreach (var sceneObject in _objects)
        {
            if (sceneObject.Layer != layoutId)
            {
                continue;
            }

            sceneObject.Layer = replacement;
            _spatialIndex.Update(sceneObject);
            moved = true;
        }

        _layouts.Remove(layout);
        RebuildLayoutIndexCache();
        NormalizeMagnetLayoutFilter();

        var activeChanged = false;
        if (_activeLayer == layoutId)
        {
            _activeLayer = replacement;
            activeChanged = true;
            ClearSelection();
        }
        else
        {
            SyncSelectedObject();
        }

        if (moved)
        {
            _sceneBoundsDirty = true;
            UpdateScrollBars();
        }

        RequestRender();
        if (activeChanged)
        {
            ActiveLayoutChanged?.Invoke(this, _activeLayer);
        }

        SelectionChanged?.Invoke(this, EventArgs.Empty);
        RaiseSelectionGeometryChanged();
        PushUndoSnapshot(history, true);
        LayoutsChanged?.Invoke(this, EventArgs.Empty);
        return true;
    }

    public bool MoveLayoutUp(int layoutId)
    {
        return MoveLayoutInternal(layoutId, -1);
    }

    public bool MoveLayoutDown(int layoutId)
    {
        return MoveLayoutInternal(layoutId, 1);
    }

    public int MoveSelectionToLayout(int targetLayoutId)
    {
        return MoveObjectsToLayout(_selectedObjects, targetLayoutId);
    }

    public int MoveObjectsToLayout(IEnumerable<SceneCanvasObject> sourceObjects, int targetLayoutId)
    {
        if (!CanEditObjects() || !LayoutExists(targetLayoutId))
        {
            return 0;
        }

        var objectsToMove = new List<SceneCanvasObject>();
        foreach (var sceneObject in sourceObjects)
        {
            if (sceneObject == null || !_objects.Contains(sceneObject))
            {
                continue;
            }

            objectsToMove.Add(sceneObject);
        }

        if (objectsToMove.Count == 0)
        {
            return 0;
        }

        var history = CaptureHistorySnapshot();
        var movedCount = 0;
        foreach (var sceneObject in objectsToMove)
        {
            if (sceneObject.Layer == targetLayoutId)
            {
                continue;
            }

            sceneObject.Layer = targetLayoutId;
            _spatialIndex.Update(sceneObject);
            movedCount++;
        }

        if (movedCount == 0)
        {
            return 0;
        }

        _sceneBoundsDirty = true;
        UpdateScrollBars();
        SyncSelectedObject();
        SelectionChanged?.Invoke(this, EventArgs.Empty);
        RequestRender();
        RaiseSelectionGeometryChanged();
        PushUndoSnapshot(history, true);
        return movedCount;
    }

    public bool MagnetEnabled
    {
        get => _magnetEnabled;
        set
        {
            if (_magnetEnabled == value)
            {
                return;
            }

            _magnetEnabled = value;
            if (!value)
            {
                ClearMagnetGuide();
            }

            RequestRender();
            MagnetEnabledChanged?.Invoke(this, _magnetEnabled);
        }
    }

    public float MagnetDistancePixels { get; set; } = DefaultMagnetDistancePixels;

    public void SetMagnetLayoutFilter(bool activeLayoutOnly, IEnumerable<int>? layoutIds = null)
    {
        _magnetActiveLayoutOnly = activeLayoutOnly;
        _magnetLayoutIds.Clear();

        if (layoutIds != null)
        {
            foreach (var layoutId in layoutIds)
            {
                if (!LayoutExists(layoutId) || _magnetLayoutIds.Contains(layoutId))
                {
                    continue;
                }

                _magnetLayoutIds.Add(layoutId);
            }
        }

        NormalizeMagnetLayoutFilter();
        ClearMagnetGuide();
        RequestRender();
    }

    public bool RequireSelectionBeforeMove { get; set; }

    public bool IsEditingEnabled
    {
        get => _isEditingEnabled;
        set
        {
            if (_isEditingEnabled == value)
            {
                return;
            }

            _isEditingEnabled = value;
            if (!value)
            {
                CancelEditingInteraction();
            }

            EditModeChanged?.Invoke(this, _isEditingEnabled);
        }
    }

    public bool IsRulerModeEnabled
    {
        get => _isRulerModeEnabled;
        set
        {
            if (_isRulerModeEnabled == value)
            {
                return;
            }

            _isRulerModeEnabled = value;
            if (value)
            {
                CancelEditingInteraction();
            }
            else
            {
                ClearMeasureState();
            }

            RequestRender();
            RulerModeChanged?.Invoke(this, _isRulerModeEnabled);
        }
    }

    public bool IsGridVisible
    {
        get => _isGridVisible;
        set
        {
            if (_isGridVisible == value)
            {
                return;
            }

            _isGridVisible = value;
            RequestRender();
            GridVisibilityChanged?.Invoke(this, _isGridVisible);
        }
    }

    public bool AreRulersVisible
    {
        get => _areRulersVisible;
        set
        {
            if (_areRulersVisible == value)
            {
                return;
            }

            _areRulersVisible = value;
            UpdateScrollBars();
            RequestRender();
            RulersVisibilityChanged?.Invoke(this, _areRulersVisible);
        }
    }

    public Rect VisibleWorldBoundsMeters
    {
        get
        {
            var viewport = GetSceneViewportPixels();
            return GetVisibleWorldBounds(viewport);
        }
    }

    public bool TryGetWorldPointFromCanvas(Point canvasPointPixels, out Vector2 worldPointMeters)
    {
        worldPointMeters = default;

        var viewport = GetSceneViewportPixels();
        if (viewport.Width <= 0d || viewport.Height <= 0d)
        {
            return false;
        }

        var clampedX = Math.Clamp(canvasPointPixels.X, viewport.X, viewport.X + viewport.Width);
        var clampedY = Math.Clamp(canvasPointPixels.Y, viewport.Y, viewport.Y + viewport.Height);

        worldPointMeters = ScreenToWorld(new Vector2((float)clampedX, (float)clampedY), viewport);
        return float.IsFinite(worldPointMeters.X) && float.IsFinite(worldPointMeters.Y);
    }

    public void BeginExternalDragPreview(SceneCanvasObject previewObject, Point pointerPixels, bool useMagnet = true, bool suppressMagnet = false)
    {
        if (previewObject == null)
        {
            EndExternalDragPreview();
            return;
        }

        _externalDragPreviewObject = previewObject.Clone();
        _externalDragPreviewObject.Layer = ActiveLayer;
        _externalDragPreviewObject.ZIndex = int.MaxValue;

        UpdateExternalDragPreview(pointerPixels, useMagnet, suppressMagnet);
    }

    public void UpdateExternalDragPreview(Point pointerPixels, bool useMagnet = true, bool suppressMagnet = false)
    {
        if (_externalDragPreviewObject == null)
        {
            return;
        }

        if (!TryGetWorldPointFromCanvas(pointerPixels, out var pointerWorldMeters))
        {
            return;
        }

        var targetCenter = new Vector2(
            (float)SnapToStep(pointerWorldMeters.X, PrecisionMeters),
            (float)SnapToStep(pointerWorldMeters.Y, PrecisionMeters));

        var currentBounds = _externalDragPreviewObject.BoundsMeters;
        var candidateBounds = new Rect(
            targetCenter.X - (currentBounds.Width * 0.5d),
            targetCenter.Y - (currentBounds.Height * 0.5d),
            currentBounds.Width,
            currentBounds.Height);

        candidateBounds = new Rect(
            SnapToStep(candidateBounds.X, PrecisionMeters),
            SnapToStep(candidateBounds.Y, PrecisionMeters),
            candidateBounds.Width,
            candidateBounds.Height);

        if (useMagnet && MagnetEnabled && !suppressMagnet)
        {
            candidateBounds = ApplyMagnetToMove(_externalDragPreviewObject, candidateBounds);
        }
        else
        {
            ClearMagnetGuide();
        }

        var deltaX = candidateBounds.X - currentBounds.X;
        var deltaY = candidateBounds.Y - currentBounds.Y;

        if (_externalDragPreviewObject is SceneCanvasLineObject lineObject)
        {
            lineObject.SetPoints(
                new Vector2(lineObject.StartPointMeters.X + (float)deltaX, lineObject.StartPointMeters.Y + (float)deltaY),
                new Vector2(lineObject.EndPointMeters.X + (float)deltaX, lineObject.EndPointMeters.Y + (float)deltaY));
        }
        else
        {
            _externalDragPreviewObject.BoundsMeters = new Rect(
                candidateBounds.X,
                candidateBounds.Y,
                currentBounds.Width,
                currentBounds.Height);
        }

        RequestRender();
    }

    public void EndExternalDragPreview()
    {
        if (_externalDragPreviewObject == null && !_hasMagnetGuide)
        {
            return;
        }

        _externalDragPreviewObject = null;
        ClearMagnetGuide();
        RequestRender();
    }

    public SceneCanvasObject? GetExternalDragPreviewSnapshot()
    {
        return _externalDragPreviewObject?.Clone();
    }

    public void SetObjects(IEnumerable<SceneCanvasObject> sceneObjects)
    {
        var history = CaptureHistorySnapshot();
        var hadObjects = _objects.Count > 0;
        var hadSelection = _selectedObjects.Count > 0;
        var changed = false;

        _objects.Clear();
        _spatialIndex.Clear();
        _objectDrawOrder.Clear();
        _nextObjectDrawOrderId = 1L;
        _selectedObjects.Clear();
        SelectedObject = null;

        foreach (var sceneObject in sceneObjects)
        {
            if (sceneObject == null)
            {
                continue;
            }

            sceneObject.Layer = ResolveExistingLayoutId(sceneObject.Layer);
            sceneObject.BoundsMeters = ApplyPrecision(sceneObject.BoundsMeters);
            _objects.Add(sceneObject);
            RegisterObjectDrawOrder(sceneObject);
            _spatialIndex.Add(sceneObject);
            changed = true;
        }

        _sceneBoundsDirty = true;

        if (hadSelection)
        {
            SelectedObjectChanged?.Invoke(this, null);
            SelectionChanged?.Invoke(this, EventArgs.Empty);
        }

        UpdateScrollBars();
        RequestRender();
        PushUndoSnapshot(history, hadObjects || changed || hadSelection);
    }

    public void AddObject(SceneCanvasObject sceneObject)
    {
        if (sceneObject == null)
        {
            return;
        }

        AddObjects([sceneObject]);
    }

    public int AddObjects(IEnumerable<SceneCanvasObject> sceneObjects)
    {
        if (!CanEditObjects())
        {
            return 0;
        }

        var history = CaptureHistorySnapshot();
        var addedCount = 0;

        foreach (var sceneObject in sceneObjects)
        {
            if (sceneObject == null || _objects.Contains(sceneObject))
            {
                continue;
            }

            sceneObject.Layer = ResolveExistingLayoutId(sceneObject.Layer);
            sceneObject.BoundsMeters = ApplyPrecision(sceneObject.BoundsMeters);
            _objects.Add(sceneObject);
            RegisterObjectDrawOrder(sceneObject);
            _spatialIndex.Add(sceneObject);
            addedCount++;
        }

        if (addedCount == 0)
        {
            return 0;
        }

        _sceneBoundsDirty = true;
        UpdateScrollBars();
        RequestRender();
        PushUndoSnapshot(history, true);
        return addedCount;
    }

    public bool RemoveObject(SceneCanvasObject sceneObject)
    {
        if (sceneObject == null)
        {
            return false;
        }

        return RemoveObjects([sceneObject]) > 0;
    }

    public int RemoveObjects(IEnumerable<SceneCanvasObject> sceneObjects)
    {
        if (!CanEditObjects())
        {
            return 0;
        }

        var objectsToRemove = new List<SceneCanvasObject>();
        foreach (var sceneObject in sceneObjects)
        {
            if (sceneObject != null)
            {
                objectsToRemove.Add(sceneObject);
            }
        }

        if (objectsToRemove.Count == 0)
        {
            return 0;
        }

        var history = CaptureHistorySnapshot();
        var removedCount = 0;
        var selectionChanged = false;

        foreach (var sceneObject in objectsToRemove)
        {
            var removed = _objects.Remove(sceneObject);
            if (!removed)
            {
                continue;
            }

            _spatialIndex.Remove(sceneObject);
            _objectDrawOrder.Remove(sceneObject);

            if (_selectedObjects.Remove(sceneObject))
            {
                selectionChanged = true;
            }

            removedCount++;
        }

        if (removedCount == 0)
        {
            return 0;
        }

        if (selectionChanged)
        {
            SyncSelectedObject();
            SelectionChanged?.Invoke(this, EventArgs.Empty);
        }

        _sceneBoundsDirty = true;
        UpdateScrollBars();
        RequestRender();
        RaiseSelectionGeometryChanged();
        PushUndoSnapshot(history, true);
        return removedCount;
    }

    public bool DeleteSelectedObjects()
    {
        if (!CanEditObjects())
        {
            return false;
        }

        if (_selectedObjects.Count == 0)
        {
            return false;
        }

        var removedCount = RemoveObjects(new List<SceneCanvasObject>(_selectedObjects));
        return removedCount > 0;
    }

    public bool UndoLastChange()
    {
        if (_undoHistory.Count == 0)
        {
            return false;
        }

        var snapshotIndex = _undoHistory.Count - 1;
        var snapshot = _undoHistory[snapshotIndex];
        _undoHistory.RemoveAt(snapshotIndex);
        ApplyHistorySnapshot(snapshot);
        return true;
    }

    public void SetSelection(IEnumerable<SceneCanvasObject> sceneObjects)
    {
        _selectedObjects.Clear();
        SceneCanvasObject? preferred = null;

        foreach (var sceneObject in sceneObjects)
        {
            if (sceneObject == null || sceneObject.Layer != ActiveLayer || !_objects.Contains(sceneObject))
            {
                continue;
            }

            if (_selectedObjects.Contains(sceneObject))
            {
                continue;
            }

            _selectedObjects.Add(sceneObject);
            preferred = sceneObject;
        }

        SyncSelectedObject(preferred);
        SelectionChanged?.Invoke(this, EventArgs.Empty);
        RequestRender();
    }

    public void ClearObjects()
    {
        var history = CaptureHistorySnapshot();
        var hadObjects = _objects.Count > 0;
        var hadSelection = _selectedObjects.Count > 0;

        _objects.Clear();
        _spatialIndex.Clear();
        _objectDrawOrder.Clear();
        _nextObjectDrawOrderId = 1L;
        _sceneBoundsDirty = true;

        ClearSelection();

        UpdateScrollBars();
        RequestRender();
        PushUndoSnapshot(history, hadObjects || hadSelection);
    }

    public void InvalidateObject(SceneCanvasObject sceneObject)
    {
        if (!CanEditObjects() && !_isApplyingHistory)
        {
            return;
        }

        if (sceneObject == null)
        {
            return;
        }

        if (!_objects.Contains(sceneObject))
        {
            return;
        }

        var history = (_interactionMode == InteractionMode.None) ? CaptureHistorySnapshot() : null;
        _spatialIndex.Update(sceneObject);
        _sceneBoundsDirty = true;
        UpdateScrollBars();
        RequestRender();
        RaiseSelectionGeometryChanged(sceneObject);
        PushUndoSnapshot(history, true);
    }

    public void SetZoom(float zoomPixelsPerMeter, Point? anchorScreenPixels = null)
    {
        var clamped = Math.Clamp(zoomPixelsPerMeter, MinZoomPixelsPerMeter, MaxZoomPixelsPerMeter);
        if (Math.Abs(_zoomPixelsPerMeter - clamped) < 0.0001f)
        {
            return;
        }

        var viewport = GetSceneViewportPixels();
        if (viewport.Width < 1d || viewport.Height < 1d)
        {
            _zoomPixelsPerMeter = clamped;
            UpdateScrollBars();
            RequestRender();
            return;
        }

        var anchor = anchorScreenPixels ?? new Point(viewport.X + (viewport.Width * 0.5d), viewport.Y + (viewport.Height * 0.5d));
        var anchorVector = ToVector2(anchor);
        var worldBefore = ScreenToWorld(anchorVector, viewport);

        _zoomPixelsPerMeter = clamped;

        var worldAfter = ScreenToWorld(anchorVector, viewport);
        _viewOriginMeters += worldBefore - worldAfter;

        ClampViewOriginToScene(viewport);
        UpdateScrollBars();
        RequestRender();
    }

    public void ZoomIn(Point? anchorScreenPixels = null)
    {
        SetZoom(_zoomPixelsPerMeter * WheelZoomFactor, anchorScreenPixels);
    }

    public void ZoomInToSelection()
    {
        if (TryGetSelectedObjectsBounds(out var selectionBounds))
        {
            CenterOn(selectionBounds);
        }

        ZoomIn();
    }

    public void ZoomOut(Point? anchorScreenPixels = null)
    {
        SetZoom(_zoomPixelsPerMeter / WheelZoomFactor, anchorScreenPixels);
    }

    public void FitToAllObjects()
    {
        var viewport = GetSceneViewportPixels();
        if (viewport.Width < 1d || viewport.Height < 1d)
        {
            return;
        }

        if (!TryGetVisibleObjectsBounds(out var objectsBounds))
        {
            SetZoom(DefaultZoomPixelsPerMeter);
            CenterOn(new Rect(
                -MinimumExtentMeters * 0.5d,
                -MinimumExtentMeters * 0.5d,
                MinimumExtentMeters,
                MinimumExtentMeters));
            return;
        }

        var paddingX = Math.Max(0.5d, objectsBounds.Width * 0.08d);
        var paddingY = Math.Max(0.5d, objectsBounds.Height * 0.08d);
        var fitBounds = new Rect(
            objectsBounds.X - paddingX,
            objectsBounds.Y - paddingY,
            objectsBounds.Width + (paddingX * 2d),
            objectsBounds.Height + (paddingY * 2d));

        var fitWidth = Math.Max(0.001d, fitBounds.Width);
        var fitHeight = Math.Max(0.001d, fitBounds.Height);
        var zoomX = viewport.Width / fitWidth;
        var zoomY = viewport.Height / fitHeight;
        var targetZoom = (float)Math.Clamp(Math.Min(zoomX, zoomY), MinZoomPixelsPerMeter, MaxZoomPixelsPerMeter);

        SetZoom(targetZoom);
        CenterOn(fitBounds);
    }

    public void FitToSelection()
    {
        var viewport = GetSceneViewportPixels();
        if (viewport.Width < 1d || viewport.Height < 1d)
        {
            return;
        }

        if (!TryGetSelectedObjectsBounds(out var selectionBounds))
        {
            return;
        }

        const double targetCoverage = 0.5d;
        var fitWidth = Math.Max(0.001d, selectionBounds.Width / targetCoverage);
        var fitHeight = Math.Max(0.001d, selectionBounds.Height / targetCoverage);
        var zoomX = viewport.Width / fitWidth;
        var zoomY = viewport.Height / fitHeight;
        var targetZoom = (float)Math.Clamp(Math.Min(zoomX, zoomY), MinZoomPixelsPerMeter, MaxZoomPixelsPerMeter);

        SetZoom(targetZoom);
        CenterOn(selectionBounds);
    }

    public bool MoveSelectionBy(double deltaXMeters, double deltaYMeters)
    {
        if (!CanEditObjects()
            || !double.IsFinite(deltaXMeters)
            || !double.IsFinite(deltaYMeters))
        {
            return false;
        }

        if (Math.Abs(deltaXMeters) < 0.000001d && Math.Abs(deltaYMeters) < 0.000001d)
        {
            return false;
        }

        return MoveSelectedObjectsWithHistory((float)deltaXMeters, (float)deltaYMeters);
    }

    public bool ScrollToPoint(double xMeters, double yMeters, string? layoutName, double zoomPixelsPerMeter)
    {
        if (!double.IsFinite(xMeters)
            || !double.IsFinite(yMeters)
            || !double.IsFinite(zoomPixelsPerMeter)
            || zoomPixelsPerMeter <= 0d)
        {
            return false;
        }

        if (!string.IsNullOrWhiteSpace(layoutName))
        {
            var layout = FindLayoutByName(layoutName);
            if (layout == null)
            {
                return false;
            }

            ActiveLayer = layout.Id;
        }

        var viewport = GetSceneViewportPixels();
        if (viewport.Width < 1d || viewport.Height < 1d)
        {
            _zoomPixelsPerMeter = (float)Math.Clamp(zoomPixelsPerMeter, MinZoomPixelsPerMeter, MaxZoomPixelsPerMeter);
            _hasNavigationPin = true;
            _navigationPinMeters = new Vector2((float)xMeters, (float)yMeters);
            RequestRender();
            return true;
        }

        _zoomPixelsPerMeter = (float)Math.Clamp(zoomPixelsPerMeter, MinZoomPixelsPerMeter, MaxZoomPixelsPerMeter);
        var worldViewWidth = viewport.Width / _zoomPixelsPerMeter;
        var worldViewHeight = viewport.Height / _zoomPixelsPerMeter;
        _viewOriginMeters = new Vector2(
            (float)(xMeters - (worldViewWidth * 0.5d)),
            (float)(yMeters - (worldViewHeight * 0.5d)));

        ClampViewOriginToScene(viewport);
        UpdateScrollBars();

        _hasNavigationPin = true;
        _navigationPinMeters = new Vector2((float)xMeters, (float)yMeters);
        RequestRender();
        return true;
    }

    public void ClearNavigationPin()
    {
        if (!_hasNavigationPin)
        {
            return;
        }

        _hasNavigationPin = false;
        RequestRender();
    }

    public bool AddVerticalGuide(double? xMeters = null)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        var viewport = GetSceneViewportPixels();
        var visible = GetVisibleWorldBounds(viewport);
        var position = xMeters ?? (visible.X + (visible.Width * 0.5d));
        position = SnapToStep(position, PrecisionMeters);
        if (!double.IsFinite(position))
        {
            return false;
        }

        var history = CaptureHistorySnapshot();
        _guideLines.Add(new SceneGuideLine(_nextGuideId++, isVertical: true, position));
        RequestRender();
        PushUndoSnapshot(history, true);
        return true;
    }

    public bool AddHorizontalGuide(double? yMeters = null)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        var viewport = GetSceneViewportPixels();
        var visible = GetVisibleWorldBounds(viewport);
        var position = yMeters ?? (visible.Y + (visible.Height * 0.5d));
        position = SnapToStep(position, PrecisionMeters);
        if (!double.IsFinite(position))
        {
            return false;
        }

        var history = CaptureHistorySnapshot();
        _guideLines.Add(new SceneGuideLine(_nextGuideId++, isVertical: false, position));
        RequestRender();
        PushUndoSnapshot(history, true);
        return true;
    }

    public bool MatchSelectionWidth()
    {
        return MatchSelectionDimension(matchWidth: true);
    }

    public bool MatchSelectionHeight()
    {
        return MatchSelectionDimension(matchWidth: false);
    }

    public bool RotateSelectionLeft()
    {
        return RotateSelectionBy(-RotationSnapStepRadians);
    }

    public bool RotateSelectionRight()
    {
        return RotateSelectionBy(RotationSnapStepRadians);
    }

    public bool AlignSelection(SelectionAlignment alignment)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        SyncSelectedObject();
        if (_selectedObjects.Count < 2)
        {
            return false;
        }

        var history = CaptureHistorySnapshot();

        var anchor = SelectedObject;
        if (anchor == null || !_selectedObjects.Contains(anchor))
        {
            anchor = _selectedObjects[^1];
        }

        if (anchor == null || anchor.Layer != ActiveLayer)
        {
            return false;
        }

        var anchorBounds = anchor.SpatialBoundsMeters;
        var targetLeft = anchorBounds.X;
        var targetRight = anchorBounds.X + anchorBounds.Width;
        var targetCenterX = anchorBounds.X + (anchorBounds.Width * 0.5d);
        var targetTop = anchorBounds.Y;
        var targetBottom = anchorBounds.Y + anchorBounds.Height;
        var targetCenterY = anchorBounds.Y + (anchorBounds.Height * 0.5d);

        var changed = false;

        foreach (var sceneObject in _selectedObjects)
        {
            if (ReferenceEquals(sceneObject, anchor) || sceneObject.Layer != ActiveLayer || !sceneObject.CanMove)
            {
                continue;
            }

            var objectBounds = sceneObject.SpatialBoundsMeters;
            var deltaX = 0d;
            var deltaY = 0d;

            switch (alignment)
            {
                case SelectionAlignment.Left:
                    deltaX = targetLeft - objectBounds.X;
                    break;
                case SelectionAlignment.Center:
                    deltaX = targetCenterX - (objectBounds.X + (objectBounds.Width * 0.5d));
                    break;
                case SelectionAlignment.Right:
                    deltaX = targetRight - (objectBounds.X + objectBounds.Width);
                    break;
                case SelectionAlignment.Top:
                    deltaY = targetTop - objectBounds.Y;
                    break;
                case SelectionAlignment.Middle:
                    deltaY = targetCenterY - (objectBounds.Y + (objectBounds.Height * 0.5d));
                    break;
                case SelectionAlignment.Bottom:
                    deltaY = targetBottom - (objectBounds.Y + objectBounds.Height);
                    break;
            }

            if (!MoveObjectBy(sceneObject, deltaX, deltaY))
            {
                continue;
            }

            changed = true;
        }

        if (!changed)
        {
            return false;
        }

        _sceneBoundsDirty = true;
        UpdateScrollBars();
        RequestRender();
        RaiseSelectionGeometryChanged();
        PushUndoSnapshot(history, true);
        return true;
    }

    private bool MatchSelectionDimension(bool matchWidth)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        SyncSelectedObject();
        if (_selectedObjects.Count < 2)
        {
            return false;
        }

        SceneCanvasObject? anchor = SelectedObject;
        if (anchor == null || !_selectedObjects.Contains(anchor) || anchor.Layer != ActiveLayer || !anchor.CanResize)
        {
            anchor = null;
            foreach (var selectedObject in _selectedObjects)
            {
                if (selectedObject.Layer != ActiveLayer || !selectedObject.CanResize)
                {
                    continue;
                }

                anchor = selectedObject;
                break;
            }
        }

        if (anchor == null)
        {
            return false;
        }

        var anchorBounds = anchor.BoundsMeters;
        var targetSize = matchWidth ? anchorBounds.Width : anchorBounds.Height;
        if (!double.IsFinite(targetSize))
        {
            return false;
        }

        targetSize = Math.Max(MinObjectSizeMeters, SnapToStep(targetSize, PrecisionMeters));
        var history = CaptureHistorySnapshot();
        var changed = false;

        foreach (var sceneObject in _selectedObjects)
        {
            if (ReferenceEquals(sceneObject, anchor) || sceneObject.Layer != ActiveLayer || !sceneObject.CanResize)
            {
                continue;
            }

            var bounds = sceneObject.BoundsMeters;
            var centerX = bounds.X + (bounds.Width * 0.5d);
            var centerY = bounds.Y + (bounds.Height * 0.5d);
            var width = matchWidth ? targetSize : bounds.Width;
            var height = matchWidth ? bounds.Height : targetSize;
            width = Math.Max(MinObjectSizeMeters, width);
            height = Math.Max(MinObjectSizeMeters, height);

            var next = new Rect(
                centerX - (width * 0.5d),
                centerY - (height * 0.5d),
                width,
                height);
            next = ApplyPrecision(next);

            if (AreRectsClose(bounds, next))
            {
                continue;
            }

            sceneObject.BoundsMeters = next;
            _spatialIndex.Update(sceneObject);
            changed = true;
        }

        if (!changed)
        {
            return false;
        }

        _sceneBoundsDirty = true;
        UpdateScrollBars();
        RequestRender();
        RaiseSelectionGeometryChanged();
        PushUndoSnapshot(history, true);
        return true;
    }

    private bool RotateSelectionBy(double deltaRadians)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        SyncSelectedObject();
        if (_selectedObjects.Count == 0)
        {
            return false;
        }

        var history = CaptureHistorySnapshot();
        var changed = false;
        foreach (var sceneObject in _selectedObjects)
        {
            if (sceneObject.Layer != ActiveLayer || !sceneObject.CanRotate)
            {
                continue;
            }

            var next = SnapToStep(sceneObject.RotationRadians + deltaRadians, RotationSnapStepRadians);
            if (Math.Abs(sceneObject.RotationRadians - next) < 0.00001d)
            {
                continue;
            }

            sceneObject.RotationRadians = next;
            _spatialIndex.Update(sceneObject);
            changed = true;
        }

        if (!changed)
        {
            return false;
        }

        _sceneBoundsDirty = true;
        UpdateScrollBars();
        RequestRender();
        RaiseSelectionGeometryChanged();
        PushUndoSnapshot(history, true);
        return true;
    }

    public bool LayoutSelection(SelectionLayout layout)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        SyncSelectedObject();
        var history = CaptureHistorySnapshot();

        var entries = new List<(SceneCanvasObject SceneObject, Rect Bounds)>();
        foreach (var sceneObject in _selectedObjects)
        {
            if (sceneObject.Layer != ActiveLayer || !sceneObject.CanMove)
            {
                continue;
            }

            entries.Add((sceneObject, sceneObject.SpatialBoundsMeters));
        }

        if (entries.Count < 2)
        {
            return false;
        }

        var changed = layout switch
        {
            SelectionLayout.PackLeftToRight => PackSelectionLeftToRight(entries),
            SelectionLayout.PackRightToLeft => PackSelectionRightToLeft(entries),
            SelectionLayout.DistributeHorizontal => DistributeSelectionHorizontally(entries),
            SelectionLayout.PackTopToBottom => PackSelectionTopToBottom(entries),
            SelectionLayout.PackBottomToTop => PackSelectionBottomToTop(entries),
            SelectionLayout.DistributeVertical => DistributeSelectionVertically(entries),
            SelectionLayout.CenterActiveHorizontal => CenterActiveSelectionHorizontally(entries),
            SelectionLayout.CenterActiveVertical => CenterActiveSelectionVertically(entries),
            _ => false
        };

        if (!changed)
        {
            return false;
        }

        _sceneBoundsDirty = true;
        UpdateScrollBars();
        RequestRender();
        RaiseSelectionGeometryChanged();
        PushUndoSnapshot(history, true);
        return true;
    }

    public bool BringSelectionToFront()
    {
        if (!CanEditObjects())
        {
            return false;
        }

        var entries = new List<SceneCanvasObject>();
        foreach (var selectedObject in _selectedObjects)
        {
            if (selectedObject.Layer != ActiveLayer)
            {
                continue;
            }

            entries.Add(selectedObject);
        }

        if (entries.Count == 0)
        {
            return false;
        }

        var history = CaptureHistorySnapshot();
        entries.Sort((a, b) =>
        {
            var byZ = a.ZIndex.CompareTo(b.ZIndex);
            if (byZ != 0)
            {
                return byZ;
            }

            return GetObjectDrawOrder(a).CompareTo(GetObjectDrawOrder(b));
        });

        var maxZ = int.MinValue;
        foreach (var sceneObject in _objects)
        {
            if (sceneObject.Layer != ActiveLayer)
            {
                continue;
            }

            maxZ = Math.Max(maxZ, sceneObject.ZIndex);
        }

        if (maxZ == int.MinValue)
        {
            maxZ = 0;
        }

        var changed = false;
        var nextZ = maxZ + 1;
        foreach (var sceneObject in entries)
        {
            if (sceneObject.ZIndex != nextZ)
            {
                sceneObject.ZIndex = nextZ;
                changed = true;
            }

            nextZ++;
        }

        if (!changed)
        {
            return false;
        }

        RequestRender();
        PushUndoSnapshot(history, true);
        RaiseSelectionGeometryChanged();
        return true;
    }

    public bool SendSelectionToBack()
    {
        if (!CanEditObjects())
        {
            return false;
        }

        var entries = new List<SceneCanvasObject>();
        foreach (var selectedObject in _selectedObjects)
        {
            if (selectedObject.Layer != ActiveLayer)
            {
                continue;
            }

            entries.Add(selectedObject);
        }

        if (entries.Count == 0)
        {
            return false;
        }

        var history = CaptureHistorySnapshot();
        entries.Sort((a, b) =>
        {
            var byZ = a.ZIndex.CompareTo(b.ZIndex);
            if (byZ != 0)
            {
                return byZ;
            }

            return GetObjectDrawOrder(a).CompareTo(GetObjectDrawOrder(b));
        });

        var minZ = int.MaxValue;
        foreach (var sceneObject in _objects)
        {
            if (sceneObject.Layer != ActiveLayer)
            {
                continue;
            }

            minZ = Math.Min(minZ, sceneObject.ZIndex);
        }

        if (minZ == int.MaxValue)
        {
            minZ = 0;
        }

        var changed = false;
        var nextZ = minZ - entries.Count;
        foreach (var sceneObject in entries)
        {
            if (sceneObject.ZIndex != nextZ)
            {
                sceneObject.ZIndex = nextZ;
                changed = true;
            }

            nextZ++;
        }

        if (!changed)
        {
            return false;
        }

        RequestRender();
        PushUndoSnapshot(history, true);
        RaiseSelectionGeometryChanged();
        return true;
    }

    public void CenterOn(Rect worldBoundsMeters)
    {
        var viewport = GetSceneViewportPixels();
        if (viewport.Width < 1d || viewport.Height < 1d)
        {
            return;
        }

        var centerX = (float)(worldBoundsMeters.X + (worldBoundsMeters.Width * 0.5d));
        var centerY = (float)(worldBoundsMeters.Y + (worldBoundsMeters.Height * 0.5d));
        var halfViewW = (float)(viewport.Width * 0.5d / _zoomPixelsPerMeter);
        var halfViewH = (float)(viewport.Height * 0.5d / _zoomPixelsPerMeter);

        _viewOriginMeters = new Vector2(centerX - halfViewW, centerY - halfViewH);
        ClampViewOriginToScene(viewport);
        UpdateScrollBars();
        RequestRender();
    }

    private void SceneCanvas_Loaded(object sender, RoutedEventArgs e)
    {
        _isReleased = false;
        UpdateScrollBars();
        RequestRender();
    }

    private void SceneCanvas_Unloaded(object sender, RoutedEventArgs e)
    {
        _isReleased = true;
        _interactionMode = InteractionMode.None;
        _activeObject = null;
        _activeGuideId = -1;
        _activeResizeHandle = ResizeHandle.None;
        _movingObjects.Clear();
        CancelInteractionHistory();
    }

    private void SceneCanvas_ActualThemeChanged(FrameworkElement sender, object args)
    {
        RefreshThemeResources();
        RequestRender();
    }

    private void Canvas_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        UpdateScrollBars();
        RequestRender();
    }

    private void HorizontalScrollBar_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        if (_isScrollSyncInProgress)
        {
            return;
        }

        _viewOriginMeters.X = (float)e.NewValue;
        RequestRender();
    }

    private void VerticalScrollBar_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        if (_isScrollSyncInProgress)
        {
            return;
        }

        _viewOriginMeters.Y = (float)e.NewValue;
        RequestRender();
    }

    private void Canvas_PointerPressed(object sender, PointerRoutedEventArgs e)
    {
        var point = e.GetCurrentPoint(_Canvas);
        if (point == null)
        {
            return;
        }

        _Canvas.Focus(FocusState.Pointer);

        var viewport = GetSceneViewportPixels();

        if (point.Properties.IsMiddleButtonPressed || point.Properties.IsRightButtonPressed)
        {
            if (!viewport.Contains(point.Position))
            {
                return;
            }

            _interactionMode = InteractionMode.Panning;
            _panStartPointerPixels = point.Position;
            _panStartViewOriginMeters = _viewOriginMeters;
            _Canvas.CapturePointer(e.Pointer);
            UpdateCursorForInteraction();
            e.Handled = true;
            return;
        }

        if (!point.Properties.IsLeftButtonPressed)
        {
            return;
        }

        var isCtrlPressed = e.KeyModifiers.HasFlag(VirtualKeyModifiers.Control);
        if (!viewport.Contains(point.Position))
        {
            if (CanEditObjects() && TryHandleGuidePointerPressed(point.Position, viewport, isCtrlPressed, e))
            {
                UpdateCursorForInteraction();
                e.Handled = true;
            }

            return;
        }

        var screen = ToVector2(point.Position);
        var world = ScreenToWorld(screen, viewport);
        _movingObjects.Clear();
        ClearMagnetGuide();

        if (IsRulerModeEnabled)
        {
            var constrainAngle = e.KeyModifiers.HasFlag(VirtualKeyModifiers.Shift);
            var allowSnap = MagnetEnabled && !isCtrlPressed;
            var measurePoint = ResolveMeasurePoint(world, allowSnap, out var snapped);
            if (constrainAngle && _hasMeasureStart && !_hasMeasureEnd)
            {
                measurePoint = SnapLinePointAngle(_measureStartMeters, measurePoint, RulerAngleSnapStepRadians);
            }

            _measureHoverMeters = measurePoint;
            _hasMeasureHover = true;
            _isMeasureHoverSnapped = snapped;
            if (!_hasMeasureStart || _hasMeasureEnd)
            {
                _measureStartMeters = measurePoint;
                _measureEndMeters = measurePoint;
                _hasMeasureStart = true;
                _hasMeasureEnd = false;
            }
            else
            {
                _measureEndMeters = measurePoint;
                _hasMeasureEnd = true;
            }

            e.Handled = true;
            RequestRender();
            return;
        }

        // Handle interactions for already selected object first.
        // Rotate handle is outside object bounds, so classic object hit test can miss it.
        if (!isCtrlPressed && CanEditObjects() && _selectedObjects.Count == 1 && SelectedObject != null && SelectedObject.Layer == ActiveLayer)
        {
            var selectedHandle = HitTestManipulationHandle(SelectedObject, point.Position, viewport);
            if (SelectedObject is SceneCanvasLineObject
                && (selectedHandle == ResizeHandle.LineStart || selectedHandle == ResizeHandle.LineEnd))
            {
                BeginInteractionHistory();
                _interactionMode = InteractionMode.MovingLinePoint;
                _activeObject = SelectedObject;
                _activeResizeHandle = selectedHandle;
                _Canvas.CapturePointer(e.Pointer);
                UpdateCursorForInteraction();
                SceneObjectClicked?.Invoke(this, new SceneCanvasObjectClickedEventArgs(SelectedObject, world, screen));
                e.Handled = true;
                RequestRender();
                return;
            }

            if (selectedHandle == ResizeHandle.Rotate && SelectedObject.CanRotate)
            {
                BeginInteractionHistory();
                _interactionMode = InteractionMode.RotatingObject;
                _activeObject = SelectedObject;
                _activeResizeHandle = ResizeHandle.Rotate;
                _manipulationStartPointerPixels = point.Position;
                _manipulationStartRotation = SelectedObject.RotationRadians;
                _manipulationPivotWorld = GetObjectCenterWorld(SelectedObject);
                _Canvas.CapturePointer(e.Pointer);
                UpdateCursorForInteraction();
                SceneObjectClicked?.Invoke(this, new SceneCanvasObjectClickedEventArgs(SelectedObject, world, screen));
                e.Handled = true;
                RequestRender();
                return;
            }

            if (selectedHandle != ResizeHandle.None && SelectedObject.CanResize)
            {
                BeginInteractionHistory();
                _interactionMode = InteractionMode.ResizingObject;
                _activeObject = SelectedObject;
                _activeResizeHandle = selectedHandle;
                _manipulationStartPointerWorld = world;
                _manipulationStartBounds = SelectedObject.BoundsMeters;
                _manipulationStartRotation = SelectedObject.RotationRadians;
                _manipulationPivotWorld = GetObjectCenterWorld(SelectedObject);
                _Canvas.CapturePointer(e.Pointer);
                UpdateCursorForInteraction();
                SceneObjectClicked?.Invoke(this, new SceneCanvasObjectClickedEventArgs(SelectedObject, world, screen));
                e.Handled = true;
                RequestRender();
                return;
            }
        }

        var hit = HitTestSceneObject(world, true);
        SceneObjectClicked?.Invoke(this, new SceneCanvasObjectClickedEventArgs(hit, world, screen));

        if (hit == null && CanEditObjects() && TryHandleGuidePointerPressed(point.Position, viewport, isCtrlPressed, e))
        {
            UpdateCursorForInteraction();
            e.Handled = true;
            return;
        }

        if (hit == null)
        {
            if (!isCtrlPressed)
            {
                ClearSelection();
            }

            _interactionMode = InteractionMode.AreaSelection;
            _activeObject = null;
            _activeResizeHandle = ResizeHandle.None;
            _areaSelectionStartPixels = point.Position;
            _areaSelectionCurrentPixels = point.Position;
            _areaSelectionAdditive = isCtrlPressed;
            _Canvas.CapturePointer(e.Pointer);
            UpdateCursorForInteraction();
            e.Handled = true;
            RequestRender();
            return;
        }

        if (isCtrlPressed)
        {
            ToggleSelection(hit);
            _interactionMode = InteractionMode.None;
            _activeObject = null;
            _activeResizeHandle = ResizeHandle.None;
            RequestRender();
            e.Handled = true;
            return;
        }

        var wasSelectedBeforePress = IsObjectSelected(hit);
        if (!wasSelectedBeforePress)
        {
            SetSingleSelection(hit);
        }

        if (RequireSelectionBeforeMove && !wasSelectedBeforePress)
        {
            _interactionMode = InteractionMode.None;
            _activeObject = null;
            _activeResizeHandle = ResizeHandle.None;
            RequestRender();
            e.Handled = true;
            return;
        }

        if (!CanEditObjects())
        {
            _interactionMode = InteractionMode.None;
            _activeObject = null;
            _activeResizeHandle = ResizeHandle.None;
            RequestRender();
            e.Handled = true;
            return;
        }

        PrepareMovingObjects(hit);

        if (hit != null && hit.Layer == ActiveLayer && hit.CanMove)
        {
            BeginInteractionHistory();
            _interactionMode = InteractionMode.MovingObject;
            _activeObject = hit;
            _activeResizeHandle = ResizeHandle.None;
            _manipulationStartPointerWorld = world;
            _manipulationStartBounds = hit.BoundsMeters;
            _Canvas.CapturePointer(e.Pointer);
            UpdateCursorForInteraction();
            e.Handled = true;
            RequestRender();
            return;
        }

        _interactionMode = InteractionMode.None;
        _activeObject = null;
        _activeResizeHandle = ResizeHandle.None;

        UpdateHoverCursor(point.Position, viewport, e.KeyModifiers);
        RequestRender();
    }

    private void Canvas_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
    {
        if (!CanEditObjects())
        {
            return;
        }

        var position = e.GetPosition(_Canvas);
        var viewport = GetSceneViewportPixels();

        if (TryHitGuideLine(position, viewport, out var guideId, out _))
        {
            var history = CaptureHistorySnapshot();
            if (RemoveGuideById(guideId))
            {
                PushUndoSnapshot(history, true);
                ClearMagnetGuide();
                RequestRender();
            }

            e.Handled = true;
            return;
        }

        if (!AreRulersVisible)
        {
            return;
        }

        var topInset = AreRulersVisible ? RulerTopHeightPx : 0f;
        var leftInset = AreRulersVisible ? RulerLeftWidthPx : 0f;

        var pointerX = (float)position.X;
        var pointerY = (float)position.Y;
        var inTopHeader = pointerY >= 0f && pointerY <= topInset && pointerX >= leftInset;
        var inLeftHeader = pointerX >= 0f && pointerX <= leftInset && pointerY >= topInset;

        if (!inTopHeader && !inLeftHeader)
        {
            return;
        }

        if (inTopHeader)
        {
            var sampleY = (float)(viewport.Y + (viewport.Height * 0.5d));
            var world = ScreenToWorld(new Vector2(pointerX, sampleY), viewport);
            AddVerticalGuide(world.X);
            e.Handled = true;
            return;
        }

        var sampleX = (float)(viewport.X + (viewport.Width * 0.5d));
        var worldY = ScreenToWorld(new Vector2(sampleX, pointerY), viewport);
        AddHorizontalGuide(worldY.Y);
        e.Handled = true;
    }

    private void Canvas_PointerMoved(object sender, PointerRoutedEventArgs e)
    {
        var point = e.GetCurrentPoint(_Canvas);
        if (point == null)
        {
            return;
        }

        var viewport = GetSceneViewportPixels();
        if (IsRulerModeEnabled && _interactionMode == InteractionMode.None)
        {
            if (!viewport.Contains(point.Position))
            {
                var hadHover = _hasMeasureHover || _hasMagnetGuide;
                _hasMeasureHover = false;
                _isMeasureHoverSnapped = false;
                ClearMagnetGuide();
                if (hadHover)
                {
                    RequestRender();
                }

                UpdateHoverCursor(point.Position, viewport, e.KeyModifiers);
                return;
            }

            var world = ScreenToWorld(ToVector2(point.Position), viewport);
            var constrainAngle = e.KeyModifiers.HasFlag(VirtualKeyModifiers.Shift);
            var allowSnap = MagnetEnabled && !e.KeyModifiers.HasFlag(VirtualKeyModifiers.Control);
            var measurePoint = ResolveMeasurePoint(world, allowSnap, out var snapped);
            if (constrainAngle && _hasMeasureStart && !_hasMeasureEnd)
            {
                measurePoint = SnapLinePointAngle(_measureStartMeters, measurePoint, RulerAngleSnapStepRadians);
            }

            _measureHoverMeters = measurePoint;
            _hasMeasureHover = true;
            _isMeasureHoverSnapped = snapped;

            if (_hasMeasureStart && !_hasMeasureEnd)
            {
                _measureEndMeters = measurePoint;
            }

            RequestRender();
            e.Handled = true;
            UpdateHoverCursor(point.Position, viewport, e.KeyModifiers);
            return;
        }

        if (_interactionMode == InteractionMode.None)
        {
            UpdateHoverCursor(point.Position, viewport, e.KeyModifiers);
            return;
        }

        UpdateCursorForInteraction();

        switch (_interactionMode)
        {
            case InteractionMode.Panning:
            {
                var deltaX = (float)(point.Position.X - _panStartPointerPixels.X);
                var deltaY = (float)(point.Position.Y - _panStartPointerPixels.Y);

                _viewOriginMeters = _panStartViewOriginMeters - new Vector2(deltaX / _zoomPixelsPerMeter, deltaY / _zoomPixelsPerMeter);
                ClampViewOriginToScene(viewport);
                UpdateScrollBars();
                RequestRender();
                e.Handled = true;
                return;
            }

            case InteractionMode.MovingObject:
            {
                if (_activeObject == null)
                {
                    return;
                }

                var world = ScreenToWorld(ToVector2(point.Position), viewport);
                var delta = world - _manipulationStartPointerWorld;
                var candidate = new Rect(
                    _manipulationStartBounds.X + delta.X,
                    _manipulationStartBounds.Y + delta.Y,
                    _manipulationStartBounds.Width,
                    _manipulationStartBounds.Height);
                var hadGuide = _hasMagnetGuide;
                var previousGuideSource = _magnetGuideSourceMeters;
                var previousGuideTarget = _magnetGuideTargetMeters;
                var suppressMagnet = e.KeyModifiers.HasFlag(VirtualKeyModifiers.Control);

                if (!suppressMagnet)
                {
                    candidate = ApplyMagnetToMove(_activeObject, candidate);
                }
                else
                {
                    ClearMagnetGuide();
                }

                candidate = ApplyPrecision(candidate);
                var appliedDeltaX = candidate.X - _manipulationStartBounds.X;
                var appliedDeltaY = candidate.Y - _manipulationStartBounds.Y;

                var changed = false;
                var guideChanged = hadGuide != _hasMagnetGuide ||
                                   (_hasMagnetGuide &&
                                    (!ArePointsClose(previousGuideSource, _magnetGuideSourceMeters) ||
                                     !ArePointsClose(previousGuideTarget, _magnetGuideTargetMeters)));
                foreach (var moveObject in _movingObjects)
                {
                    var objectCandidate = new Rect(
                        moveObject.StartBounds.X + appliedDeltaX,
                        moveObject.StartBounds.Y + appliedDeltaY,
                        moveObject.StartBounds.Width,
                        moveObject.StartBounds.Height);

                    objectCandidate = ApplyPrecision(objectCandidate);
                    if (AreRectsClose(moveObject.SceneObject.BoundsMeters, objectCandidate))
                    {
                        continue;
                    }

                    moveObject.SceneObject.BoundsMeters = objectCandidate;
                    _spatialIndex.Update(moveObject.SceneObject);
                    changed = true;
                }

                if (changed)
                {
                    _interactionChanged = true;
                    _sceneBoundsDirty = true;
                    RaiseSelectionGeometryChanged();
                }

                if (changed || guideChanged)
                {
                    RequestRender();
                }

                e.Handled = true;
                return;
            }

            case InteractionMode.MovingGuideLine:
            {
                if (!TryGetGuideById(_activeGuideId, out var guide))
                {
                    return;
                }

                var world = ScreenToWorld(ToVector2(point.Position), viewport);
                var suppressMagnet = e.KeyModifiers.HasFlag(VirtualKeyModifiers.Control);
                double candidate = _activeGuideIsVertical ? world.X : world.Y;
                candidate = SnapToStep(candidate, PrecisionMeters);

                if (!suppressMagnet && MagnetEnabled)
                {
                    candidate = ApplyMagnetToGuide(guide, candidate, world, viewport);
                }
                else
                {
                    ClearMagnetGuide();
                }

                candidate = SnapToStep(candidate, PrecisionMeters);
                if (!double.IsFinite(candidate) || Math.Abs(guide.PositionMeters - candidate) < 0.00001d)
                {
                    e.Handled = true;
                    return;
                }

                guide.PositionMeters = candidate;
                _interactionChanged = true;
                RequestRender();
                e.Handled = true;
                return;
            }

            case InteractionMode.MovingLinePoint:
            {
                if (_activeObject is not SceneCanvasLineObject lineObject)
                {
                    return;
                }

                var world = ScreenToWorld(ToVector2(point.Position), viewport);
                var snappedWorld = new Vector2(
                    (float)SnapToStep(world.X, PrecisionMeters),
                    (float)SnapToStep(world.Y, PrecisionMeters));
                var constrainAngle = e.KeyModifiers.HasFlag(VirtualKeyModifiers.Shift);
                var suppressMagnet = e.KeyModifiers.HasFlag(VirtualKeyModifiers.Control);
                var pointChanged = false;

                if (_activeResizeHandle == ResizeHandle.LineStart)
                {
                    var previousPoint = lineObject.StartPointMeters;
                    if (constrainAngle)
                    {
                        snappedWorld = SnapLinePointAngle(lineObject.EndPointMeters, snappedWorld, RotationSnapStepRadians);
                    }

                    if (!suppressMagnet)
                    {
                        snappedWorld = ApplyMagnetToPoint(lineObject, snappedWorld);
                    }
                    else
                    {
                        ClearMagnetGuide();
                    }

                    lineObject.StartPointMeters = snappedWorld;
                    pointChanged = !ArePointsClose(previousPoint, lineObject.StartPointMeters);
                }
                else if (_activeResizeHandle == ResizeHandle.LineEnd)
                {
                    var previousPoint = lineObject.EndPointMeters;
                    if (constrainAngle)
                    {
                        snappedWorld = SnapLinePointAngle(lineObject.StartPointMeters, snappedWorld, RotationSnapStepRadians);
                    }

                    if (!suppressMagnet)
                    {
                        snappedWorld = ApplyMagnetToPoint(lineObject, snappedWorld);
                    }
                    else
                    {
                        ClearMagnetGuide();
                    }

                    lineObject.EndPointMeters = snappedWorld;
                    pointChanged = !ArePointsClose(previousPoint, lineObject.EndPointMeters);
                }

                if (pointChanged)
                {
                    _interactionChanged = true;
                }

                _spatialIndex.Update(lineObject);
                _sceneBoundsDirty = true;
                RequestRender();
                RaiseSelectionGeometryChanged(lineObject);
                e.Handled = true;
                return;
            }

            case InteractionMode.ResizingObject:
            {
                if (_activeObject == null)
                {
                    return;
                }

                var world = ScreenToWorld(ToVector2(point.Position), viewport);
                var hadGuide = _hasMagnetGuide;
                var previousGuideSource = _magnetGuideSourceMeters;
                var previousGuideTarget = _magnetGuideTargetMeters;
                var suppressMagnet = e.KeyModifiers.HasFlag(VirtualKeyModifiers.Control);

                if (!suppressMagnet)
                {
                    world = ApplyMagnetToPoint(_activeObject, world);
                }
                else
                {
                    ClearMagnetGuide();
                }

                var resized = ResizeBounds(
                    _manipulationStartBounds,
                    _manipulationStartRotation,
                    _manipulationPivotWorld,
                    _activeResizeHandle,
                    world,
                    true,
                    PrecisionMeters);

                resized = ApplyPrecision(resized);
                var guideChanged = hadGuide != _hasMagnetGuide ||
                                   (_hasMagnetGuide &&
                                    (!ArePointsClose(previousGuideSource, _magnetGuideSourceMeters) ||
                                     !ArePointsClose(previousGuideTarget, _magnetGuideTargetMeters)));
                if (AreRectsClose(_activeObject.BoundsMeters, resized))
                {
                    if (guideChanged)
                    {
                        RequestRender();
                    }

                    e.Handled = true;
                    return;
                }

                _activeObject.BoundsMeters = resized;
                _spatialIndex.Update(_activeObject);
                _interactionChanged = true;
                _sceneBoundsDirty = true;
                RequestRender();
                RaiseSelectionGeometryChanged(_activeObject);
                e.Handled = true;
                return;
            }

            case InteractionMode.RotatingObject:
            {
                if (_activeObject == null)
                {
                    return;
                }

                var pivotScreen = WorldToScreen(_manipulationPivotWorld, viewport);
                var startVector = new Vector2(
                    (float)(_manipulationStartPointerPixels.X - pivotScreen.X),
                    (float)(_manipulationStartPointerPixels.Y - pivotScreen.Y));
                var currentVector = new Vector2(
                    (float)(point.Position.X - pivotScreen.X),
                    (float)(point.Position.Y - pivotScreen.Y));

                if (startVector.LengthSquared() < 0.0001f || currentVector.LengthSquared() < 0.0001f)
                {
                    return;
                }

                var startAngle = Math.Atan2(startVector.Y, startVector.X);
                var currentAngle = Math.Atan2(currentVector.Y, currentVector.X);
                var rotation = _manipulationStartRotation + (currentAngle - startAngle);
                if (!e.KeyModifiers.HasFlag(VirtualKeyModifiers.Control))
                {
                    rotation = SnapToStep(rotation, RotationSnapStepRadians);
                }

                if (Math.Abs(_activeObject.RotationRadians - rotation) < 0.00001d)
                {
                    e.Handled = true;
                    return;
                }

                _activeObject.RotationRadians = rotation;
                _spatialIndex.Update(_activeObject);
                _interactionChanged = true;
                _sceneBoundsDirty = true;
                RequestRender();
                RaiseSelectionGeometryChanged(_activeObject);
                e.Handled = true;
                return;
            }

            case InteractionMode.AreaSelection:
            {
                _areaSelectionCurrentPixels = point.Position;
                RequestRender();
                e.Handled = true;
                return;
            }
        }
    }

    private void Canvas_PointerReleased(object sender, PointerRoutedEventArgs e)
    {
        if (_interactionMode == InteractionMode.None)
        {
            return;
        }

        if (_interactionMode == InteractionMode.AreaSelection)
        {
            var screenRect = GetAreaSelectionRectPixels();
            if (screenRect.Width >= 3d && screenRect.Height >= 3d)
            {
                var worldRect = ScreenRectToWorldRect(screenRect, GetSceneViewportPixels());
                SelectByArea(worldRect, _areaSelectionAdditive);
            }
        }
        else if (_interactionMode == InteractionMode.MovingObject)
        {
            foreach (var moveObject in _movingObjects)
            {
                _spatialIndex.Update(moveObject.SceneObject);
            }
        }
        else if (_activeObject != null)
        {
            _spatialIndex.Update(_activeObject);
        }

        _sceneBoundsDirty = true;
        UpdateScrollBars();

        if (_interactionMode is InteractionMode.MovingObject
            or InteractionMode.MovingGuideLine
            or InteractionMode.MovingLinePoint
            or InteractionMode.ResizingObject
            or InteractionMode.RotatingObject)
        {
            EndInteractionHistory(_interactionChanged);
        }
        else
        {
            CancelInteractionHistory();
        }

        _interactionMode = InteractionMode.None;
        _activeObject = null;
        _activeGuideId = -1;
        _activeResizeHandle = ResizeHandle.None;
        _movingObjects.Clear();
        ClearMagnetGuide();
        _Canvas.ReleasePointerCapture(e.Pointer);
        UpdateHoverCursor(e.GetCurrentPoint(_Canvas)?.Position ?? new Point(-1d, -1d), GetSceneViewportPixels(), e.KeyModifiers);
        RequestRender();
        e.Handled = true;
    }

    private void Canvas_PointerExited(object sender, PointerRoutedEventArgs e)
    {
        if (_interactionMode != InteractionMode.None)
        {
            return;
        }

        if (IsRulerModeEnabled && (_hasMeasureHover || _hasMagnetGuide))
        {
            _hasMeasureHover = false;
            _isMeasureHoverSnapped = false;
            ClearMagnetGuide();
            RequestRender();
        }

        _Canvas.ReleasePointerCaptures();
        SetCursor(InputSystemCursorShape.Arrow);
    }

    private void UpdateHoverCursor(Point pointerPixels, Rect sceneViewportPixels, VirtualKeyModifiers keyModifiers)
    {
        if (_interactionMode != InteractionMode.None)
        {
            UpdateCursorForInteraction();
            return;
        }

        if (IsRulerModeEnabled)
        {
            SetCursor(sceneViewportPixels.Contains(pointerPixels)
                ? InputSystemCursorShape.Cross
                : InputSystemCursorShape.Arrow);
            return;
        }

        if (CanEditObjects() && TryHitGuideRemoveHandle(pointerPixels, sceneViewportPixels, out _))
        {
            SetCursor(InputSystemCursorShape.Hand);
            return;
        }

        if (sceneViewportPixels.Contains(pointerPixels))
        {
            if (!keyModifiers.HasFlag(VirtualKeyModifiers.Control)
                && _selectedObjects.Count == 1
                && SelectedObject != null
                && SelectedObject.Layer == ActiveLayer)
            {
                var handle = HitTestManipulationHandle(SelectedObject, pointerPixels, sceneViewportPixels);
                if (handle != ResizeHandle.None)
                {
                    SetCursor(GetCursorForResizeHandle(handle));
                    return;
                }
            }

            var world = ScreenToWorld(ToVector2(pointerPixels), sceneViewportPixels);
            var hitObject = HitTestSceneObject(world, true);
            if (hitObject != null)
            {
                SetCursor(CanEditObjects() && hitObject.CanMove
                    ? InputSystemCursorShape.SizeAll
                    : InputSystemCursorShape.Arrow);
                return;
            }
        }

        if (CanEditObjects() && TryHitGuideLine(pointerPixels, sceneViewportPixels, out _, out var isVerticalGuide))
        {
            SetCursor(isVerticalGuide
                ? InputSystemCursorShape.SizeWestEast
                : InputSystemCursorShape.SizeNorthSouth);
            return;
        }

        SetCursor(InputSystemCursorShape.Arrow);
    }

    private void UpdateCursorForInteraction()
    {
        switch (_interactionMode)
        {
            case InteractionMode.Panning:
            case InteractionMode.MovingObject:
                SetCursor(InputSystemCursorShape.SizeAll);
                break;
            case InteractionMode.MovingGuideLine:
                SetCursor(_activeGuideIsVertical
                    ? InputSystemCursorShape.SizeWestEast
                    : InputSystemCursorShape.SizeNorthSouth);
                break;
            case InteractionMode.MovingLinePoint:
            case InteractionMode.AreaSelection:
                SetCursor(InputSystemCursorShape.Cross);
                break;
            case InteractionMode.ResizingObject:
                SetCursor(GetCursorForResizeHandle(_activeResizeHandle));
                break;
            case InteractionMode.RotatingObject:
                SetCursor(InputSystemCursorShape.Hand);
                break;
            default:
                SetCursor(InputSystemCursorShape.Arrow);
                break;
        }
    }

    private static InputSystemCursorShape GetCursorForResizeHandle(ResizeHandle handle)
    {
        return handle switch
        {
            ResizeHandle.Left or ResizeHandle.Right => InputSystemCursorShape.SizeWestEast,
            ResizeHandle.Top or ResizeHandle.Bottom => InputSystemCursorShape.SizeNorthSouth,
            ResizeHandle.TopLeft or ResizeHandle.BottomRight => InputSystemCursorShape.SizeNorthwestSoutheast,
            ResizeHandle.TopRight or ResizeHandle.BottomLeft => InputSystemCursorShape.SizeNortheastSouthwest,
            ResizeHandle.Rotate => InputSystemCursorShape.Hand,
            ResizeHandle.LineStart or ResizeHandle.LineEnd => InputSystemCursorShape.Cross,
            _ => InputSystemCursorShape.Arrow
        };
    }

    private void SetCursor(InputSystemCursorShape shape)
    {
        if (_cursorShape == shape || _Canvas == null)
        {
            return;
        }

        if (!_cursorCache.TryGetValue(shape, out var cursor))
        {
            cursor = InputSystemCursor.Create(shape);
            _cursorCache[shape] = cursor;
        }

        ProtectedCursor = cursor;
        _cursorShape = shape;
    }

    private void Canvas_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
    {
        var point = e.GetCurrentPoint(_Canvas);
        if (point == null)
        {
            return;
        }

        var viewport = GetSceneViewportPixels();
        var wheelDelta = point.Properties.MouseWheelDelta;
        if (wheelDelta == 0)
        {
            return;
        }

        if (!viewport.Contains(point.Position))
        {
            return;
        }

        if (e.KeyModifiers.HasFlag(VirtualKeyModifiers.Control))
        {
            var factor = wheelDelta > 0 ? WheelZoomFactor : (1f / WheelZoomFactor);
            SetZoom(_zoomPixelsPerMeter * factor, point.Position);
            e.Handled = true;
            return;
        }

        var visible = GetVisibleWorldBounds(viewport);
        var panStep = (float)Math.Max(0.5d, visible.Height * 0.12d);

        if (e.KeyModifiers.HasFlag(VirtualKeyModifiers.Shift))
        {
            _viewOriginMeters.X += wheelDelta > 0 ? -panStep : panStep;
        }
        else
        {
            _viewOriginMeters.Y += wheelDelta > 0 ? -panStep : panStep;
        }

        ClampViewOriginToScene(viewport);
        UpdateScrollBars();
        RequestRender();
        e.Handled = true;
    }

    private void Canvas_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
    {
        var viewport = GetSceneViewportPixels();
        if (viewport.Width < 1d || viewport.Height < 1d)
        {
            return;
        }

        var isCtrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
        var isShiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
        var isAltPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);

        if (isCtrlPressed && isAltPressed && isShiftPressed)
        {
            switch (e.Key)
            {
                case VirtualKey.Left:
                    e.Handled = LayoutSelection(SelectionLayout.PackLeftToRight);
                    return;
                case VirtualKey.Right:
                    e.Handled = LayoutSelection(SelectionLayout.PackRightToLeft);
                    return;
                case VirtualKey.H:
                    e.Handled = LayoutSelection(SelectionLayout.DistributeHorizontal);
                    return;
                case VirtualKey.Up:
                    e.Handled = LayoutSelection(SelectionLayout.PackTopToBottom);
                    return;
                case VirtualKey.Down:
                    e.Handled = LayoutSelection(SelectionLayout.PackBottomToTop);
                    return;
                case VirtualKey.V:
                    e.Handled = LayoutSelection(SelectionLayout.DistributeVertical);
                    return;
                case VirtualKey.X:
                    e.Handled = LayoutSelection(SelectionLayout.CenterActiveHorizontal);
                    return;
                case VirtualKey.Y:
                    e.Handled = LayoutSelection(SelectionLayout.CenterActiveVertical);
                    return;
            }
        }

        if (isCtrlPressed && isAltPressed)
        {
            switch (e.Key)
            {
                case VirtualKey.Left:
                    e.Handled = AlignSelection(SelectionAlignment.Left);
                    return;
                case VirtualKey.Right:
                    e.Handled = AlignSelection(SelectionAlignment.Right);
                    return;
                case VirtualKey.H:
                    e.Handled = AlignSelection(SelectionAlignment.Center);
                    return;
                case VirtualKey.Up:
                    e.Handled = AlignSelection(SelectionAlignment.Top);
                    return;
                case VirtualKey.Down:
                    e.Handled = AlignSelection(SelectionAlignment.Bottom);
                    return;
                case VirtualKey.V:
                    e.Handled = AlignSelection(SelectionAlignment.Middle);
                    return;
                case VirtualKey.Q:
                    e.Handled = RotateSelectionLeft();
                    return;
                case VirtualKey.E:
                    e.Handled = RotateSelectionRight();
                    return;
                case VirtualKey.PageUp:
                    e.Handled = MoveLayoutUp(ActiveLayer);
                    return;
                case VirtualKey.PageDown:
                    e.Handled = MoveLayoutDown(ActiveLayer);
                    return;
            }
        }

        if (isCtrlPressed && isShiftPressed)
        {
            switch (e.Key)
            {
                case VirtualKey.G:
                    e.Handled = AddVerticalGuide();
                    return;
                case VirtualKey.J:
                    e.Handled = AddHorizontalGuide();
                    return;
                case VirtualKey.F:
                    e.Handled = BringSelectionToFront();
                    return;
                case VirtualKey.B:
                    e.Handled = SendSelectionToBack();
                    return;
                case VirtualKey.W:
                    e.Handled = MatchSelectionWidth();
                    return;
                case VirtualKey.H:
                    e.Handled = MatchSelectionHeight();
                    return;
                case VirtualKey.N:
                    if (CanEditObjects())
                    {
                        var addedLayoutId = AddLayout();
                        if (addedLayoutId >= 0)
                        {
                            ActiveLayer = addedLayoutId;
                            e.Handled = true;
                        }
                    }
                    return;
                case VirtualKey.Delete:
                    e.Handled = RemoveLayout(ActiveLayer);
                    return;
                case VirtualKey.M:
                    e.Handled = MoveSelectionToLayout(ActiveLayer) > 0;
                    return;
                case VirtualKey.R:
                    AreRulersVisible = !AreRulersVisible;
                    e.Handled = true;
                    return;
            }
        }

        if (e.Key == VirtualKey.F2 && CanEditObjects())
        {
            RenameLayoutRequested?.Invoke(this, ActiveLayer);
            e.Handled = true;
            return;
        }

        if (isCtrlPressed)
        {
            switch (e.Key)
            {
                case VirtualKey.C:
                    CopyRequested?.Invoke(this, EventArgs.Empty);
                    e.Handled = true;
                    return;

                case VirtualKey.X:
                    if (CanEditObjects())
                    {
                        CutRequested?.Invoke(this, EventArgs.Empty);
                        e.Handled = true;
                    }
                    return;

                case VirtualKey.V:
                    if (CanEditObjects())
                    {
                        PasteRequested?.Invoke(this, EventArgs.Empty);
                        e.Handled = true;
                    }
                    return;

                case VirtualKey.Z:
                    e.Handled = UndoLastChange();
                    return;

                case VirtualKey.D:
                    CloneRequested?.Invoke(this, EventArgs.Empty);
                    e.Handled = true;
                    return;

                case VirtualKey.E:
                    IsEditingEnabled = !IsEditingEnabled;
                    e.Handled = true;
                    return;

                case VirtualKey.R:
                    IsRulerModeEnabled = !IsRulerModeEnabled;
                    e.Handled = true;
                    return;

                case VirtualKey.M:
                    MagnetEnabled = !MagnetEnabled;
                    e.Handled = true;
                    return;

                case VirtualKey.G:
                    IsGridVisible = !IsGridVisible;
                    e.Handled = true;
                    return;

                case VirtualKey.Number0:
                    FitToAllObjects();
                    e.Handled = true;
                    return;

                case VirtualKey.Number9:
                    FitToSelection();
                    e.Handled = true;
                    return;

                case VirtualKey.Add:
                    SetZoom(_zoomPixelsPerMeter * WheelZoomFactor);
                    e.Handled = true;
                    return;

                case VirtualKey.Subtract:
                    SetZoom(_zoomPixelsPerMeter / WheelZoomFactor);
                    e.Handled = true;
                    return;
            }
        }

        var step = (float)PrecisionMeters;

        switch (e.Key)
        {
            case VirtualKey.Add:
            case VirtualKey.GamepadRightShoulder:
                SetZoom(_zoomPixelsPerMeter * WheelZoomFactor);
                e.Handled = true;
                break;

            case VirtualKey.Subtract:
            case VirtualKey.GamepadLeftShoulder:
                SetZoom(_zoomPixelsPerMeter / WheelZoomFactor);
                e.Handled = true;
                break;

            case VirtualKey.Left:
                e.Handled = CanEditObjects() && MoveSelectedObjectsWithHistory(-step, 0f);
                break;

            case VirtualKey.Right:
                e.Handled = CanEditObjects() && MoveSelectedObjectsWithHistory(step, 0f);
                break;

            case VirtualKey.Up:
                e.Handled = CanEditObjects() && MoveSelectedObjectsWithHistory(0f, -step);
                break;

            case VirtualKey.Down:
                e.Handled = CanEditObjects() && MoveSelectedObjectsWithHistory(0f, step);
                break;

            case VirtualKey.Delete:
                if (CanEditObjects() && DeleteRequested != null)
                {
                    DeleteRequested.Invoke(this, EventArgs.Empty);
                    e.Handled = true;
                }
                else if (CanEditObjects())
                {
                    e.Handled = DeleteSelectedObjects();
                }
                break;
        }
    }

    private void Canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
    {
        var ds = args.DrawingSession;
        var sceneViewport = GetSceneViewportPixels();

        ds.Clear(_surfaceBackgroundColor);

        DrawScene(ds, sceneViewport);
        if (AreRulersVisible)
        {
            DrawRulers(ds, sceneViewport);
        }
    }

    private void DrawScene(CanvasDrawingSession ds, Rect sceneViewportPixels)
    {
        if (sceneViewportPixels.Width <= 0d || sceneViewportPixels.Height <= 0d)
        {
            return;
        }

        var visibleWorldBounds = GetVisibleWorldBounds(sceneViewportPixels);

        using (ds.CreateLayer(1f, sceneViewportPixels))
        {
            ds.FillRectangle(sceneViewportPixels, _sceneBackgroundColor);
            if (IsGridVisible)
            {
                DrawGrid(ds, sceneViewportPixels, visibleWorldBounds);
            }
            DrawGuideLines(ds, sceneViewportPixels);

            var worldToScreen = CreateWorldToScreenMatrix(sceneViewportPixels);
            if (!Matrix3x2.Invert(worldToScreen, out var screenToWorld))
            {
                screenToWorld = Matrix3x2.Identity;
            }

            _visibleObjects.Clear();
            _queryToken = _queryToken == int.MaxValue ? 1 : _queryToken + 1;
            _spatialIndex.Query(visibleWorldBounds, _visibleObjects, _queryToken);
            for (var i = _visibleObjects.Count - 1; i >= 0; i--)
            {
                if (IsSceneObjectVisible(_visibleObjects[i]))
                {
                    continue;
                }

                _visibleObjects.RemoveAt(i);
            }
            _visibleObjects.Sort(CompareObjectsForDraw);

            ds.Transform = worldToScreen;

            var drawContext = new SceneCanvasDrawContext(
                ds,
                visibleWorldBounds,
                sceneViewportPixels,
                _zoomPixelsPerMeter,
                worldToScreen,
                screenToWorld);

            using (ds.CreateLayer(InactiveLayerOpacity))
            {
                foreach (var sceneObject in _visibleObjects)
                {
                    if (sceneObject.Layer == ActiveLayer)
                    {
                        continue;
                    }

                    sceneObject.OnDraw(drawContext);
                }
            }

            foreach (var sceneObject in _visibleObjects)
            {
                if (sceneObject.Layer != ActiveLayer)
                {
                    continue;
                }

                sceneObject.OnDraw(drawContext);
            }

            if (_externalDragPreviewObject != null)
            {
                using (ds.CreateLayer(0.72f))
                {
                    _externalDragPreviewObject.OnDraw(drawContext);
                }
            }

            ds.Transform = Matrix3x2.Identity;

            foreach (var selectedObject in _selectedObjects)
            {
                if (!IsSceneObjectVisible(selectedObject))
                {
                    continue;
                }

                DrawSelection(
                    ds,
                    sceneViewportPixels,
                    selectedObject,
                    ReferenceEquals(selectedObject, SelectedObject));
            }

            DrawMagnetGuide(ds, sceneViewportPixels);
            DrawMeasureGuide(ds, sceneViewportPixels);
            DrawMeasureHoverPoint(ds, sceneViewportPixels);
            DrawNavigationPin(ds, sceneViewportPixels);

            if (_interactionMode == InteractionMode.AreaSelection)
            {
                DrawAreaSelection(ds);
            }
        }
    }

    private void DrawSelection(
        CanvasDrawingSession ds,
        Rect sceneViewportPixels,
        SceneCanvasObject sceneObject,
        bool showManipulationHandles)
    {
        if (sceneObject is SceneCanvasLineObject lineObject)
        {
            DrawLineSelection(ds, sceneViewportPixels, lineObject, showManipulationHandles);
            return;
        }

        GetSelectionFramePixels(
            sceneObject,
            sceneViewportPixels,
            out var topLeft,
            out var topRight,
            out var bottomRight,
            out var bottomLeft,
            out var center);

        ExpandSelectionFrame(
            topLeft,
            topRight,
            bottomRight,
            bottomLeft,
            center,
            SelectionFrameStrokePixels * 0.5f,
            out var topLeftOuter,
            out var topRightOuter,
            out var bottomRightOuter,
            out var bottomLeftOuter);

        ds.DrawLine(topLeftOuter, topRightOuter, _selectionColor, SelectionFrameStrokePixels);
        ds.DrawLine(topRightOuter, bottomRightOuter, _selectionColor, SelectionFrameStrokePixels);
        ds.DrawLine(bottomRightOuter, bottomLeftOuter, _selectionColor, SelectionFrameStrokePixels);
        ds.DrawLine(bottomLeftOuter, topLeftOuter, _selectionColor, SelectionFrameStrokePixels);

        if (!showManipulationHandles || sceneObject.Layer != ActiveLayer)
        {
            return;
        }

        if (sceneObject.CanResize)
        {
            DrawResizeHandles(ds, topLeft, topRight, bottomRight, bottomLeft, center);
        }

        if (sceneObject.CanRotate)
        {
            DrawRotateHandle(ds, topLeft, topRight, bottomRight, bottomLeft, center);
        }
    }

    private void DrawLineSelection(
        CanvasDrawingSession ds,
        Rect sceneViewportPixels,
        SceneCanvasLineObject lineObject,
        bool showManipulationHandles)
    {
        var selectionBounds = lineObject.SpatialBoundsMeters;
        var topLeftWorld = new Vector2((float)selectionBounds.X, (float)selectionBounds.Y);
        var bottomRightWorld = new Vector2((float)(selectionBounds.X + selectionBounds.Width), (float)(selectionBounds.Y + selectionBounds.Height));
        var topLeft = WorldToScreen(topLeftWorld, sceneViewportPixels);
        var bottomRight = WorldToScreen(bottomRightWorld, sceneViewportPixels);

        var left = Math.Min(topLeft.X, bottomRight.X);
        var top = Math.Min(topLeft.Y, bottomRight.Y);
        var width = Math.Max(1f, Math.Abs(bottomRight.X - topLeft.X));
        var height = Math.Max(1f, Math.Abs(bottomRight.Y - topLeft.Y));
        var halfStroke = SelectionFrameStrokePixels * 0.5f;
        var selectionRect = new Rect(left, top, width, height);
        var outerRect = new Rect(
            selectionRect.X - halfStroke,
            selectionRect.Y - halfStroke,
            selectionRect.Width + SelectionFrameStrokePixels,
            selectionRect.Height + SelectionFrameStrokePixels);
        ds.DrawRectangle(outerRect, _selectionColor, SelectionFrameStrokePixels);

        var center = new Vector2(
            (float)(selectionRect.X + (selectionRect.Width * 0.5d)),
            (float)(selectionRect.Y + (selectionRect.Height * 0.5d)));
        var start = WorldToScreen(lineObject.StartPointMeters, sceneViewportPixels);
        var end = WorldToScreen(lineObject.EndPointMeters, sceneViewportPixels);
        if (!showManipulationHandles || lineObject.Layer != ActiveLayer)
        {
            return;
        }

        DrawLinePointHandle(ds, start);
        DrawLinePointHandle(ds, end);
    }

    private void DrawLinePointHandle(CanvasDrawingSession ds, Vector2 point)
    {
        var radius = Math.Max(4f, ResizeHandleSizePx * 0.6f);
        ds.FillCircle(point, radius, _sceneBackgroundColor);
        ds.DrawCircle(point, radius, _selectionColor, 1.25f);
    }

    private void DrawMagnetGuide(CanvasDrawingSession ds, Rect sceneViewportPixels)
    {
        if (!_hasMagnetGuide)
        {
            return;
        }

        var target = WorldToScreen(_magnetGuideTargetMeters, sceneViewportPixels);

        var guideColor = _magnetGuideColor;
        const float pointRadius = 4.2f;
        ds.FillCircle(target, pointRadius, _sceneBackgroundColor);
        ds.DrawCircle(target, pointRadius, guideColor, 1.25f);
    }

    private void DrawMeasureGuide(CanvasDrawingSession ds, Rect sceneViewportPixels)
    {
        if (!IsRulerModeEnabled || !_hasMeasureStart)
        {
            return;
        }

        var start = WorldToScreen(_measureStartMeters, sceneViewportPixels);
        var end = WorldToScreen(_measureEndMeters, sceneViewportPixels);

        var guideColor = Color.FromArgb(255, 23, 94, 180);
        var snappedPointColor = Color.FromArgb(255, 210, 35, 35);
        var endPointColor = (!_hasMeasureEnd && _isMeasureHoverSnapped) ? snappedPointColor : guideColor;
        const float pointRadius = 3.8f;
        const float strokeWidth = 1.1f;

        ds.DrawLine(start, end, guideColor, strokeWidth);
        ds.FillCircle(start, pointRadius, _sceneBackgroundColor);
        ds.DrawCircle(start, pointRadius, guideColor, 1f);
        ds.FillCircle(end, pointRadius, _sceneBackgroundColor);
        ds.DrawCircle(end, pointRadius, endPointColor, 1f);

        var distanceMeters = Vector2.Distance(_measureStartMeters, _measureEndMeters);
        var label = FormatMeters(distanceMeters);
        var mid = (start + end) * 0.5f;
        var labelWidth = Math.Max(32f, (float)(label.Length * 7.2d));
        const float labelHeight = 16f;
        var labelX = mid.X - (labelWidth * 0.5f);
        var labelY = mid.Y - labelHeight - 10f;

        var backgroundRect = new Rect(
            labelX - 4f,
            labelY - 2f,
            labelWidth + 8f,
            labelHeight + 4f);
        var textRect = new Rect(labelX, labelY, labelWidth, labelHeight);

        ds.FillRoundedRectangle(backgroundRect, 3f, 3f, _sceneBackgroundColor);
        ds.DrawRoundedRectangle(backgroundRect, 3f, 3f, guideColor, 1f);
        ds.DrawText(label, textRect, guideColor, OverlayCenteredTextFormat);
    }

    private void DrawMeasureHoverPoint(CanvasDrawingSession ds, Rect sceneViewportPixels)
    {
        if (!IsRulerModeEnabled || !_hasMeasureHover)
        {
            return;
        }

        if (_hasMeasureStart && !_hasMeasureEnd)
        {
            return;
        }

        var point = WorldToScreen(_measureHoverMeters, sceneViewportPixels);
        var color = _isMeasureHoverSnapped
            ? Color.FromArgb(255, 210, 35, 35)
            : Color.FromArgb(255, 23, 94, 180);

        const float pointRadius = 4.2f;
        ds.FillCircle(point, pointRadius, _sceneBackgroundColor);
        ds.DrawCircle(point, pointRadius, color, 1.25f);
    }

    private void DrawNavigationPin(CanvasDrawingSession ds, Rect sceneViewportPixels)
    {
        if (!_hasNavigationPin)
        {
            return;
        }

        var point = WorldToScreen(_navigationPinMeters, sceneViewportPixels);
        if (point.X < (float)sceneViewportPixels.X - 20f
            || point.X > (float)(sceneViewportPixels.X + sceneViewportPixels.Width) + 20f
            || point.Y < (float)sceneViewportPixels.Y - 20f
            || point.Y > (float)(sceneViewportPixels.Y + sceneViewportPixels.Height) + 20f)
        {
            return;
        }

        var pinColor = Color.FromArgb(255, 210, 35, 35);
        var stemStartY = point.Y - NavigationPinStemLengthPx;
        ds.DrawLine(point.X, stemStartY, point.X, point.Y - NavigationPinRadiusPx, pinColor, 1.5f);

        ds.FillCircle(point, NavigationPinRadiusPx, _sceneBackgroundColor);
        ds.DrawCircle(point, NavigationPinRadiusPx, pinColor, 1.9f);
        ds.DrawLine(point.X - 2.8f, point.Y, point.X + 2.8f, point.Y, pinColor, 1.25f);
        ds.DrawLine(point.X, point.Y - 2.8f, point.X, point.Y + 2.8f, pinColor, 1.25f);
    }

    private void DrawAreaSelection(CanvasDrawingSession ds)
    {
        var selectionRect = GetAreaSelectionRectPixels();
        if (selectionRect.Width < 1d || selectionRect.Height < 1d)
        {
            return;
        }

        var fillColor = Color.FromArgb(40, _selectionColor.R, _selectionColor.G, _selectionColor.B);
        ds.FillRectangle(selectionRect, fillColor);
        ds.DrawRectangle(selectionRect, _selectionColor, 1f);
    }

    private void DrawGrid(CanvasDrawingSession ds, Rect sceneViewportPixels, Rect visibleWorldBounds)
    {
        var majorStepMeters = CalculateNiceStep(90d / _zoomPixelsPerMeter);
        var minorStepMeters = majorStepMeters / 5d;
        if ((minorStepMeters * _zoomPixelsPerMeter) < 14d)
        {
            minorStepMeters = majorStepMeters;
        }

        var worldLeft = visibleWorldBounds.X;
        var worldTop = visibleWorldBounds.Y;
        var worldRight = visibleWorldBounds.X + visibleWorldBounds.Width;
        var worldBottom = visibleWorldBounds.Y + visibleWorldBounds.Height;

        var firstVertical = Math.Floor(worldLeft / minorStepMeters) * minorStepMeters;
        for (double x = firstVertical; x <= worldRight + 0.0001d; x += minorStepMeters)
        {
            var screenX = WorldToScreenX((float)x, sceneViewportPixels);
            var isMajor = IsMajorLine(x, majorStepMeters);
            ds.DrawLine(
                screenX,
                (float)sceneViewportPixels.Y,
                screenX,
                (float)(sceneViewportPixels.Y + sceneViewportPixels.Height),
                isMajor ? _gridMajorColor : _gridMinorColor,
                isMajor ? 1.1f : 0.6f);
        }

        var firstHorizontal = Math.Floor(worldTop / minorStepMeters) * minorStepMeters;
        for (double y = firstHorizontal; y <= worldBottom + 0.0001d; y += minorStepMeters)
        {
            var screenY = WorldToScreenY((float)y, sceneViewportPixels);
            var isMajor = IsMajorLine(y, majorStepMeters);
            ds.DrawLine(
                (float)sceneViewportPixels.X,
                screenY,
                (float)(sceneViewportPixels.X + sceneViewportPixels.Width),
                screenY,
                isMajor ? _gridMajorColor : _gridMinorColor,
                isMajor ? 1.1f : 0.6f);
        }
    }

    private void DrawRulers(CanvasDrawingSession ds, Rect sceneViewportPixels)
    {
        var topRect = new Rect(0d, 0d, _Canvas.ActualWidth, RulerTopHeightPx);
        var leftRect = new Rect(0d, RulerTopHeightPx, RulerLeftWidthPx, Math.Max(0d, _Canvas.ActualHeight - RulerTopHeightPx));
        var cornerRect = new Rect(0d, 0d, RulerLeftWidthPx, RulerTopHeightPx);

        ds.FillRectangle(topRect, _rulerBackgroundColor);
        ds.FillRectangle(leftRect, _rulerBackgroundColor);
        ds.FillRectangle(cornerRect, _rulerBackgroundColor);

        var visible = GetVisibleWorldBounds(sceneViewportPixels);
        var majorStepMeters = CalculateNiceStep(90d / _zoomPixelsPerMeter);
        var minorStepMeters = majorStepMeters / RulerMinorDivisionCount;
        if (minorStepMeters <= 0d)
        {
            return;
        }

        var worldLeft = visible.X;
        var worldTop = visible.Y;
        var worldRight = visible.X + visible.Width;
        var worldBottom = visible.Y + visible.Height;

        var firstVertical = Math.Floor(worldLeft / minorStepMeters) * minorStepMeters;
        for (double x = firstVertical; x <= worldRight + 0.0001d; x += minorStepMeters)
        {
            var screenX = WorldToScreenX((float)x, sceneViewportPixels);
            if (screenX < RulerLeftWidthPx || screenX > (float)_Canvas.ActualWidth + 0.5f)
            {
                continue;
            }

            var tickLength = GetRulerTickLengthPixels(x, majorStepMeters);
            var tickStroke = GetRulerTickStrokePixels(tickLength);
            var tickColor = GetRulerTickColor(tickLength);
            ds.DrawLine(screenX, RulerTopHeightPx - tickLength, screenX, RulerTopHeightPx, tickColor, tickStroke);

            if (tickLength >= RulerMajorTickLengthPx)
            {
                DrawTopRulerLabel(ds, screenX, x, majorStepMeters);
            }
        }

        var firstHorizontal = Math.Floor(worldTop / minorStepMeters) * minorStepMeters;
        for (double y = firstHorizontal; y <= worldBottom + 0.0001d; y += minorStepMeters)
        {
            var screenY = WorldToScreenY((float)y, sceneViewportPixels);
            if (screenY < RulerTopHeightPx || screenY > (float)_Canvas.ActualHeight + 0.5f)
            {
                continue;
            }

            var tickLength = GetRulerTickLengthPixels(y, majorStepMeters);
            var tickStroke = GetRulerTickStrokePixels(tickLength);
            var tickColor = GetRulerTickColor(tickLength);
            ds.DrawLine(RulerLeftWidthPx - tickLength, screenY, RulerLeftWidthPx, screenY, tickColor, tickStroke);

            if (tickLength >= RulerMajorTickLengthPx)
            {
                DrawLeftRulerLabel(ds, screenY, y, majorStepMeters);
            }
        }

        DrawGuideHeaderHandles(ds, sceneViewportPixels);

        ds.DrawLine(RulerLeftWidthPx, RulerTopHeightPx, RulerLeftWidthPx, (float)_Canvas.ActualHeight, _rulerTickMidColor, 1f);
        ds.DrawLine(RulerLeftWidthPx, RulerTopHeightPx, (float)_Canvas.ActualWidth, RulerTopHeightPx, _rulerTickMidColor, 1f);
    }

    private static float GetRulerTickLengthPixels(double value, double majorStepMeters)
    {
        if (IsMajorLine(value, majorStepMeters))
        {
            return RulerMajorTickLengthPx;
        }

        var midStepMeters = majorStepMeters / 2d;
        if (midStepMeters > 0d && IsMajorLine(value, midStepMeters))
        {
            return RulerMidTickLengthPx;
        }

        return RulerMinorTickLengthPx;
    }

    private static float GetRulerTickStrokePixels(float tickLength)
    {
        if (tickLength >= RulerMajorTickLengthPx)
        {
            return RulerMajorTickStrokePx;
        }

        if (tickLength >= RulerMidTickLengthPx)
        {
            return RulerMidTickStrokePx;
        }

        return RulerMinorTickStrokePx;
    }

    private Color GetRulerTickColor(float tickLength)
    {
        if (tickLength >= RulerMajorTickLengthPx)
        {
            return _rulerTickMajorColor;
        }

        if (tickLength >= RulerMidTickLengthPx)
        {
            return _rulerTickMidColor;
        }

        return _rulerTickMinorColor;
    }

    private void DrawTopRulerLabel(CanvasDrawingSession ds, float screenX, double worldValueMeters, double majorStepMeters)
    {
        var labelWidth = (float)(majorStepMeters * _zoomPixelsPerMeter);
        labelWidth = MathF.Max(56f, MathF.Min(240f, labelWidth));

        var labelHeight = MathF.Max(10f, RulerTopHeightPx - RulerMajorTickLengthPx - (RulerTextPaddingPx * 2f));
        var labelRect = new Rect(
            screenX - (labelWidth * 0.5f),
            RulerTextPaddingPx,
            labelWidth,
            labelHeight);

        ds.DrawText(FormatMeters(worldValueMeters), labelRect, _rulerTextColor, RulerTextFormat);
    }

    private void DrawLeftRulerLabel(CanvasDrawingSession ds, float screenY, double worldValueMeters, double majorStepMeters)
    {
        var labelHeight = (float)(majorStepMeters * _zoomPixelsPerMeter);
        labelHeight = MathF.Max(56f, MathF.Min(240f, labelHeight));
        var labelWidth = MathF.Max(10f, RulerLeftWidthPx - RulerMajorTickLengthPx - (RulerTextPaddingPx * 2f));

        var labelCenterX = (RulerLeftWidthPx * 0.5f) + LeftRulerLabelOffsetXPx;
        var labelRect = new Rect(
            labelCenterX - (labelHeight * 0.5f),
            screenY - (labelWidth * 0.5f),
            labelHeight,
            labelWidth);

        var previousTransform = ds.Transform;
        ds.Transform = Matrix3x2.CreateRotation((float)(-Math.PI / 2d), new Vector2(labelCenterX, screenY)) * previousTransform;
        ds.DrawText(FormatMeters(worldValueMeters), labelRect, _rulerTextColor, RulerTextFormat);
        ds.Transform = previousTransform;
    }

    private void DrawGuideLines(CanvasDrawingSession ds, Rect sceneViewportPixels)
    {
        if (_guideLines.Count == 0)
        {
            return;
        }

        var left = (float)sceneViewportPixels.X;
        var top = (float)sceneViewportPixels.Y;
        var right = (float)(sceneViewportPixels.X + sceneViewportPixels.Width);
        var bottom = (float)(sceneViewportPixels.Y + sceneViewportPixels.Height);

        foreach (var guide in _guideLines)
        {
            if (guide.IsVertical)
            {
                var x = WorldToScreenX((float)guide.PositionMeters, sceneViewportPixels);
                ds.DrawLine(x, top, x, bottom, _guideLineColor, GuideLineStrokePx);
            }
            else
            {
                var y = WorldToScreenY((float)guide.PositionMeters, sceneViewportPixels);
                ds.DrawLine(left, y, right, y, _guideLineColor, GuideLineStrokePx);
            }
        }
    }

    private void DrawGuideHeaderHandles(CanvasDrawingSession ds, Rect sceneViewportPixels)
    {
        if (_guideLines.Count == 0)
        {
            return;
        }

        var topCenterY = (RulerTopHeightPx * 0.5f) + VerticalGuideHeaderHandleOffsetYPx;
        var leftCenterX = (RulerLeftWidthPx * 0.5f) + HorizontalGuideHeaderHandleOffsetXPx;
        var radius = GuideHeaderHandleRadiusPx;
        var minusColor = Color.FromArgb(255, 255, 255, 255);
        var canEdit = CanEditObjects();

        foreach (var guide in _guideLines)
        {
            if (guide.IsVertical)
            {
                var x = WorldToScreenX((float)guide.PositionMeters, sceneViewportPixels);
                if (x < (float)sceneViewportPixels.X - radius || x > (float)(sceneViewportPixels.X + sceneViewportPixels.Width) + radius)
                {
                    continue;
                }

                ds.FillCircle(new Vector2(x, topCenterY), radius, _guideLineColor);
                ds.DrawCircle(new Vector2(x, topCenterY), radius, _borderColor, 1f);
                ds.DrawLine(
                    x - (radius - 3f),
                    topCenterY,
                    x + (radius - 3f),
                    topCenterY,
                    minusColor,
                    canEdit ? 1.4f : 1f);
            }
            else
            {
                var y = WorldToScreenY((float)guide.PositionMeters, sceneViewportPixels);
                if (y < (float)sceneViewportPixels.Y - radius || y > (float)(sceneViewportPixels.Y + sceneViewportPixels.Height) + radius)
                {
                    continue;
                }

                ds.FillCircle(new Vector2(leftCenterX, y), radius, _guideLineColor);
                ds.DrawCircle(new Vector2(leftCenterX, y), radius, _borderColor, 1f);
                ds.DrawLine(
                    leftCenterX - (radius - 3f),
                    y,
                    leftCenterX + (radius - 3f),
                    y,
                    minusColor,
                    canEdit ? 1.4f : 1f);
            }
        }
    }

    private SceneCanvasObject? HitTestSceneObject(Vector2 worldPointMeters, bool activeLayerOnly)
    {
        var tolerance = MathF.Max(0.05f, 6f / _zoomPixelsPerMeter);
        var hitRect = new Rect(
            worldPointMeters.X - tolerance,
            worldPointMeters.Y - tolerance,
            tolerance * 2f,
            tolerance * 2f);

        _hitTestObjects.Clear();
        _queryToken = _queryToken == int.MaxValue ? 1 : _queryToken + 1;
        _spatialIndex.Query(hitRect, _hitTestObjects, _queryToken);
        _hitTestObjects.Sort(CompareObjectsForHitTest);

        foreach (var sceneObject in _hitTestObjects)
        {
            if (activeLayerOnly && sceneObject.Layer != ActiveLayer)
            {
                continue;
            }

            if (!IsSceneObjectVisible(sceneObject))
            {
                continue;
            }

            if (sceneObject.HitTest(worldPointMeters, tolerance))
            {
                return sceneObject;
            }
        }

        return null;
    }

    private bool TryHandleGuidePointerPressed(Point pointerPixels, Rect sceneViewportPixels, bool isCtrlPressed, PointerRoutedEventArgs pointerArgs)
    {
        if (TryHitGuideRemoveHandle(pointerPixels, sceneViewportPixels, out var removeGuideId))
        {
            var history = CaptureHistorySnapshot();
            if (RemoveGuideById(removeGuideId))
            {
                PushUndoSnapshot(history, true);
                ClearMagnetGuide();
                RequestRender();
            }

            return true;
        }

        if (!TryHitGuideLine(pointerPixels, sceneViewportPixels, out var guideId, out var isVertical))
        {
            return false;
        }

        if (isCtrlPressed)
        {
            return false;
        }

        BeginInteractionHistory();
        _interactionMode = InteractionMode.MovingGuideLine;
        _activeObject = null;
        _activeResizeHandle = ResizeHandle.None;
        _activeGuideId = guideId;
        _activeGuideIsVertical = isVertical;
        _Canvas.CapturePointer(pointerArgs.Pointer);
        RequestRender();
        return true;
    }

    private bool TryHitGuideRemoveHandle(Point pointerPixels, Rect sceneViewportPixels, out int guideId)
    {
        guideId = -1;
        if (!CanEditObjects() || !AreRulersVisible || _guideLines.Count == 0)
        {
            return false;
        }

        var pointer = ToVector2(pointerPixels);
        var topInset = AreRulersVisible ? RulerTopHeightPx : 0f;
        var leftInset = AreRulersVisible ? RulerLeftWidthPx : 0f;
        var topCenterY = (topInset * 0.5f) + VerticalGuideHeaderHandleOffsetYPx;
        var leftCenterX = (leftInset * 0.5f) + HorizontalGuideHeaderHandleOffsetXPx;
        var radius = GuideHeaderHandleHitRadiusPx;
        var bestDistance = float.MaxValue;

        foreach (var guide in _guideLines)
        {
            Vector2 center;
            if (guide.IsVertical)
            {
                var x = WorldToScreenX((float)guide.PositionMeters, sceneViewportPixels);
                center = new Vector2(x, topCenterY);
            }
            else
            {
                var y = WorldToScreenY((float)guide.PositionMeters, sceneViewportPixels);
                center = new Vector2(leftCenterX, y);
            }

            var distance = Vector2.Distance(pointer, center);
            if (distance > radius || distance >= bestDistance)
            {
                continue;
            }

            bestDistance = distance;
            guideId = guide.Id;
        }

        return guideId >= 0;
    }

    private bool TryHitGuideLine(Point pointerPixels, Rect sceneViewportPixels, out int guideId, out bool isVertical)
    {
        guideId = -1;
        isVertical = true;
        if (!CanEditObjects() || _guideLines.Count == 0)
        {
            return false;
        }

        var hitDistance = Math.Max(4f, GuideHitDistancePixels);
        var pointerX = (float)pointerPixels.X;
        var pointerY = (float)pointerPixels.Y;
        var topInset = AreRulersVisible ? RulerTopHeightPx : 0f;
        var leftInset = AreRulersVisible ? RulerLeftWidthPx : 0f;
        var inTopHeader = AreRulersVisible && pointerY >= 0f && pointerY <= topInset && pointerX >= leftInset;
        var inLeftHeader = AreRulersVisible && pointerX >= 0f && pointerX <= leftInset && pointerY >= topInset;
        var inScene = sceneViewportPixels.Contains(pointerPixels);

        if (!inTopHeader && !inLeftHeader && !inScene)
        {
            return false;
        }

        var bestDistance = float.MaxValue;
        foreach (var guide in _guideLines)
        {
            float distance;
            if (guide.IsVertical)
            {
                if (!inTopHeader && !inScene)
                {
                    continue;
                }

                var x = WorldToScreenX((float)guide.PositionMeters, sceneViewportPixels);
                distance = Math.Abs(pointerX - x);
            }
            else
            {
                if (!inLeftHeader && !inScene)
                {
                    continue;
                }

                var y = WorldToScreenY((float)guide.PositionMeters, sceneViewportPixels);
                distance = Math.Abs(pointerY - y);
            }

            if (distance > hitDistance || distance >= bestDistance)
            {
                continue;
            }

            bestDistance = distance;
            guideId = guide.Id;
            isVertical = guide.IsVertical;
        }

        return guideId >= 0;
    }

    private bool TryGetGuideById(int guideId, out SceneGuideLine guideLine)
    {
        foreach (var guide in _guideLines)
        {
            if (guide.Id != guideId)
            {
                continue;
            }

            guideLine = guide;
            return true;
        }

        guideLine = null!;
        return false;
    }

    private bool RemoveGuideById(int guideId)
    {
        for (var i = 0; i < _guideLines.Count; i++)
        {
            if (_guideLines[i].Id != guideId)
            {
                continue;
            }

            _guideLines.RemoveAt(i);
            return true;
        }

        return false;
    }

    private void RegisterObjectDrawOrder(SceneCanvasObject sceneObject)
    {
        if (sceneObject == null || _objectDrawOrder.ContainsKey(sceneObject))
        {
            return;
        }

        _objectDrawOrder[sceneObject] = _nextObjectDrawOrderId++;
    }

    private long GetObjectDrawOrder(SceneCanvasObject sceneObject)
    {
        if (sceneObject == null)
        {
            return long.MinValue;
        }

        if (_objectDrawOrder.TryGetValue(sceneObject, out var order))
        {
            return order;
        }

        RegisterObjectDrawOrder(sceneObject);
        return _objectDrawOrder[sceneObject];
    }

    private void SetSingleSelection(SceneCanvasObject? sceneObject)
    {
        _selectedObjects.Clear();
        if (sceneObject != null && sceneObject.Layer == ActiveLayer)
        {
            _selectedObjects.Add(sceneObject);
        }

        SyncSelectedObject(sceneObject);
        SelectionChanged?.Invoke(this, EventArgs.Empty);
    }

    private void ToggleSelection(SceneCanvasObject sceneObject)
    {
        if (sceneObject == null || sceneObject.Layer != ActiveLayer)
        {
            return;
        }

        if (_selectedObjects.Remove(sceneObject))
        {
            SyncSelectedObject();
            SelectionChanged?.Invoke(this, EventArgs.Empty);
            return;
        }

        _selectedObjects.Add(sceneObject);
        SyncSelectedObject(sceneObject);
        SelectionChanged?.Invoke(this, EventArgs.Empty);
    }

    private void ClearSelection()
    {
        if (_selectedObjects.Count == 0 && SelectedObject == null)
        {
            return;
        }

        _selectedObjects.Clear();
        SyncSelectedObject();
        SelectionChanged?.Invoke(this, EventArgs.Empty);
    }

    private void RaiseSelectionGeometryChanged(SceneCanvasObject? sceneObject = null)
    {
        if (_selectedObjects.Count == 0)
        {
            return;
        }

        if (sceneObject != null && !_selectedObjects.Contains(sceneObject))
        {
            return;
        }

        SelectionGeometryChanged?.Invoke(this, EventArgs.Empty);
    }

    private bool IsObjectSelected(SceneCanvasObject sceneObject)
    {
        return sceneObject != null && _selectedObjects.Contains(sceneObject);
    }

    private void SyncSelectedObject(SceneCanvasObject? preferred = null)
    {
        _selectedObjects.RemoveAll(sceneObject => sceneObject == null || !_objects.Contains(sceneObject) || sceneObject.Layer != ActiveLayer);

        var previous = SelectedObject;
        if (preferred != null && _selectedObjects.Contains(preferred))
        {
            SelectedObject = preferred;
        }
        else if (SelectedObject != null && _selectedObjects.Contains(SelectedObject))
        {
            // Keep the existing primary selection.
        }
        else
        {
            SelectedObject = _selectedObjects.Count > 0 ? _selectedObjects[^1] : null;
        }

        if (!ReferenceEquals(previous, SelectedObject))
        {
            SelectedObjectChanged?.Invoke(this, SelectedObject);
        }
    }

    private void PrepareMovingObjects(SceneCanvasObject anchorObject)
    {
        _movingObjects.Clear();
        if (anchorObject == null)
        {
            return;
        }

        if (IsObjectSelected(anchorObject))
        {
            foreach (var selectedObject in _selectedObjects)
            {
                if (selectedObject.Layer != ActiveLayer || !selectedObject.CanMove)
                {
                    continue;
                }

                _movingObjects.Add((selectedObject, selectedObject.BoundsMeters));
            }
        }
        else if (anchorObject.Layer == ActiveLayer && anchorObject.CanMove)
        {
            _movingObjects.Add((anchorObject, anchorObject.BoundsMeters));
        }
    }

    private bool MoveSelectedObjects(float deltaXMeters, float deltaYMeters)
    {
        if (_selectedObjects.Count == 0)
        {
            return false;
        }

        var changed = false;
        foreach (var selectedObject in _selectedObjects)
        {
            if (selectedObject.Layer != ActiveLayer || !selectedObject.CanMove)
            {
                continue;
            }

            var moved = new Rect(
                selectedObject.BoundsMeters.X + deltaXMeters,
                selectedObject.BoundsMeters.Y + deltaYMeters,
                selectedObject.BoundsMeters.Width,
                selectedObject.BoundsMeters.Height);

            moved = ApplyPrecision(moved);
            if (AreRectsClose(selectedObject.BoundsMeters, moved))
            {
                continue;
            }

            selectedObject.BoundsMeters = moved;
            _spatialIndex.Update(selectedObject);
            changed = true;
        }

        if (!changed)
        {
            return false;
        }

        _sceneBoundsDirty = true;
        UpdateScrollBars();
        RequestRender();
        return true;
    }

    private bool MoveSelectedObjectsWithHistory(float deltaXMeters, float deltaYMeters)
    {
        var history = CaptureHistorySnapshot();
        if (!MoveSelectedObjects(deltaXMeters, deltaYMeters))
        {
            return false;
        }

        PushUndoSnapshot(history, true);
        RaiseSelectionGeometryChanged();
        return true;
    }

    private bool PackSelectionLeftToRight(List<(SceneCanvasObject SceneObject, Rect Bounds)> entries)
    {
        entries.Sort((a, b) =>
        {
            var byX = a.Bounds.X.CompareTo(b.Bounds.X);
            if (byX != 0)
            {
                return byX;
            }

            return GetObjectDrawOrder(a.SceneObject).CompareTo(GetObjectDrawOrder(b.SceneObject));
        });

        var cursor = entries[0].Bounds.X;
        var changed = false;

        foreach (var entry in entries)
        {
            if (MoveObjectBy(entry.SceneObject, cursor - entry.Bounds.X, 0d))
            {
                changed = true;
            }

            cursor += entry.Bounds.Width;
        }

        return changed;
    }

    private bool PackSelectionRightToLeft(List<(SceneCanvasObject SceneObject, Rect Bounds)> entries)
    {
        entries.Sort((a, b) =>
        {
            var aRight = a.Bounds.X + a.Bounds.Width;
            var bRight = b.Bounds.X + b.Bounds.Width;
            var byRight = bRight.CompareTo(aRight);
            if (byRight != 0)
            {
                return byRight;
            }

            return GetObjectDrawOrder(a.SceneObject).CompareTo(GetObjectDrawOrder(b.SceneObject));
        });

        var cursorRight = entries[0].Bounds.X + entries[0].Bounds.Width;
        var changed = false;

        foreach (var entry in entries)
        {
            var targetLeft = cursorRight - entry.Bounds.Width;
            if (MoveObjectBy(entry.SceneObject, targetLeft - entry.Bounds.X, 0d))
            {
                changed = true;
            }

            cursorRight = targetLeft;
        }

        return changed;
    }

    private bool DistributeSelectionHorizontally(List<(SceneCanvasObject SceneObject, Rect Bounds)> entries)
    {
        if (entries.Count < 3)
        {
            return false;
        }

        entries.Sort((a, b) =>
        {
            var byX = a.Bounds.X.CompareTo(b.Bounds.X);
            if (byX != 0)
            {
                return byX;
            }

            return GetObjectDrawOrder(a.SceneObject).CompareTo(GetObjectDrawOrder(b.SceneObject));
        });

        var firstLeft = entries[0].Bounds.X;
        var lastRight = entries[^1].Bounds.X + entries[^1].Bounds.Width;
        var totalWidth = 0d;
        foreach (var entry in entries)
        {
            totalWidth += entry.Bounds.Width;
        }

        var gap = (lastRight - firstLeft - totalWidth) / (entries.Count - 1);
        if (!double.IsFinite(gap))
        {
            return false;
        }

        var cursor = firstLeft;
        var changed = false;

        foreach (var entry in entries)
        {
            if (MoveObjectBy(entry.SceneObject, cursor - entry.Bounds.X, 0d))
            {
                changed = true;
            }

            cursor += entry.Bounds.Width + gap;
        }

        return changed;
    }

    private bool PackSelectionTopToBottom(List<(SceneCanvasObject SceneObject, Rect Bounds)> entries)
    {
        entries.Sort((a, b) =>
        {
            var byY = a.Bounds.Y.CompareTo(b.Bounds.Y);
            if (byY != 0)
            {
                return byY;
            }

            return GetObjectDrawOrder(a.SceneObject).CompareTo(GetObjectDrawOrder(b.SceneObject));
        });

        var cursor = entries[0].Bounds.Y;
        var changed = false;

        foreach (var entry in entries)
        {
            if (MoveObjectBy(entry.SceneObject, 0d, cursor - entry.Bounds.Y))
            {
                changed = true;
            }

            cursor += entry.Bounds.Height;
        }

        return changed;
    }

    private bool PackSelectionBottomToTop(List<(SceneCanvasObject SceneObject, Rect Bounds)> entries)
    {
        entries.Sort((a, b) =>
        {
            var aBottom = a.Bounds.Y + a.Bounds.Height;
            var bBottom = b.Bounds.Y + b.Bounds.Height;
            var byBottom = bBottom.CompareTo(aBottom);
            if (byBottom != 0)
            {
                return byBottom;
            }

            return GetObjectDrawOrder(a.SceneObject).CompareTo(GetObjectDrawOrder(b.SceneObject));
        });

        var cursorBottom = entries[0].Bounds.Y + entries[0].Bounds.Height;
        var changed = false;

        foreach (var entry in entries)
        {
            var targetTop = cursorBottom - entry.Bounds.Height;
            if (MoveObjectBy(entry.SceneObject, 0d, targetTop - entry.Bounds.Y))
            {
                changed = true;
            }

            cursorBottom = targetTop;
        }

        return changed;
    }

    private bool DistributeSelectionVertically(List<(SceneCanvasObject SceneObject, Rect Bounds)> entries)
    {
        if (entries.Count < 3)
        {
            return false;
        }

        entries.Sort((a, b) =>
        {
            var byY = a.Bounds.Y.CompareTo(b.Bounds.Y);
            if (byY != 0)
            {
                return byY;
            }

            return GetObjectDrawOrder(a.SceneObject).CompareTo(GetObjectDrawOrder(b.SceneObject));
        });

        var firstTop = entries[0].Bounds.Y;
        var lastBottom = entries[^1].Bounds.Y + entries[^1].Bounds.Height;
        var totalHeight = 0d;
        foreach (var entry in entries)
        {
            totalHeight += entry.Bounds.Height;
        }

        var gap = (lastBottom - firstTop - totalHeight) / (entries.Count - 1);
        if (!double.IsFinite(gap))
        {
            return false;
        }

        var cursor = firstTop;
        var changed = false;

        foreach (var entry in entries)
        {
            if (MoveObjectBy(entry.SceneObject, 0d, cursor - entry.Bounds.Y))
            {
                changed = true;
            }

            cursor += entry.Bounds.Height + gap;
        }

        return changed;
    }

    private bool CenterActiveSelectionHorizontally(List<(SceneCanvasObject SceneObject, Rect Bounds)> entries)
    {
        if (entries.Count < 2)
        {
            return false;
        }

        var anchor = SelectedObject;
        if (anchor == null)
        {
            return false;
        }

        var hasAnchor = false;
        Rect anchorBounds = default;
        var minX = double.PositiveInfinity;
        var maxX = double.NegativeInfinity;

        foreach (var entry in entries)
        {
            if (ReferenceEquals(entry.SceneObject, anchor))
            {
                hasAnchor = true;
                anchorBounds = entry.Bounds;
                continue;
            }

            minX = Math.Min(minX, entry.Bounds.X);
            maxX = Math.Max(maxX, entry.Bounds.X + entry.Bounds.Width);
        }

        if (!hasAnchor || !double.IsFinite(minX) || !double.IsFinite(maxX))
        {
            return false;
        }

        var targetCenterX = minX + ((maxX - minX) * 0.5d);
        var anchorCenterX = anchorBounds.X + (anchorBounds.Width * 0.5d);
        return MoveObjectBy(anchor, targetCenterX - anchorCenterX, 0d);
    }

    private bool CenterActiveSelectionVertically(List<(SceneCanvasObject SceneObject, Rect Bounds)> entries)
    {
        if (entries.Count < 2)
        {
            return false;
        }

        var anchor = SelectedObject;
        if (anchor == null)
        {
            return false;
        }

        var hasAnchor = false;
        Rect anchorBounds = default;
        var minY = double.PositiveInfinity;
        var maxY = double.NegativeInfinity;

        foreach (var entry in entries)
        {
            if (ReferenceEquals(entry.SceneObject, anchor))
            {
                hasAnchor = true;
                anchorBounds = entry.Bounds;
                continue;
            }

            minY = Math.Min(minY, entry.Bounds.Y);
            maxY = Math.Max(maxY, entry.Bounds.Y + entry.Bounds.Height);
        }

        if (!hasAnchor || !double.IsFinite(minY) || !double.IsFinite(maxY))
        {
            return false;
        }

        var targetCenterY = minY + ((maxY - minY) * 0.5d);
        var anchorCenterY = anchorBounds.Y + (anchorBounds.Height * 0.5d);
        return MoveObjectBy(anchor, 0d, targetCenterY - anchorCenterY);
    }

    private bool MoveObjectBy(SceneCanvasObject sceneObject, double deltaXMeters, double deltaYMeters)
    {
        const double epsilon = 0.00001d;
        if (sceneObject == null || !double.IsFinite(deltaXMeters) || !double.IsFinite(deltaYMeters))
        {
            return false;
        }

        if (Math.Abs(deltaXMeters) < epsilon && Math.Abs(deltaYMeters) < epsilon)
        {
            return false;
        }

        var moved = new Rect(
            SnapToStep(sceneObject.BoundsMeters.X + deltaXMeters, PrecisionMeters),
            SnapToStep(sceneObject.BoundsMeters.Y + deltaYMeters, PrecisionMeters),
            sceneObject.BoundsMeters.Width,
            sceneObject.BoundsMeters.Height);

        if (AreRectsClose(sceneObject.BoundsMeters, moved))
        {
            return false;
        }

        sceneObject.BoundsMeters = moved;
        _spatialIndex.Update(sceneObject);
        return true;
    }

    private Rect GetAreaSelectionRectPixels()
    {
        var left = Math.Min(_areaSelectionStartPixels.X, _areaSelectionCurrentPixels.X);
        var top = Math.Min(_areaSelectionStartPixels.Y, _areaSelectionCurrentPixels.Y);
        var right = Math.Max(_areaSelectionStartPixels.X, _areaSelectionCurrentPixels.X);
        var bottom = Math.Max(_areaSelectionStartPixels.Y, _areaSelectionCurrentPixels.Y);
        return new Rect(left, top, right - left, bottom - top);
    }

    private Rect ScreenRectToWorldRect(Rect screenRectPixels, Rect sceneViewportPixels)
    {
        var topLeft = ScreenToWorld(new Vector2((float)screenRectPixels.X, (float)screenRectPixels.Y), sceneViewportPixels);
        var bottomRight = ScreenToWorld(new Vector2((float)(screenRectPixels.X + screenRectPixels.Width), (float)(screenRectPixels.Y + screenRectPixels.Height)), sceneViewportPixels);

        var minX = Math.Min(topLeft.X, bottomRight.X);
        var minY = Math.Min(topLeft.Y, bottomRight.Y);
        var maxX = Math.Max(topLeft.X, bottomRight.X);
        var maxY = Math.Max(topLeft.Y, bottomRight.Y);
        return new Rect(minX, minY, maxX - minX, maxY - minY);
    }

    private void SelectByArea(Rect worldRect, bool additive)
    {
        var initialCount = _selectedObjects.Count;
        var initialSelected = SelectedObject;

        _areaSelectionCandidates.Clear();
        _queryToken = _queryToken == int.MaxValue ? 1 : _queryToken + 1;
        _spatialIndex.Query(worldRect, _areaSelectionCandidates, _queryToken);

        if (!additive)
        {
            _selectedObjects.Clear();
        }

        SceneCanvasObject? preferred = null;
        foreach (var sceneObject in _areaSelectionCandidates)
        {
            if (sceneObject.Layer != ActiveLayer)
            {
                continue;
            }

            if (!IsSceneObjectVisible(sceneObject))
            {
                continue;
            }

            if (!Intersects(worldRect, sceneObject.SpatialBoundsMeters))
            {
                continue;
            }

            if (_selectedObjects.Contains(sceneObject))
            {
                continue;
            }

            _selectedObjects.Add(sceneObject);
            preferred = sceneObject;
        }

        SyncSelectedObject(preferred);

        if (initialCount != _selectedObjects.Count || !ReferenceEquals(initialSelected, SelectedObject))
        {
            SelectionChanged?.Invoke(this, EventArgs.Empty);
        }
    }

    private static bool Intersects(Rect a, Rect b)
    {
        return a.X < (b.X + b.Width) &&
               (a.X + a.Width) > b.X &&
               a.Y < (b.Y + b.Height) &&
               (a.Y + a.Height) > b.Y;
    }

    private Rect ApplyPrecision(Rect rect)
    {
        var x = SnapToStep(rect.X, PrecisionMeters);
        var y = SnapToStep(rect.Y, PrecisionMeters);
        var width = Math.Max(MinObjectSizeMeters, SnapToStep(Math.Max(0d, rect.Width), PrecisionMeters));
        var height = Math.Max(MinObjectSizeMeters, SnapToStep(Math.Max(0d, rect.Height), PrecisionMeters));
        return new Rect(x, y, width, height);
    }

    private static bool AreRectsClose(Rect a, Rect b)
    {
        const double epsilon = 0.00001d;
        return Math.Abs(a.X - b.X) < epsilon &&
               Math.Abs(a.Y - b.Y) < epsilon &&
               Math.Abs(a.Width - b.Width) < epsilon &&
               Math.Abs(a.Height - b.Height) < epsilon;
    }

    private static bool ArePointsClose(Vector2 a, Vector2 b)
    {
        const float epsilon = 0.00001f;
        return Math.Abs(a.X - b.X) < epsilon &&
               Math.Abs(a.Y - b.Y) < epsilon;
    }

    private Rect GetSceneViewportPixels()
    {
        var leftInset = AreRulersVisible ? RulerLeftWidthPx : 0f;
        var topInset = AreRulersVisible ? RulerTopHeightPx : 0f;
        var width = Math.Max(0d, _Canvas.ActualWidth - leftInset);
        var height = Math.Max(0d, _Canvas.ActualHeight - topInset);
        return new Rect(leftInset, topInset, width, height);
    }

    private Rect GetVisibleWorldBounds(Rect sceneViewportPixels)
    {
        var widthMeters = sceneViewportPixels.Width / _zoomPixelsPerMeter;
        var heightMeters = sceneViewportPixels.Height / _zoomPixelsPerMeter;
        return new Rect(_viewOriginMeters.X, _viewOriginMeters.Y, widthMeters, heightMeters);
    }

    private void UpdateScrollBars()
    {
        var viewport = GetSceneViewportPixels();
        if (viewport.Width < 1d || viewport.Height < 1d)
        {
            return;
        }

        ClampViewOriginToScene(viewport);
        EnsureSceneBounds();

        var worldViewWidth = viewport.Width / _zoomPixelsPerMeter;
        var worldViewHeight = viewport.Height / _zoomPixelsPerMeter;

        var minX = _sceneBoundsMeters.X - ScrollMarginMeters;
        var minY = _sceneBoundsMeters.Y - ScrollMarginMeters;
        var maxX = (_sceneBoundsMeters.X + _sceneBoundsMeters.Width + ScrollMarginMeters) - worldViewWidth;
        var maxY = (_sceneBoundsMeters.Y + _sceneBoundsMeters.Height + ScrollMarginMeters) - worldViewHeight;

        if (maxX < minX)
        {
            maxX = minX;
        }

        if (maxY < minY)
        {
            maxY = minY;
        }

        _isScrollSyncInProgress = true;

        _HorizontalScrollBar.Minimum = minX;
        _HorizontalScrollBar.Maximum = maxX;
        _HorizontalScrollBar.ViewportSize = Math.Max(1d, worldViewWidth);
        _HorizontalScrollBar.SmallChange = Math.Max(0.25d, worldViewWidth * 0.05d);
        _HorizontalScrollBar.LargeChange = Math.Max(1d, worldViewWidth * 0.9d);
        _HorizontalScrollBar.Value = Math.Clamp(_viewOriginMeters.X, (float)minX, (float)maxX);

        _VerticalScrollBar.Minimum = minY;
        _VerticalScrollBar.Maximum = maxY;
        _VerticalScrollBar.ViewportSize = Math.Max(1d, worldViewHeight);
        _VerticalScrollBar.SmallChange = Math.Max(0.25d, worldViewHeight * 0.05d);
        _VerticalScrollBar.LargeChange = Math.Max(1d, worldViewHeight * 0.9d);
        _VerticalScrollBar.Value = Math.Clamp(_viewOriginMeters.Y, (float)minY, (float)maxY);

        _isScrollSyncInProgress = false;
    }

    private void ClampViewOriginToScene(Rect sceneViewportPixels)
    {
        EnsureSceneBounds();

        var worldViewWidth = sceneViewportPixels.Width / _zoomPixelsPerMeter;
        var worldViewHeight = sceneViewportPixels.Height / _zoomPixelsPerMeter;

        var minX = (float)(_sceneBoundsMeters.X - ScrollMarginMeters);
        var minY = (float)(_sceneBoundsMeters.Y - ScrollMarginMeters);
        var maxX = (float)(_sceneBoundsMeters.X + _sceneBoundsMeters.Width + ScrollMarginMeters - worldViewWidth);
        var maxY = (float)(_sceneBoundsMeters.Y + _sceneBoundsMeters.Height + ScrollMarginMeters - worldViewHeight);

        if (maxX < minX)
        {
            maxX = minX;
        }

        if (maxY < minY)
        {
            maxY = minY;
        }

        _viewOriginMeters.X = Math.Clamp(_viewOriginMeters.X, minX, maxX);
        _viewOriginMeters.Y = Math.Clamp(_viewOriginMeters.Y, minY, maxY);
    }

    private void EnsureSceneBounds()
    {
        if (!_sceneBoundsDirty)
        {
            return;
        }

        if (_objects.Count == 0)
        {
            _sceneBoundsMeters = new Rect(
                -MinimumExtentMeters * 0.5d,
                -MinimumExtentMeters * 0.5d,
                MinimumExtentMeters,
                MinimumExtentMeters);
            _sceneBoundsDirty = false;
            return;
        }

        var minX = double.PositiveInfinity;
        var minY = double.PositiveInfinity;
        var maxX = double.NegativeInfinity;
        var maxY = double.NegativeInfinity;

        foreach (var sceneObject in _objects)
        {
            if (!IsSceneObjectVisible(sceneObject))
            {
                continue;
            }

            var bounds = sceneObject.SpatialBoundsMeters;
            minX = Math.Min(minX, bounds.X);
            minY = Math.Min(minY, bounds.Y);
            maxX = Math.Max(maxX, bounds.X + bounds.Width);
            maxY = Math.Max(maxY, bounds.Y + bounds.Height);
        }

        if (!double.IsFinite(minX) || !double.IsFinite(minY) || !double.IsFinite(maxX) || !double.IsFinite(maxY))
        {
            _sceneBoundsMeters = new Rect(
                -MinimumExtentMeters * 0.5d,
                -MinimumExtentMeters * 0.5d,
                MinimumExtentMeters,
                MinimumExtentMeters);
            _sceneBoundsDirty = false;
            return;
        }

        if ((maxX - minX) < MinimumExtentMeters)
        {
            var center = (minX + maxX) * 0.5d;
            minX = center - (MinimumExtentMeters * 0.5d);
            maxX = center + (MinimumExtentMeters * 0.5d);
        }

        if ((maxY - minY) < MinimumExtentMeters)
        {
            var center = (minY + maxY) * 0.5d;
            minY = center - (MinimumExtentMeters * 0.5d);
            maxY = center + (MinimumExtentMeters * 0.5d);
        }

        _sceneBoundsMeters = new Rect(minX, minY, maxX - minX, maxY - minY);
        _sceneBoundsDirty = false;
    }

    private Matrix3x2 CreateWorldToScreenMatrix(Rect sceneViewportPixels)
    {
        return new Matrix3x2(
            _zoomPixelsPerMeter, 0f,
            0f, _zoomPixelsPerMeter,
            (float)sceneViewportPixels.X - (_viewOriginMeters.X * _zoomPixelsPerMeter),
            (float)sceneViewportPixels.Y - (_viewOriginMeters.Y * _zoomPixelsPerMeter));
    }

    private Vector2 WorldToScreen(Vector2 worldMeters, Rect sceneViewportPixels)
    {
        var x = ((worldMeters.X - _viewOriginMeters.X) * _zoomPixelsPerMeter) + (float)sceneViewportPixels.X;
        var y = ((worldMeters.Y - _viewOriginMeters.Y) * _zoomPixelsPerMeter) + (float)sceneViewportPixels.Y;
        return new Vector2(x, y);
    }

    private float WorldToScreenX(float worldX, Rect sceneViewportPixels)
    {
        return ((worldX - _viewOriginMeters.X) * _zoomPixelsPerMeter) + (float)sceneViewportPixels.X;
    }

    private float WorldToScreenY(float worldY, Rect sceneViewportPixels)
    {
        return ((worldY - _viewOriginMeters.Y) * _zoomPixelsPerMeter) + (float)sceneViewportPixels.Y;
    }

    private Vector2 ScreenToWorld(Vector2 screenPixels, Rect sceneViewportPixels)
    {
        var x = ((screenPixels.X - (float)sceneViewportPixels.X) / _zoomPixelsPerMeter) + _viewOriginMeters.X;
        var y = ((screenPixels.Y - (float)sceneViewportPixels.Y) / _zoomPixelsPerMeter) + _viewOriginMeters.Y;
        return new Vector2(x, y);
    }

    private static Vector2 ToVector2(Point point)
    {
        return new Vector2((float)point.X, (float)point.Y);
    }

    private int CompareObjectsForDraw(SceneCanvasObject a, SceneCanvasObject b)
    {
        var aActive = a.Layer == ActiveLayer ? 1 : 0;
        var bActive = b.Layer == ActiveLayer ? 1 : 0;
        if (aActive != bActive)
        {
            return aActive.CompareTo(bActive);
        }

        if (a.Layer != b.Layer)
        {
            var aLayoutIndex = FindLayoutIndex(a.Layer);
            var bLayoutIndex = FindLayoutIndex(b.Layer);
            if (aLayoutIndex < 0)
            {
                aLayoutIndex = int.MaxValue;
            }

            if (bLayoutIndex < 0)
            {
                bLayoutIndex = int.MaxValue;
            }

            if (aLayoutIndex != bLayoutIndex)
            {
                return aLayoutIndex.CompareTo(bLayoutIndex);
            }

            return a.Layer.CompareTo(b.Layer);
        }

        if (a.ZIndex != b.ZIndex)
        {
            return a.ZIndex.CompareTo(b.ZIndex);
        }

        return GetObjectDrawOrder(a).CompareTo(GetObjectDrawOrder(b));
    }

    private int CompareObjectsForHitTest(SceneCanvasObject a, SceneCanvasObject b)
    {
        var aPriority = IsHitTestPriorityObject(a) ? 1 : 0;
        var bPriority = IsHitTestPriorityObject(b) ? 1 : 0;
        if (aPriority != bPriority)
        {
            return bPriority.CompareTo(aPriority);
        }

        var aActive = a.Layer == ActiveLayer ? 1 : 0;
        var bActive = b.Layer == ActiveLayer ? 1 : 0;
        if (aActive != bActive)
        {
            return bActive.CompareTo(aActive);
        }

        if (a.Layer != b.Layer)
        {
            var aLayoutIndex = FindLayoutIndex(a.Layer);
            var bLayoutIndex = FindLayoutIndex(b.Layer);
            if (aLayoutIndex < 0)
            {
                aLayoutIndex = int.MaxValue;
            }

            if (bLayoutIndex < 0)
            {
                bLayoutIndex = int.MaxValue;
            }

            if (aLayoutIndex != bLayoutIndex)
            {
                return bLayoutIndex.CompareTo(aLayoutIndex);
            }

            return b.Layer.CompareTo(a.Layer);
        }

        if (a.ZIndex != b.ZIndex)
        {
            return b.ZIndex.CompareTo(a.ZIndex);
        }

        return GetObjectDrawOrder(b).CompareTo(GetObjectDrawOrder(a));
    }

    private bool IsHitTestPriorityObject(SceneCanvasObject sceneObject)
    {
        if (_activeObject != null && ReferenceEquals(sceneObject, _activeObject))
        {
            return true;
        }

        return SelectedObject != null && ReferenceEquals(sceneObject, SelectedObject);
    }

    private void GetSelectionFramePixels(
        SceneCanvasObject sceneObject,
        Rect sceneViewportPixels,
        out Vector2 topLeft,
        out Vector2 topRight,
        out Vector2 bottomRight,
        out Vector2 bottomLeft,
        out Vector2 center)
    {
        var bounds = sceneObject.BoundsMeters;
        var worldTopLeft = new Vector2((float)bounds.X, (float)bounds.Y);
        var worldTopRight = new Vector2((float)(bounds.X + bounds.Width), (float)bounds.Y);
        var worldBottomRight = new Vector2((float)(bounds.X + bounds.Width), (float)(bounds.Y + bounds.Height));
        var worldBottomLeft = new Vector2((float)bounds.X, (float)(bounds.Y + bounds.Height));
        var worldCenter = GetObjectCenterWorld(sceneObject);

        if (Math.Abs(sceneObject.RotationRadians) > 0.0001d)
        {
            worldTopLeft = RotatePointAround(worldTopLeft, worldCenter, sceneObject.RotationRadians);
            worldTopRight = RotatePointAround(worldTopRight, worldCenter, sceneObject.RotationRadians);
            worldBottomRight = RotatePointAround(worldBottomRight, worldCenter, sceneObject.RotationRadians);
            worldBottomLeft = RotatePointAround(worldBottomLeft, worldCenter, sceneObject.RotationRadians);
        }

        topLeft = WorldToScreen(worldTopLeft, sceneViewportPixels);
        topRight = WorldToScreen(worldTopRight, sceneViewportPixels);
        bottomRight = WorldToScreen(worldBottomRight, sceneViewportPixels);
        bottomLeft = WorldToScreen(worldBottomLeft, sceneViewportPixels);
        center = WorldToScreen(worldCenter, sceneViewportPixels);
    }

    private static void ExpandSelectionFrame(
        Vector2 topLeft,
        Vector2 topRight,
        Vector2 bottomRight,
        Vector2 bottomLeft,
        Vector2 center,
        float offsetPixels,
        out Vector2 topLeftOuter,
        out Vector2 topRightOuter,
        out Vector2 bottomRightOuter,
        out Vector2 bottomLeftOuter)
    {
        topLeftOuter = topLeft + (NormalizeOrFallback(topLeft - center, new Vector2(-1f, -1f)) * offsetPixels);
        topRightOuter = topRight + (NormalizeOrFallback(topRight - center, new Vector2(1f, -1f)) * offsetPixels);
        bottomRightOuter = bottomRight + (NormalizeOrFallback(bottomRight - center, new Vector2(1f, 1f)) * offsetPixels);
        bottomLeftOuter = bottomLeft + (NormalizeOrFallback(bottomLeft - center, new Vector2(-1f, 1f)) * offsetPixels);
    }

    private static Vector2 NormalizeOrFallback(Vector2 vector, Vector2 fallback)
    {
        if (vector.LengthSquared() > 0.000001f)
        {
            return Vector2.Normalize(vector);
        }

        if (fallback.LengthSquared() > 0.000001f)
        {
            return Vector2.Normalize(fallback);
        }

        return Vector2.Zero;
    }

    private void DrawResizeHandles(
        CanvasDrawingSession ds,
        Vector2 topLeft,
        Vector2 topRight,
        Vector2 bottomRight,
        Vector2 bottomLeft,
        Vector2 center)
    {
        var handles = new[]
        {
            ResizeHandle.TopLeft,
            ResizeHandle.Top,
            ResizeHandle.TopRight,
            ResizeHandle.Right,
            ResizeHandle.BottomRight,
            ResizeHandle.Bottom,
            ResizeHandle.BottomLeft,
            ResizeHandle.Left
        };

        var half = ResizeHandleSizePx * 0.5f;
        foreach (var handle in handles)
        {
            var pos = GetHandlePosition(topLeft, topRight, bottomRight, bottomLeft, center, handle);
            var rect = new Rect(pos.X - half, pos.Y - half, ResizeHandleSizePx, ResizeHandleSizePx);
            ds.FillRectangle(rect, _sceneBackgroundColor);
            ds.DrawRectangle(rect, _selectionColor, 1f);
        }
    }

    private void DrawRotateHandle(
        CanvasDrawingSession ds,
        Vector2 topLeft,
        Vector2 topRight,
        Vector2 bottomRight,
        Vector2 bottomLeft,
        Vector2 center)
    {
        var rotatePos = GetHandlePosition(topLeft, topRight, bottomRight, bottomLeft, center, ResizeHandle.Rotate);
        var topCenter = GetHandlePosition(topLeft, topRight, bottomRight, bottomLeft, center, ResizeHandle.Top);

        ds.DrawLine(topCenter, rotatePos, _selectionColor, 1f);
        ds.FillCircle(rotatePos, RotateHandleRadiusPx, _sceneBackgroundColor);
        ds.DrawCircle(rotatePos, RotateHandleRadiusPx, _selectionColor, 1f);
    }

    private ResizeHandle HitTestManipulationHandle(SceneCanvasObject sceneObject, Point pointerPixels, Rect sceneViewportPixels)
    {
        if (sceneObject is SceneCanvasLineObject lineObject)
        {
            return HitTestLinePointHandle(lineObject, pointerPixels, sceneViewportPixels);
        }

        GetSelectionFramePixels(
            sceneObject,
            sceneViewportPixels,
            out var topLeft,
            out var topRight,
            out var bottomRight,
            out var bottomLeft,
            out var center);

        var pointer = new Vector2((float)pointerPixels.X, (float)pointerPixels.Y);

        if (sceneObject.CanRotate)
        {
            var rotatePos = GetHandlePosition(topLeft, topRight, bottomRight, bottomLeft, center, ResizeHandle.Rotate);
            if (Vector2.Distance(pointer, rotatePos) <= (RotateHandleRadiusPx + 3f))
            {
                return ResizeHandle.Rotate;
            }
        }

        if (!sceneObject.CanResize)
        {
            return ResizeHandle.None;
        }

        var handles = new[]
        {
            ResizeHandle.TopLeft,
            ResizeHandle.Top,
            ResizeHandle.TopRight,
            ResizeHandle.Right,
            ResizeHandle.BottomRight,
            ResizeHandle.Bottom,
            ResizeHandle.BottomLeft,
            ResizeHandle.Left
        };

        var half = ResizeHandleSizePx * 0.6f;
        foreach (var handle in handles)
        {
            var pos = GetHandlePosition(topLeft, topRight, bottomRight, bottomLeft, center, handle);
            if (Math.Abs(pointer.X - pos.X) <= half && Math.Abs(pointer.Y - pos.Y) <= half)
            {
                return handle;
            }
        }

        return ResizeHandle.None;
    }

    private ResizeHandle HitTestLinePointHandle(SceneCanvasLineObject lineObject, Point pointerPixels, Rect sceneViewportPixels)
    {
        var pointer = new Vector2((float)pointerPixels.X, (float)pointerPixels.Y);
        var start = WorldToScreen(lineObject.StartPointMeters, sceneViewportPixels);
        var end = WorldToScreen(lineObject.EndPointMeters, sceneViewportPixels);

        var radius = Math.Max(6f, ResizeHandleSizePx * 0.9f);
        if (Vector2.Distance(pointer, start) <= radius)
        {
            return ResizeHandle.LineStart;
        }

        if (Vector2.Distance(pointer, end) <= radius)
        {
            return ResizeHandle.LineEnd;
        }

        return ResizeHandle.None;
    }

    private Vector2 GetHandlePosition(
        Vector2 topLeft,
        Vector2 topRight,
        Vector2 bottomRight,
        Vector2 bottomLeft,
        Vector2 center,
        ResizeHandle handle)
    {
        var top = (topLeft + topRight) * 0.5f;
        var right = (topRight + bottomRight) * 0.5f;
        var bottom = (bottomLeft + bottomRight) * 0.5f;
        var left = (topLeft + bottomLeft) * 0.5f;

        return handle switch
        {
            ResizeHandle.TopLeft => topLeft,
            ResizeHandle.Top => top,
            ResizeHandle.TopRight => topRight,
            ResizeHandle.Right => right,
            ResizeHandle.BottomRight => bottomRight,
            ResizeHandle.Bottom => bottom,
            ResizeHandle.BottomLeft => bottomLeft,
            ResizeHandle.Left => left,
            ResizeHandle.Rotate => top + (GetOutwardDirection(center, top) * RotateHandleDistancePx),
            _ => center
        };
    }

    private static Vector2 GetObjectCenterWorld(SceneCanvasObject sceneObject)
    {
        var bounds = sceneObject.BoundsMeters;
        return new Vector2(
            (float)(bounds.X + (bounds.Width * 0.5d)),
            (float)(bounds.Y + (bounds.Height * 0.5d)));
    }

    private Rect ResizeBounds(
        Rect start,
        double rotationRadians,
        Vector2 startCenterWorld,
        ResizeHandle handle,
        Vector2 pointerWorld,
        bool snapToStep,
        double stepMeters)
    {
        var movesLeft = handle is ResizeHandle.Left or ResizeHandle.TopLeft or ResizeHandle.BottomLeft;
        var movesRight = handle is ResizeHandle.Right or ResizeHandle.TopRight or ResizeHandle.BottomRight;
        var movesTop = handle is ResizeHandle.Top or ResizeHandle.TopLeft or ResizeHandle.TopRight;
        var movesBottom = handle is ResizeHandle.Bottom or ResizeHandle.BottomLeft or ResizeHandle.BottomRight;

        if (!movesLeft && !movesRight && !movesTop && !movesBottom)
        {
            return start;
        }

        var pointerLocal = RotatePointAround(pointerWorld, startCenterWorld, -rotationRadians) - startCenterWorld;

        var halfWidth = start.Width * 0.5d;
        var halfHeight = start.Height * 0.5d;

        var left = -halfWidth;
        var right = halfWidth;
        var top = -halfHeight;
        var bottom = halfHeight;

        if (movesLeft)
        {
            left = pointerLocal.X;
        }

        if (movesRight)
        {
            right = pointerLocal.X;
        }

        if (movesTop)
        {
            top = pointerLocal.Y;
        }

        if (movesBottom)
        {
            bottom = pointerLocal.Y;
        }

        if (snapToStep && stepMeters > 0d)
        {
            if (movesLeft)
            {
                left = SnapToStep(left, stepMeters);
            }

            if (movesRight)
            {
                right = SnapToStep(right, stepMeters);
            }

            if (movesTop)
            {
                top = SnapToStep(top, stepMeters);
            }

            if (movesBottom)
            {
                bottom = SnapToStep(bottom, stepMeters);
            }
        }

        if ((right - left) < MinObjectSizeMeters)
        {
            if (movesLeft && !movesRight)
            {
                left = right - MinObjectSizeMeters;
            }
            else if (movesRight && !movesLeft)
            {
                right = left + MinObjectSizeMeters;
            }
        }

        if ((bottom - top) < MinObjectSizeMeters)
        {
            if (movesTop && !movesBottom)
            {
                top = bottom - MinObjectSizeMeters;
            }
            else if (movesBottom && !movesTop)
            {
                bottom = top + MinObjectSizeMeters;
            }
        }

        var width = Math.Max(MinObjectSizeMeters, right - left);
        var height = Math.Max(MinObjectSizeMeters, bottom - top);

        var localCenter = new Vector2((float)((left + right) * 0.5d), (float)((top + bottom) * 0.5d));
        var worldCenter = startCenterWorld + RotateVector(localCenter, rotationRadians);

        return new Rect(
            worldCenter.X - (width * 0.5d),
            worldCenter.Y - (height * 0.5d),
            width,
            height);
    }

    private static float SnapToStep(float value, float step)
    {
        if (step <= 0.0001f)
        {
            return value;
        }

        return MathF.Round(value / step) * step;
    }

    private static double SnapToStep(double value, double step)
    {
        if (step <= 0.0001d)
        {
            return value;
        }

        return Math.Round(value / step) * step;
    }

    private static Vector2 RotatePointAround(Vector2 point, Vector2 center, double angleRadians)
    {
        var translated = point - center;
        var rotated = RotateVector(translated, angleRadians);
        return rotated + center;
    }

    private static Vector2 RotateVector(Vector2 vector, double angleRadians)
    {
        var sin = Math.Sin(angleRadians);
        var cos = Math.Cos(angleRadians);

        return new Vector2(
            (float)((vector.X * cos) - (vector.Y * sin)),
            (float)((vector.X * sin) + (vector.Y * cos)));
    }

    private static Vector2 SnapLinePointAngle(Vector2 anchor, Vector2 point, double stepRadians)
    {
        if (stepRadians <= 0d)
        {
            return point;
        }

        var delta = point - anchor;
        var length = delta.Length();
        if (length < 0.0001f)
        {
            return point;
        }

        var angle = Math.Atan2(delta.Y, delta.X);
        var snappedAngle = SnapToStep(angle, stepRadians);

        return new Vector2(
            anchor.X + (float)(Math.Cos(snappedAngle) * length),
            anchor.Y + (float)(Math.Sin(snappedAngle) * length));
    }

    private Vector2 ResolveMeasurePoint(Vector2 pointerWorldMeters, bool allowSnap, out bool snapped)
    {
        snapped = false;
        if (!allowSnap || !MagnetEnabled)
        {
            ClearMagnetGuide();
            return pointerWorldMeters;
        }

        var snapThresholdMeters = Math.Max(0.001d, MeasureSnapDistancePixels / _zoomPixelsPerMeter);
        var queryRect = new Rect(
            pointerWorldMeters.X - snapThresholdMeters,
            pointerWorldMeters.Y - snapThresholdMeters,
            snapThresholdMeters * 2d,
            snapThresholdMeters * 2d);

        _snapCandidates.Clear();
        _queryToken = _queryToken == int.MaxValue ? 1 : _queryToken + 1;
        _spatialIndex.Query(queryRect, _snapCandidates, _queryToken);

        var snapThresholdSquared = snapThresholdMeters * snapThresholdMeters;
        var bestDistanceSquared = double.MaxValue;
        var hasBest = false;
        var bestPoint = pointerWorldMeters;

        void TryApplyCandidate(Vector2 candidate)
        {
            var dx = candidate.X - pointerWorldMeters.X;
            var dy = candidate.Y - pointerWorldMeters.Y;
            var distanceSquared = (dx * dx) + (dy * dy);
            if (distanceSquared <= snapThresholdSquared && (!hasBest || distanceSquared < bestDistanceSquared))
            {
                hasBest = true;
                bestDistanceSquared = distanceSquared;
                bestPoint = candidate;
            }
        }

        foreach (var sceneObject in _snapCandidates)
        {
            if (!IsSceneObjectVisible(sceneObject) || !IsLayoutEnabledForMagnet(sceneObject.Layer))
            {
                continue;
            }

            _magnetPointBuffer.Clear();
            CollectMagnetPoints(sceneObject, _magnetPointBuffer);
            foreach (var point in _magnetPointBuffer)
            {
                TryApplyCandidate(point);
            }

            if (sceneObject is SceneCanvasLineObject lineObject)
            {
                var projected = ClosestPointOnSegment(pointerWorldMeters, lineObject.StartPointMeters, lineObject.EndPointMeters);
                TryApplyCandidate(projected);
            }
            else if (_magnetPointBuffer.Count >= 4)
            {
                // Snap ruler to object edges by projecting pointer to each edge segment.
                var topLeft = _magnetPointBuffer[0];
                var topRight = _magnetPointBuffer[1];
                var bottomRight = _magnetPointBuffer[2];
                var bottomLeft = _magnetPointBuffer[3];

                TryApplyCandidate(ClosestPointOnSegment(pointerWorldMeters, topLeft, topRight));
                TryApplyCandidate(ClosestPointOnSegment(pointerWorldMeters, topRight, bottomRight));
                TryApplyCandidate(ClosestPointOnSegment(pointerWorldMeters, bottomRight, bottomLeft));
                TryApplyCandidate(ClosestPointOnSegment(pointerWorldMeters, bottomLeft, topLeft));
            }
        }

        foreach (var guide in _guideLines)
        {
            Vector2 projected;
            if (guide.IsVertical)
            {
                projected = new Vector2((float)guide.PositionMeters, pointerWorldMeters.Y);
            }
            else
            {
                projected = new Vector2(pointerWorldMeters.X, (float)guide.PositionMeters);
            }

            TryApplyCandidate(projected);
        }

        if (!hasBest)
        {
            ClearMagnetGuide();
            return pointerWorldMeters;
        }

        snapped = true;
        SetMagnetGuide(pointerWorldMeters, bestPoint);
        return bestPoint;
    }

    private static Vector2 ClosestPointOnSegment(Vector2 point, Vector2 segmentStart, Vector2 segmentEnd)
    {
        var segment = segmentEnd - segmentStart;
        var segmentLengthSquared = segment.LengthSquared();
        if (segmentLengthSquared < 0.0000001f)
        {
            return segmentStart;
        }

        var t = Vector2.Dot(point - segmentStart, segment) / segmentLengthSquared;
        t = Math.Clamp(t, 0f, 1f);
        return segmentStart + (segment * t);
    }

    private static Vector2 GetOutwardDirection(Vector2 center, Vector2 edgeMidpoint)
    {
        var direction = edgeMidpoint - center;
        var lengthSquared = direction.LengthSquared();
        if (lengthSquared < 0.0001f)
        {
            return new Vector2(0f, -1f);
        }

        return direction / MathF.Sqrt(lengthSquared);
    }

    private Rect ApplyMagnetToMove(SceneCanvasObject movingObject, Rect candidateBounds)
    {
        if (!MagnetEnabled || MagnetDistancePixels <= 0f)
        {
            ClearMagnetGuide();
            return candidateBounds;
        }

        var snapThresholdMeters = Math.Max(0.001d, MagnetDistancePixels / _zoomPixelsPerMeter);
        var snapThresholdSquared = snapThresholdMeters * snapThresholdMeters;

        _movingMagnetPointBuffer.Clear();
        CollectMovedObjectMagnetPoints(movingObject, candidateBounds, _movingMagnetPointBuffer);
        if (_movingMagnetPointBuffer.Count == 0)
        {
            ClearMagnetGuide();
            return candidateBounds;
        }

        var queryRect = ExpandRect(GetBoundsFromPoints(_movingMagnetPointBuffer), snapThresholdMeters * 2d);

        _snapCandidates.Clear();
        _queryToken = _queryToken == int.MaxValue ? 1 : _queryToken + 1;
        _spatialIndex.Query(queryRect, _snapCandidates, _queryToken);

        var hasBest = false;
        var bestDistanceSquared = double.MaxValue;
        var bestSnapDelta = Vector2.Zero;
        var bestSourcePoint = Vector2.Zero;
        var bestTargetPoint = Vector2.Zero;

        foreach (var sceneObject in _snapCandidates)
        {
            if (ReferenceEquals(sceneObject, movingObject))
            {
                continue;
            }

            if (IsObjectInCurrentMoveSet(sceneObject))
            {
                continue;
            }

            if (!IsSceneObjectVisible(sceneObject) || !IsLayoutEnabledForMagnet(sceneObject.Layer))
            {
                continue;
            }

            _magnetPointBuffer.Clear();
            CollectMagnetPoints(sceneObject, _magnetPointBuffer);

            foreach (var sourcePoint in _movingMagnetPointBuffer)
            {
                foreach (var targetPoint in _magnetPointBuffer)
                {
                    var dx = targetPoint.X - sourcePoint.X;
                    var dy = targetPoint.Y - sourcePoint.Y;
                    var distanceSquared = (dx * dx) + (dy * dy);
                    if (distanceSquared > snapThresholdSquared)
                    {
                        continue;
                    }

                    if (!hasBest || distanceSquared < bestDistanceSquared)
                    {
                        hasBest = true;
                        bestDistanceSquared = distanceSquared;
                        bestSnapDelta = new Vector2(dx, dy);
                        bestSourcePoint = sourcePoint;
                        bestTargetPoint = targetPoint;
                    }
                }
            }
        }

        foreach (var guide in _guideLines)
        {
            foreach (var sourcePoint in _movingMagnetPointBuffer)
            {
                var targetPoint = guide.IsVertical
                    ? new Vector2((float)guide.PositionMeters, sourcePoint.Y)
                    : new Vector2(sourcePoint.X, (float)guide.PositionMeters);

                var dx = targetPoint.X - sourcePoint.X;
                var dy = targetPoint.Y - sourcePoint.Y;
                var distanceSquared = (dx * dx) + (dy * dy);
                if (distanceSquared > snapThresholdSquared)
                {
                    continue;
                }

                if (!hasBest || distanceSquared < bestDistanceSquared)
                {
                    hasBest = true;
                    bestDistanceSquared = distanceSquared;
                    bestSnapDelta = new Vector2(dx, dy);
                    bestSourcePoint = sourcePoint;
                    bestTargetPoint = targetPoint;
                }
            }
        }

        if (!hasBest)
        {
            ClearMagnetGuide();
            return candidateBounds;
        }

        SetMagnetGuide(bestSourcePoint, bestTargetPoint);

        var snappedX = candidateBounds.X + bestSnapDelta.X;
        var snappedY = candidateBounds.Y + bestSnapDelta.Y;
        return new Rect(snappedX, snappedY, candidateBounds.Width, candidateBounds.Height);
    }

    private Vector2 ApplyMagnetToPoint(SceneCanvasObject movingObject, Vector2 candidatePointMeters)
    {
        if (!MagnetEnabled || MagnetDistancePixels <= 0f)
        {
            ClearMagnetGuide();
            return candidatePointMeters;
        }

        var snapThresholdMeters = Math.Max(0.001d, MagnetDistancePixels / _zoomPixelsPerMeter);
        var queryRect = new Rect(
            candidatePointMeters.X - snapThresholdMeters,
            candidatePointMeters.Y - snapThresholdMeters,
            snapThresholdMeters * 2d,
            snapThresholdMeters * 2d);

        _snapCandidates.Clear();
        _queryToken = _queryToken == int.MaxValue ? 1 : _queryToken + 1;
        _spatialIndex.Query(queryRect, _snapCandidates, _queryToken);

        var snapThresholdSquared = snapThresholdMeters * snapThresholdMeters;
        var bestDistanceSquared = double.MaxValue;
        var hasBest = false;
        var bestPoint = candidatePointMeters;

        foreach (var sceneObject in _snapCandidates)
        {
            if (ReferenceEquals(sceneObject, movingObject))
            {
                continue;
            }

            if (!IsSceneObjectVisible(sceneObject) || !IsLayoutEnabledForMagnet(sceneObject.Layer))
            {
                continue;
            }

            _magnetPointBuffer.Clear();
            CollectMagnetPoints(sceneObject, _magnetPointBuffer);
            foreach (var point in _magnetPointBuffer)
            {
                var dx = point.X - candidatePointMeters.X;
                var dy = point.Y - candidatePointMeters.Y;
                var distanceSquared = (dx * dx) + (dy * dy);
                if (distanceSquared > snapThresholdSquared)
                {
                    continue;
                }

                if (!hasBest || distanceSquared < bestDistanceSquared)
                {
                    hasBest = true;
                    bestDistanceSquared = distanceSquared;
                    bestPoint = point;
                }
            }
        }

        foreach (var guide in _guideLines)
        {
            var point = guide.IsVertical
                ? new Vector2((float)guide.PositionMeters, candidatePointMeters.Y)
                : new Vector2(candidatePointMeters.X, (float)guide.PositionMeters);

            var dx = point.X - candidatePointMeters.X;
            var dy = point.Y - candidatePointMeters.Y;
            var distanceSquared = (dx * dx) + (dy * dy);
            if (distanceSquared > snapThresholdSquared)
            {
                continue;
            }

            if (!hasBest || distanceSquared < bestDistanceSquared)
            {
                hasBest = true;
                bestDistanceSquared = distanceSquared;
                bestPoint = point;
            }
        }

        if (hasBest)
        {
            SetMagnetGuide(candidatePointMeters, bestPoint);
            return bestPoint;
        }

        ClearMagnetGuide();
        return candidatePointMeters;
    }

    private double ApplyMagnetToGuide(SceneGuideLine guideLine, double candidatePositionMeters, Vector2 pointerWorldMeters, Rect sceneViewportPixels)
    {
        if (guideLine == null || !MagnetEnabled || MagnetDistancePixels <= 0f)
        {
            ClearMagnetGuide();
            return candidatePositionMeters;
        }

        var snapThresholdMeters = Math.Max(0.001d, MagnetDistancePixels / _zoomPixelsPerMeter);
        var cursorThresholdMeters = Math.Max(
            snapThresholdMeters,
            GuidePointSnapCursorDistancePixels / _zoomPixelsPerMeter);
        var bestDelta = double.MaxValue;
        var hasBest = false;
        var bestPosition = candidatePositionMeters;
        var bestTargetPoint = pointerWorldMeters;

        Rect queryRect;
        if (guideLine.IsVertical)
        {
            queryRect = new Rect(
                candidatePositionMeters - snapThresholdMeters,
                pointerWorldMeters.Y - cursorThresholdMeters,
                snapThresholdMeters * 2d,
                cursorThresholdMeters * 2d);
        }
        else
        {
            queryRect = new Rect(
                pointerWorldMeters.X - cursorThresholdMeters,
                candidatePositionMeters - snapThresholdMeters,
                cursorThresholdMeters * 2d,
                snapThresholdMeters * 2d);
        }

        _snapCandidates.Clear();
        _queryToken = _queryToken == int.MaxValue ? 1 : _queryToken + 1;
        _spatialIndex.Query(queryRect, _snapCandidates, _queryToken);

        foreach (var sceneObject in _snapCandidates)
        {
            if (!IsSceneObjectVisible(sceneObject) || !IsLayoutEnabledForMagnet(sceneObject.Layer))
            {
                continue;
            }

            _magnetPointBuffer.Clear();
            CollectMagnetPoints(sceneObject, _magnetPointBuffer);
            foreach (var point in _magnetPointBuffer)
            {
                var axisDelta = guideLine.IsVertical
                    ? Math.Abs(point.X - (float)candidatePositionMeters)
                    : Math.Abs(point.Y - (float)candidatePositionMeters);
                var cursorDelta = guideLine.IsVertical
                    ? Math.Abs(point.Y - pointerWorldMeters.Y)
                    : Math.Abs(point.X - pointerWorldMeters.X);
                if (axisDelta > snapThresholdMeters || cursorDelta > cursorThresholdMeters)
                {
                    continue;
                }

                var delta = (axisDelta * axisDelta) + (cursorDelta * cursorDelta);
                if (delta >= bestDelta)
                {
                    continue;
                }

                hasBest = true;
                bestDelta = delta;
                bestPosition = guideLine.IsVertical ? point.X : point.Y;
                bestTargetPoint = point;
            }
        }

        foreach (var guide in _guideLines)
        {
            if (guide.Id == guideLine.Id || guide.IsVertical != guideLine.IsVertical)
            {
                continue;
            }

            var delta = Math.Abs(guide.PositionMeters - candidatePositionMeters);
            if (delta > snapThresholdMeters || delta >= bestDelta)
            {
                continue;
            }

            hasBest = true;
            bestDelta = delta;
            bestPosition = guide.PositionMeters;
            bestTargetPoint = guideLine.IsVertical
                ? new Vector2((float)guide.PositionMeters, pointerWorldMeters.Y)
                : new Vector2(pointerWorldMeters.X, (float)guide.PositionMeters);
        }

        if (!hasBest)
        {
            ClearMagnetGuide();
            return candidatePositionMeters;
        }

        var source = guideLine.IsVertical
            ? new Vector2((float)candidatePositionMeters, pointerWorldMeters.Y)
            : new Vector2(pointerWorldMeters.X, (float)candidatePositionMeters);
        SetMagnetGuide(source, bestTargetPoint);
        return bestPosition;
    }

    private bool IsObjectInCurrentMoveSet(SceneCanvasObject sceneObject)
    {
        foreach (var movingObject in _movingObjects)
        {
            if (ReferenceEquals(movingObject.SceneObject, sceneObject))
            {
                return true;
            }
        }

        return false;
    }

    private void CollectMagnetPoints(SceneCanvasObject sceneObject, List<Vector2> destination)
    {
        if (sceneObject is SceneCanvasLineObject lineObject)
        {
            destination.Add(lineObject.StartPointMeters);
            destination.Add(lineObject.EndPointMeters);
            return;
        }

        var bounds = sceneObject.BoundsMeters;
        var topLeft = new Vector2((float)bounds.X, (float)bounds.Y);
        var topRight = new Vector2((float)(bounds.X + bounds.Width), (float)bounds.Y);
        var bottomRight = new Vector2((float)(bounds.X + bounds.Width), (float)(bounds.Y + bounds.Height));
        var bottomLeft = new Vector2((float)bounds.X, (float)(bounds.Y + bounds.Height));

        if (Math.Abs(sceneObject.RotationRadians) > 0.0001d)
        {
            var center = GetObjectCenterWorld(sceneObject);
            topLeft = RotatePointAround(topLeft, center, sceneObject.RotationRadians);
            topRight = RotatePointAround(topRight, center, sceneObject.RotationRadians);
            bottomRight = RotatePointAround(bottomRight, center, sceneObject.RotationRadians);
            bottomLeft = RotatePointAround(bottomLeft, center, sceneObject.RotationRadians);
        }

        destination.Add(topLeft);
        destination.Add(topRight);
        destination.Add(bottomRight);
        destination.Add(bottomLeft);
    }

    private static void CollectMovedObjectMagnetPoints(SceneCanvasObject movingObject, Rect candidateBounds, List<Vector2> destination)
    {
        if (movingObject is SceneCanvasLineObject lineObject)
        {
            var offset = new Vector2(
                (float)(candidateBounds.X - movingObject.BoundsMeters.X),
                (float)(candidateBounds.Y - movingObject.BoundsMeters.Y));
            destination.Add(lineObject.StartPointMeters + offset);
            destination.Add(lineObject.EndPointMeters + offset);
            return;
        }

        var topLeft = new Vector2((float)candidateBounds.X, (float)candidateBounds.Y);
        var topRight = new Vector2((float)(candidateBounds.X + candidateBounds.Width), (float)candidateBounds.Y);
        var bottomRight = new Vector2((float)(candidateBounds.X + candidateBounds.Width), (float)(candidateBounds.Y + candidateBounds.Height));
        var bottomLeft = new Vector2((float)candidateBounds.X, (float)(candidateBounds.Y + candidateBounds.Height));

        if (Math.Abs(movingObject.RotationRadians) > 0.0001d)
        {
            var center = new Vector2(
                (float)(candidateBounds.X + (candidateBounds.Width * 0.5d)),
                (float)(candidateBounds.Y + (candidateBounds.Height * 0.5d)));
            topLeft = RotatePointAround(topLeft, center, movingObject.RotationRadians);
            topRight = RotatePointAround(topRight, center, movingObject.RotationRadians);
            bottomRight = RotatePointAround(bottomRight, center, movingObject.RotationRadians);
            bottomLeft = RotatePointAround(bottomLeft, center, movingObject.RotationRadians);
        }

        destination.Add(topLeft);
        destination.Add(topRight);
        destination.Add(bottomRight);
        destination.Add(bottomLeft);
    }


    private static Rect GetBoundsFromPoints(List<Vector2> points)
    {
        if (points.Count == 0)
        {
            return new Rect(0d, 0d, 0d, 0d);
        }

        var minX = double.PositiveInfinity;
        var minY = double.PositiveInfinity;
        var maxX = double.NegativeInfinity;
        var maxY = double.NegativeInfinity;

        foreach (var point in points)
        {
            minX = Math.Min(minX, point.X);
            minY = Math.Min(minY, point.Y);
            maxX = Math.Max(maxX, point.X);
            maxY = Math.Max(maxY, point.Y);
        }

        return new Rect(minX, minY, Math.Max(0d, maxX - minX), Math.Max(0d, maxY - minY));
    }

    private void SetMagnetGuide(Vector2 sourceMeters, Vector2 targetMeters)
    {
        _hasMagnetGuide = true;
        _magnetGuideSourceMeters = sourceMeters;
        _magnetGuideTargetMeters = targetMeters;
    }

    private void ClearMagnetGuide()
    {
        _hasMagnetGuide = false;
    }

    private static Rect ExpandRect(Rect rect, double margin)
    {
        return new Rect(
            rect.X - margin,
            rect.Y - margin,
            rect.Width + (margin * 2d),
            rect.Height + (margin * 2d));
    }

    private bool TryGetVisibleObjectsBounds(out Rect bounds)
    {
        bounds = default;

        var hasBounds = false;
        var minX = double.PositiveInfinity;
        var minY = double.PositiveInfinity;
        var maxX = double.NegativeInfinity;
        var maxY = double.NegativeInfinity;

        foreach (var sceneObject in _objects)
        {
            if (!IsSceneObjectVisible(sceneObject))
            {
                continue;
            }

            var objectBounds = sceneObject.SpatialBoundsMeters;
            if (!double.IsFinite(objectBounds.X)
                || !double.IsFinite(objectBounds.Y)
                || !double.IsFinite(objectBounds.Width)
                || !double.IsFinite(objectBounds.Height))
            {
                continue;
            }

            hasBounds = true;
            minX = Math.Min(minX, objectBounds.X);
            minY = Math.Min(minY, objectBounds.Y);
            maxX = Math.Max(maxX, objectBounds.X + objectBounds.Width);
            maxY = Math.Max(maxY, objectBounds.Y + objectBounds.Height);
        }

        if (!hasBounds)
        {
            return false;
        }

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

        return true;
    }

    private bool TryGetSelectedObjectsBounds(out Rect bounds)
    {
        bounds = default;

        var hasBounds = false;
        var minX = double.PositiveInfinity;
        var minY = double.PositiveInfinity;
        var maxX = double.NegativeInfinity;
        var maxY = double.NegativeInfinity;

        foreach (var sceneObject in _selectedObjects)
        {
            if (!IsSceneObjectVisible(sceneObject))
            {
                continue;
            }

            var objectBounds = sceneObject.SpatialBoundsMeters;
            if (!double.IsFinite(objectBounds.X)
                || !double.IsFinite(objectBounds.Y)
                || !double.IsFinite(objectBounds.Width)
                || !double.IsFinite(objectBounds.Height))
            {
                continue;
            }

            hasBounds = true;
            minX = Math.Min(minX, objectBounds.X);
            minY = Math.Min(minY, objectBounds.Y);
            maxX = Math.Max(maxX, objectBounds.X + objectBounds.Width);
            maxY = Math.Max(maxY, objectBounds.Y + objectBounds.Height);
        }

        if (!hasBounds)
        {
            return false;
        }

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

        return true;
    }

    private static double CalculateNiceStep(double minimumStep)
    {
        if (minimumStep <= 0d)
        {
            return 1d;
        }

        var exponent = Math.Pow(10d, Math.Floor(Math.Log10(minimumStep)));
        var fraction = minimumStep / exponent;

        if (fraction <= 1d) return exponent;
        if (fraction <= 2d) return 2d * exponent;
        if (fraction <= 5d) return 5d * exponent;
        return 10d * exponent;
    }

    private static bool IsMajorLine(double value, double majorStep)
    {
        if (majorStep <= 0d)
        {
            return false;
        }

        var ratio = value / majorStep;
        return Math.Abs(ratio - Math.Round(ratio)) < 0.0001d;
    }

    private static string FormatMeters(double meters)
    {
        var abs = Math.Abs(meters);
        if (abs >= 1000d)
        {
            return $"{meters / 1000d:0.##} km";
        }

        if (abs >= 1d)
        {
            return $"{meters:0.##} m";
        }

        return $"{meters:0.###} m";
    }

    private void RefreshThemeResources()
    {
        _surfaceBackgroundColor = ResolveColor("SolidBackgroundFillColorBaseBrush", Color.FromArgb(255, 245, 245, 245));
        _sceneBackgroundColor = ResolveColor("SolidBackgroundFillColorQuarternaryBrush", Color.FromArgb(255, 250, 250, 250));
        _rulerBackgroundColor = ResolveColor("SolidBackgroundFillColorTertiaryBrush", Color.FromArgb(255, 240, 240, 240));
        _rulerTextColor = ResolveColor("TextFillColorSecondaryBrush", Color.FromArgb(255, 90, 90, 90));
        var rulerTickBaseColor = ResolveColor("TextFillColorPrimaryBrush", Color.FromArgb(255, 45, 45, 45));
        _rulerTickMajorColor = WithAlpha(rulerTickBaseColor, ActualTheme == ElementTheme.Dark ? (byte)230 : (byte)190);
        _rulerTickMidColor = WithAlpha(rulerTickBaseColor, ActualTheme == ElementTheme.Dark ? (byte)200 : (byte)155);
        _rulerTickMinorColor = WithAlpha(rulerTickBaseColor, ActualTheme == ElementTheme.Dark ? (byte)165 : (byte)125);
        _gridMajorColor = ResolveColor("ControlStrongStrokeColorDefaultBrush", Color.FromArgb(80, 90, 90, 90));
        _gridMinorColor = ResolveColor("SurfaceStrokeColorDefaultBrush", Color.FromArgb(45, 90, 90, 90));
        _selectionColor = ResolveColor("AccentFillColorDefaultBrush", Color.FromArgb(255, 0, 120, 215));
        _borderColor = ResolveColor("SurfaceStrokeColorFlyoutBrush", Color.FromArgb(150, 120, 120, 120));
        _magnetGuideColor = ActualTheme == ElementTheme.Dark
            ? Color.FromArgb(255, 255, 214, 92)
            : Color.FromArgb(255, 204, 64, 0);
        _guideLineColor = Color.FromArgb(255, 210, 35, 35);
    }

    private static Color ResolveColor(string resourceKey, Color fallback)
    {
        var resources = Application.Current?.Resources;
        if (resources == null)
        {
            return fallback;
        }

        if (resources.TryGetValue(resourceKey, out var value))
        {
            if (value is SolidColorBrush brush)
            {
                return brush.Color;
            }

            if (value is Color color)
            {
                return color;
            }
        }

        return fallback;
    }

    private static Color WithAlpha(Color color, byte alpha)
    {
        return Color.FromArgb(alpha, color.R, color.G, color.B);
    }

    private void EnsureDefaultLayouts()
    {
        if (_layouts.Count > 0)
        {
            return;
        }

        _layouts.Add(new SceneCanvasLayoutInfo(0, "Layout 0", string.Empty, true));
        _layouts.Add(new SceneCanvasLayoutInfo(1, "Layout 1", string.Empty, true));
        _layouts.Add(new SceneCanvasLayoutInfo(2, "Layout 2", string.Empty, true));
        RebuildLayoutIndexCache();
        _nextLayoutId = 3;
        _activeLayer = 0;
    }

    private void RebuildLayoutIndexCache()
    {
        _layoutIndexById.Clear();
        for (var i = 0; i < _layouts.Count; i++)
        {
            _layoutIndexById[_layouts[i].Id] = i;
        }
    }

    private bool LayoutExists(int layoutId)
    {
        return FindLayout(layoutId) != null;
    }

    private bool MoveLayoutInternal(int layoutId, int direction)
    {
        if (!CanEditObjects())
        {
            return false;
        }

        if (_layouts.Count < 2)
        {
            return false;
        }

        var sourceIndex = FindLayoutIndex(layoutId);
        if (sourceIndex < 0)
        {
            return false;
        }

        var targetIndex = sourceIndex + direction;
        if (targetIndex < 0 || targetIndex >= _layouts.Count)
        {
            return false;
        }

        var history = CaptureHistorySnapshot();
        (_layouts[sourceIndex], _layouts[targetIndex]) = (_layouts[targetIndex], _layouts[sourceIndex]);
        RebuildLayoutIndexCache();
        PushUndoSnapshot(history, true);
        RequestRender();
        LayoutsChanged?.Invoke(this, EventArgs.Empty);
        return true;
    }

    private bool IsLayoutEnabledForMagnet(int layoutId)
    {
        if (_magnetActiveLayoutOnly)
        {
            return layoutId == ActiveLayer;
        }

        return _magnetLayoutIds.Contains(layoutId);
    }

    private bool IsLayoutVisible(int layoutId)
    {
        var layout = FindLayout(layoutId);
        return layout?.IsVisible != false;
    }

    private bool IsSceneObjectVisible(SceneCanvasObject? sceneObject)
    {
        return sceneObject != null
            && sceneObject.IsVisible
            && IsLayoutVisible(sceneObject.Layer);
    }

    private void NormalizeMagnetLayoutFilter()
    {
        for (var i = _magnetLayoutIds.Count - 1; i >= 0; i--)
        {
            if (LayoutExists(_magnetLayoutIds[i]))
            {
                continue;
            }

            _magnetLayoutIds.RemoveAt(i);
        }

        if (_magnetActiveLayoutOnly)
        {
            return;
        }

        if (_magnetLayoutIds.Count == 0)
        {
            _magnetLayoutIds.Add(ActiveLayer);
        }
    }

    private SceneCanvasLayoutInfo? FindLayout(int layoutId)
    {
        foreach (var layout in _layouts)
        {
            if (layout.Id == layoutId)
            {
                return layout;
            }
        }

        return null;
    }

    private SceneCanvasLayoutInfo? FindLayoutByName(string layoutName)
    {
        if (string.IsNullOrWhiteSpace(layoutName))
        {
            return null;
        }

        var normalized = layoutName.Trim();
        foreach (var layout in _layouts)
        {
            if (string.Equals(layout.Name, normalized, StringComparison.OrdinalIgnoreCase))
            {
                return layout;
            }
        }

        return null;
    }

    private int FindLayoutIndex(int layoutId)
    {
        return _layoutIndexById.TryGetValue(layoutId, out var index) ? index : -1;
    }

    private int ResolveExistingLayoutId(int preferredLayoutId)
    {
        var existing = FindLayout(preferredLayoutId);
        if (existing != null)
        {
            return existing.Id;
        }

        if (_layouts.Count == 0)
        {
            EnsureDefaultLayouts();
        }

        return _layouts.Count > 0 ? _layouts[0].Id : 0;
    }

    private int FindReplacementLayoutId(int removedLayoutId)
    {
        foreach (var layout in _layouts)
        {
            if (layout.Id != removedLayoutId)
            {
                return layout.Id;
            }
        }

        return ResolveExistingLayoutId(_activeLayer);
    }

    private string CreateUniqueLayoutName(string? requestedName, int fallbackId, int? ignoreLayoutId = null)
    {
        var baseName = string.IsNullOrWhiteSpace(requestedName)
            ? $"Layout {fallbackId}"
            : requestedName.Trim();

        var candidate = baseName;
        var index = 2;
        while (IsLayoutNameUsed(candidate, ignoreLayoutId))
        {
            candidate = $"{baseName} ({index})";
            index++;
        }

        return candidate;
    }

    private bool IsLayoutNameUsed(string layoutName, int? ignoreLayoutId = null)
    {
        foreach (var layout in _layouts)
        {
            if (ignoreLayoutId.HasValue && layout.Id == ignoreLayoutId.Value)
            {
                continue;
            }

            if (string.Equals(layout.Name, layoutName, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
        }

        return false;
    }

    private static string NormalizeLayoutDescription(string? description)
    {
        return string.IsNullOrWhiteSpace(description) ? string.Empty : description.Trim();
    }

    private bool CanEditObjects()
    {
        return IsEditingEnabled && !IsRulerModeEnabled;
    }

    private void CancelEditingInteraction()
    {
        if (_interactionMode is InteractionMode.MovingObject
            or InteractionMode.MovingGuideLine
            or InteractionMode.MovingLinePoint
            or InteractionMode.ResizingObject
            or InteractionMode.RotatingObject
            or InteractionMode.AreaSelection)
        {
            _Canvas.ReleasePointerCaptures();
            _interactionMode = InteractionMode.None;
            _activeObject = null;
            _activeGuideId = -1;
            _activeResizeHandle = ResizeHandle.None;
            _movingObjects.Clear();
            ClearMagnetGuide();
            CancelInteractionHistory();
            SetCursor(InputSystemCursorShape.Arrow);
            RequestRender();
        }
    }

    private void ClearMeasureState()
    {
        _hasMeasureStart = false;
        _hasMeasureEnd = false;
        _hasMeasureHover = false;
        _isMeasureHoverSnapped = false;
        _measureStartMeters = Vector2.Zero;
        _measureEndMeters = Vector2.Zero;
        _measureHoverMeters = Vector2.Zero;
        ClearMagnetGuide();
    }

    private SceneCanvasHistorySnapshot? CaptureHistorySnapshot()
    {
        if (_isApplyingHistory || _interactionHistoryStart != null)
        {
            return null;
        }

        return CreateHistorySnapshot();
    }

    private void PushUndoSnapshot(SceneCanvasHistorySnapshot? snapshot, bool changed)
    {
        if (!changed || snapshot == null || _isApplyingHistory)
        {
            return;
        }

        _undoHistory.Add(snapshot);
        if (_undoHistory.Count > MaxUndoHistoryEntries)
        {
            _undoHistory.RemoveAt(0);
        }
    }

    private void BeginInteractionHistory()
    {
        if (_isApplyingHistory)
        {
            return;
        }

        _interactionHistoryStart = CreateHistorySnapshot();
        _interactionChanged = false;
    }

    private void EndInteractionHistory(bool changed)
    {
        PushUndoSnapshot(_interactionHistoryStart, changed);
        _interactionHistoryStart = null;
        _interactionChanged = false;
    }

    private void CancelInteractionHistory()
    {
        _interactionHistoryStart = null;
        _interactionChanged = false;
    }

    private SceneCanvasHistorySnapshot CreateHistorySnapshot()
    {
        var objects = new List<SceneCanvasObject>(_objects.Count);
        foreach (var sceneObject in _objects)
        {
            objects.Add(CloneForHistory(sceneObject));
        }

        var layouts = new List<SceneCanvasLayoutInfo>(_layouts.Count);
        foreach (var layout in _layouts)
        {
            layouts.Add(new SceneCanvasLayoutInfo(layout.Id, layout.Name, layout.Description, layout.IsVisible));
        }

        var selectedIds = new List<string>(_selectedObjects.Count);
        foreach (var selectedObject in _selectedObjects)
        {
            selectedIds.Add(selectedObject.Id);
        }

        var selectedId = SelectedObject?.Id;
        var guides = new List<SceneGuideLine>(_guideLines.Count);
        foreach (var guide in _guideLines)
        {
            guides.Add(guide.Clone());
        }

        return new SceneCanvasHistorySnapshot(
            objects,
            selectedIds,
            selectedId,
            ActiveLayer,
            layouts,
            _nextLayoutId,
            guides,
            _nextGuideId);
    }

    private void ApplyHistorySnapshot(SceneCanvasHistorySnapshot snapshot)
    {
        _isApplyingHistory = true;
        try
        {
            _objects.Clear();
            _spatialIndex.Clear();
            _objectDrawOrder.Clear();
            _nextObjectDrawOrderId = 1L;
            _selectedObjects.Clear();
            SelectedObject = null;
            _layouts.Clear();
            _guideLines.Clear();
            foreach (var layout in snapshot.Layouts)
            {
                _layouts.Add(new SceneCanvasLayoutInfo(layout.Id, layout.Name, layout.Description, layout.IsVisible));
            }
            RebuildLayoutIndexCache();
            foreach (var guide in snapshot.Guides)
            {
                _guideLines.Add(guide.Clone());
            }
            _nextGuideId = Math.Max(snapshot.NextGuideId, 1);
            foreach (var guide in _guideLines)
            {
                _nextGuideId = Math.Max(_nextGuideId, guide.Id + 1);
            }

            var maxLayoutId = 0;
            foreach (var layout in _layouts)
            {
                if (layout.Id > maxLayoutId)
                {
                    maxLayoutId = layout.Id;
                }
            }

            _nextLayoutId = Math.Max(Math.Max(snapshot.NextLayoutId, 1), maxLayoutId + 1);
            if (_layouts.Count == 0)
            {
                EnsureDefaultLayouts();
            }
            else
            {
                RebuildLayoutIndexCache();
            }

            foreach (var snapshotObject in snapshot.Objects)
            {
                var restoredObject = CloneForHistory(snapshotObject);
                restoredObject.Layer = ResolveExistingLayoutId(restoredObject.Layer);
                restoredObject.BoundsMeters = ApplyPrecision(restoredObject.BoundsMeters);
                _objects.Add(restoredObject);
                RegisterObjectDrawOrder(restoredObject);
                _spatialIndex.Add(restoredObject);
            }

            var previousActive = _activeLayer;
            _activeLayer = ResolveExistingLayoutId(snapshot.ActiveLayer);

            var byId = new Dictionary<string, SceneCanvasObject>(StringComparer.OrdinalIgnoreCase);
            foreach (var sceneObject in _objects)
            {
                byId[sceneObject.Id] = sceneObject;
            }

            foreach (var selectedId in snapshot.SelectedObjectIds)
            {
                if (!byId.TryGetValue(selectedId, out var selectedObject))
                {
                    continue;
                }

                _selectedObjects.Add(selectedObject);
            }

            if (!string.IsNullOrWhiteSpace(snapshot.PrimarySelectedObjectId)
                && byId.TryGetValue(snapshot.PrimarySelectedObjectId, out var preferred))
            {
                SelectedObject = preferred;
            }
            else if (_selectedObjects.Count > 0)
            {
                SelectedObject = _selectedObjects[^1];
            }

            _sceneBoundsDirty = true;
            UpdateScrollBars();
            RequestRender();
            SelectedObjectChanged?.Invoke(this, SelectedObject);
            SelectionChanged?.Invoke(this, EventArgs.Empty);
            RaiseSelectionGeometryChanged();
            LayoutsChanged?.Invoke(this, EventArgs.Empty);
            if (previousActive != _activeLayer)
            {
                ActiveLayoutChanged?.Invoke(this, _activeLayer);
            }
        }
        finally
        {
            _isApplyingHistory = false;
        }
    }

    private static SceneCanvasObject CloneForHistory(SceneCanvasObject sceneObject)
    {
        var clone = sceneObject.Clone();
        clone.Id = sceneObject.Id;
        return clone;
    }

    private void RequestRender()
    {
        if (_isReleased)
        {
            return;
        }

        _hasPendingRenderRequest = true;

        if (_isRenderRequestQueued)
        {
            return;
        }

        var dispatcherQueue = DispatcherQueue;
        if (dispatcherQueue == null)
        {
            FlushRenderRequest();
            return;
        }

        _isRenderRequestQueued = true;
        var priority = _interactionMode == InteractionMode.None
            ? Microsoft.UI.Dispatching.DispatcherQueuePriority.Low
            : Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal;
        if (!dispatcherQueue.TryEnqueue(priority, FlushRenderRequest))
        {
            _isRenderRequestQueued = false;
            FlushRenderRequest();
        }
    }

    private void FlushRenderRequest()
    {
        _isRenderRequestQueued = false;

        if (_isReleased || !_hasPendingRenderRequest)
        {
            return;
        }

        _hasPendingRenderRequest = false;
        _Canvas?.Invalidate();
    }

    private sealed class SceneGuideLine
    {
        public SceneGuideLine(int id, bool isVertical, double positionMeters)
        {
            Id = Math.Max(1, id);
            IsVertical = isVertical;
            PositionMeters = positionMeters;
        }

        public int Id { get; }

        public bool IsVertical { get; }

        public double PositionMeters { get; set; }

        public SceneGuideLine Clone()
        {
            return new SceneGuideLine(Id, IsVertical, PositionMeters);
        }
    }

    private sealed class SceneCanvasHistorySnapshot
    {
        public SceneCanvasHistorySnapshot(
            List<SceneCanvasObject> objects,
            List<string> selectedObjectIds,
            string? primarySelectedObjectId,
            int activeLayer,
            List<SceneCanvasLayoutInfo> layouts,
            int nextLayoutId,
            List<SceneGuideLine> guides,
            int nextGuideId)
        {
            Objects = objects;
            SelectedObjectIds = selectedObjectIds;
            PrimarySelectedObjectId = primarySelectedObjectId;
            ActiveLayer = activeLayer;
            Layouts = layouts;
            NextLayoutId = nextLayoutId;
            Guides = guides;
            NextGuideId = nextGuideId;
        }

        public List<SceneCanvasObject> Objects { get; }

        public List<string> SelectedObjectIds { get; }

        public string? PrimarySelectedObjectId { get; }

        public int ActiveLayer { get; }

        public List<SceneCanvasLayoutInfo> Layouts { get; }

        public int NextLayoutId { get; }

        public List<SceneGuideLine> Guides { get; }

        public int NextGuideId { get; }
    }
}
