using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Sonex.Client.Controls;
using Sonex.Client.Controls.SceneCanvas;
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation;
using Windows.UI;

namespace Sonex.Client.Pages.Laboratory;

public sealed partial class SceneCanvasLab : Page
{
    private const double PasteOffsetMeters = 0.25d;

    private readonly Random _random = new();
    private readonly List<SceneCanvasObject> _clipboard = [];
    private int _pasteSequence;
    private bool _isLeftPanelSplitInitialized;
    private bool _isUpdatingLayerPanel;
    private bool _isUpdatingPropertyPanel;
    private readonly ObservableCollection<LayerListItem> _layerItems = [];
    private readonly ObservableCollection<ObjectLibraryItem> _libraryItems = [];
    private readonly ObservableCollection<ObjectLibraryItem> _visibleLibraryItems = [];
    private readonly ObservableCollection<string> _libraryCategories = [];
    private ObjectLibraryItem? _draggingLibraryItem;
    private bool _isLibraryDragPreviewActive;

    public SceneCanvasLab()
    {
        InitializeComponent();
        Loaded += SceneCanvasLab_Loaded;
        _LeftPanelsHost.SizeChanged += LeftPanelsHost_SizeChanged;

        _ToolBar.MagnetToggled += ToolBar_MagnetToggled;
        _ToolBar.MagnetLayoutsChanged += ToolBar_MagnetLayoutsChanged;
        _ToolBar.ActiveLayoutChanged += ToolBar_ActiveLayoutChanged;
        _ToolBar.AddLayoutRequested += ToolBar_AddLayoutRequested;
        _ToolBar.RenameLayoutRequested += ToolBar_RenameLayoutRequested;
        _ToolBar.DeleteLayoutRequested += ToolBar_DeleteLayoutRequested;
        _ToolBar.MoveSelectionToLayoutRequested += ToolBar_MoveSelectionToLayoutRequested;
        _ToolBar.MoveLayoutUpRequested += ToolBar_MoveLayoutUpRequested;
        _ToolBar.MoveLayoutDownRequested += ToolBar_MoveLayoutDownRequested;
        _ToolBar.ZoomInRequested += ToolBar_ZoomInRequested;
        _ToolBar.ZoomOutRequested += ToolBar_ZoomOutRequested;
        _ToolBar.FitToObjectsRequested += ToolBar_FitToObjectsRequested;
        _ToolBar.ZoomToSelectionRequested += ToolBar_ZoomToSelectionRequested;
        _ToolBar.GridVisibilityChanged += ToolBar_GridVisibilityChanged;
        _ToolBar.RulersVisibilityChanged += ToolBar_RulersVisibilityChanged;
        _ToolBar.MatchSelectionWidthRequested += ToolBar_MatchSelectionWidthRequested;
        _ToolBar.MatchSelectionHeightRequested += ToolBar_MatchSelectionHeightRequested;
        _ToolBar.RotateSelectionLeftRequested += ToolBar_RotateSelectionLeftRequested;
        _ToolBar.RotateSelectionRightRequested += ToolBar_RotateSelectionRightRequested;
        _ToolBar.AlignmentRequested += ToolBar_AlignmentRequested;
        _ToolBar.LayoutRequested += ToolBar_LayoutRequested;
        _ToolBar.CutRequested += ToolBar_CutRequested;
        _ToolBar.CopyRequested += ToolBar_CopyRequested;
        _ToolBar.PasteRequested += ToolBar_PasteRequested;
        _ToolBar.DeleteRequested += ToolBar_DeleteRequested;
        _ToolBar.CloneRequested += ToolBar_CloneRequested;
        _ToolBar.UndoRequested += ToolBar_UndoRequested;
        _ToolBar.BringToFrontRequested += ToolBar_BringToFrontRequested;
        _ToolBar.SendToBackRequested += ToolBar_SendToBackRequested;
        _ToolBar.MoveSelectionRequested += ToolBar_MoveSelectionRequested;
        _ToolBar.AddVerticalGuideRequested += ToolBar_AddVerticalGuideRequested;
        _ToolBar.AddHorizontalGuideRequested += ToolBar_AddHorizontalGuideRequested;
        _ToolBar.EditModeChanged += ToolBar_EditModeChanged;
        _ToolBar.RulerModeChanged += ToolBar_RulerModeChanged;
        _ToolBar.ShapePropertiesChanged += ToolBar_ShapePropertiesChanged;
        _ToolBar.LinePropertiesChanged += ToolBar_LinePropertiesChanged;

        _SceneCanvas.SelectionChanged += SceneCanvas_SelectionChanged;
        _SceneCanvas.SelectionGeometryChanged += SceneCanvas_SelectionGeometryChanged;
        _SceneCanvas.CopyRequested += SceneCanvas_CopyRequested;
        _SceneCanvas.CutRequested += SceneCanvas_CutRequested;
        _SceneCanvas.PasteRequested += SceneCanvas_PasteRequested;
        _SceneCanvas.DeleteRequested += SceneCanvas_DeleteRequested;
        _SceneCanvas.CloneRequested += SceneCanvas_CloneRequested;
        _SceneCanvas.RenameLayoutRequested += SceneCanvas_RenameLayoutRequested;
        _SceneCanvas.ActiveLayoutChanged += SceneCanvas_ActiveLayoutChanged;
        _SceneCanvas.LayoutsChanged += SceneCanvas_LayoutsChanged;
        _SceneCanvas.MagnetEnabledChanged += SceneCanvas_MagnetEnabledChanged;
        _SceneCanvas.EditModeChanged += SceneCanvas_EditModeChanged;
        _SceneCanvas.RulerModeChanged += SceneCanvas_RulerModeChanged;
        _SceneCanvas.GridVisibilityChanged += SceneCanvas_GridVisibilityChanged;
        _SceneCanvas.RulersVisibilityChanged += SceneCanvas_RulersVisibilityChanged;

        _LayersListView.ItemsSource = _layerItems;
        _LibraryObjectsListView.ItemsSource = _visibleLibraryItems;
        _LibraryCategoryComboBox.ItemsSource = _libraryCategories;

        InitializeObjectLibrary();
        RebuildLibraryCategories();
        ApplyObjectLibraryFilter();

        _ToolBar.IsMagnetEnabled = true;
        _SceneCanvas.MagnetEnabled = _ToolBar.IsMagnetEnabled;
        _SceneCanvas.PrecisionMeters = 0.01d;
        _SceneCanvas.IsEditingEnabled = true;
        _SceneCanvas.IsRulerModeEnabled = false;
        _SceneCanvas.IsGridVisible = true;
        _SceneCanvas.AreRulersVisible = true;
        _ToolBar.IsEditModeEnabled = _SceneCanvas.IsEditingEnabled;
        _ToolBar.IsRulerModeEnabled = _SceneCanvas.IsRulerModeEnabled;
        _ToolBar.IsGridVisible = _SceneCanvas.IsGridVisible;
        _ToolBar.AreRulersVisible = _SceneCanvas.AreRulersVisible;
        _ToolBar.SetLayouts(_SceneCanvas.GetLayouts(), _SceneCanvas.ActiveLayoutId);
        _ToolBar.ActiveLayoutId = _SceneCanvas.ActiveLayoutId;
        _ToolBar.SetMagnetLayoutSelection(activeLayoutOnly: true, [_SceneCanvas.ActiveLayoutId]);
        _SceneCanvas.SetMagnetLayoutFilter(activeLayoutOnly: true, [_SceneCanvas.ActiveLayoutId]);
        RefreshLayerPanel();
        UpdateObjectLibraryControlsState();
        UpdateSelectionContext();
    }

    private void SceneCanvasLab_Loaded(object sender, RoutedEventArgs e)
    {
        if (_SceneCanvas.Objects.Count == 0)
        {
            GenerateScene(10_000);
        }

        InitializeLeftPanelSplit();
        RefreshLayoutContext();
        UpdateSelectionContext();
    }

    private void LeftPanelsHost_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (_isLeftPanelSplitInitialized)
        {
            return;
        }

        InitializeLeftPanelSplit();
    }

    private void InitializeLeftPanelSplit()
    {
        if (_isLeftPanelSplitInitialized)
        {
            return;
        }

        var hostHeight = _LeftPanelsHost.ActualHeight;
        if (!double.IsFinite(hostHeight) || hostHeight <= 0d)
        {
            return;
        }

        var sizerHeight = _LeftPanelContentSizer.ActualHeight > 0d
            ? _LeftPanelContentSizer.ActualHeight
            : _LeftPanelContentSizer.Height;

        var topPanelHeight = Math.Max(_LeftTopPanel.MinHeight, (hostHeight - sizerHeight) * 0.5d);
        _LeftTopPanel.Height = topPanelHeight;
        _isLeftPanelSplitInitialized = true;
    }

    private void ToolBar_MagnetToggled(object? sender, bool isEnabled)
    {
        _SceneCanvas.MagnetEnabled = isEnabled;
    }

    private void ToolBar_MagnetLayoutsChanged(object? sender, SceneCanvasMagnetLayoutsChangedEventArgs e)
    {
        _SceneCanvas.SetMagnetLayoutFilter(e.ActiveLayoutOnly, e.LayoutIds);
    }

    private void ToolBar_ActiveLayoutChanged(object? sender, int layoutId)
    {
        _SceneCanvas.ActiveLayoutId = layoutId;
    }

    private async void ToolBar_AddLayoutRequested(object? sender, EventArgs e)
    {
        var defaultName = $"Layout {_SceneCanvas.GetLayouts().Count + 1}";
        var layoutName = await PromptLayoutNameAsync("Add layout", defaultName);
        if (layoutName == null)
        {
            return;
        }

        var addedLayoutId = _SceneCanvas.AddLayout(layoutName);
        if (addedLayoutId < 0)
        {
            return;
        }

        _SceneCanvas.ActiveLayoutId = addedLayoutId;
        RefreshLayoutContext();
    }

    private async void ToolBar_RenameLayoutRequested(object? sender, int layoutId)
    {
        var layout = _SceneCanvas.GetLayout(layoutId);
        if (layout == null)
        {
            return;
        }

        var layoutName = await PromptLayoutNameAsync("Rename layout", layout.Name);
        if (layoutName == null)
        {
            return;
        }

        if (_SceneCanvas.RenameLayout(layoutId, layoutName))
        {
            RefreshLayoutContext();
        }
    }

    private void ToolBar_DeleteLayoutRequested(object? sender, int layoutId)
    {
        if (_SceneCanvas.RemoveLayout(layoutId))
        {
            RefreshLayoutContext();
        }
    }

    private void ToolBar_MoveSelectionToLayoutRequested(object? sender, int layoutId)
    {
        _SceneCanvas.MoveSelectionToLayout(layoutId);
        UpdateSelectionContext();
    }

    private void ToolBar_MoveLayoutUpRequested(object? sender, int layoutId)
    {
        if (_SceneCanvas.MoveLayoutUp(layoutId))
        {
            RefreshLayoutContext();
        }
    }

    private void ToolBar_MoveLayoutDownRequested(object? sender, int layoutId)
    {
        if (_SceneCanvas.MoveLayoutDown(layoutId))
        {
            RefreshLayoutContext();
        }
    }

    private void ToolBar_ZoomInRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.ZoomInToSelection();
    }

    private void ToolBar_ZoomOutRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.ZoomOut();
    }

    private void ToolBar_FitToObjectsRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.FitToAllObjects();
    }

    private void ToolBar_ZoomToSelectionRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.FitToSelection();
    }

    private void ToolBar_GridVisibilityChanged(object? sender, bool isVisible)
    {
        _SceneCanvas.IsGridVisible = isVisible;
    }

    private void ToolBar_RulersVisibilityChanged(object? sender, bool isVisible)
    {
        _SceneCanvas.AreRulersVisible = isVisible;
    }

    private void ToolBar_MatchSelectionWidthRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.MatchSelectionWidth();
    }

    private void ToolBar_MatchSelectionHeightRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.MatchSelectionHeight();
    }

    private void ToolBar_RotateSelectionLeftRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.RotateSelectionLeft();
    }

    private void ToolBar_RotateSelectionRightRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.RotateSelectionRight();
    }

    private void ToolBar_AlignmentRequested(object? sender, SceneCanvas.SelectionAlignment alignment)
    {
        _SceneCanvas.AlignSelection(alignment);
    }

    private void ToolBar_LayoutRequested(object? sender, SceneCanvas.SelectionLayout layout)
    {
        _SceneCanvas.LayoutSelection(layout);
    }

    private void ToolBar_CutRequested(object? sender, EventArgs e)
    {
        if (!CopySelectionToClipboard())
        {
            return;
        }

        DeleteSelection();
    }

    private void ToolBar_CopyRequested(object? sender, EventArgs e)
    {
        CopySelectionToClipboard();
    }

    private void ToolBar_PasteRequested(object? sender, EventArgs e)
    {
        PasteClipboard();
    }

    private void ToolBar_DeleteRequested(object? sender, EventArgs e)
    {
        DeleteSelection();
    }

    private void ToolBar_CloneRequested(object? sender, EventArgs e)
    {
        CloneSelection();
    }

    private void ToolBar_UndoRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.UndoLastChange();
    }

    private void ToolBar_BringToFrontRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.BringSelectionToFront();
    }

    private void ToolBar_SendToBackRequested(object? sender, EventArgs e)
    {
        _SceneCanvas.SendSelectionToBack();
    }

    private void ToolBar_MoveSelectionRequested(object? sender, SceneCanvasMoveSelectionRequestedEventArgs e)
    {
        _SceneCanvas.MoveSelectionBy(e.DeltaX, e.DeltaY);
    }

    private void ToolBar_AddVerticalGuideRequested(object? sender, EventArgs e)
    {
        if (TryGetSelectedGuideOrigin(out var x, out _))
        {
            _SceneCanvas.AddVerticalGuide(x);
            return;
        }

        _SceneCanvas.AddVerticalGuide();
    }

    private void ToolBar_AddHorizontalGuideRequested(object? sender, EventArgs e)
    {
        if (TryGetSelectedGuideOrigin(out _, out var y))
        {
            _SceneCanvas.AddHorizontalGuide(y);
            return;
        }

        _SceneCanvas.AddHorizontalGuide();
    }

    private void ToolBar_EditModeChanged(object? sender, bool isEnabled)
    {
        _SceneCanvas.IsEditingEnabled = isEnabled;
    }

    private void ToolBar_RulerModeChanged(object? sender, bool isEnabled)
    {
        _SceneCanvas.IsRulerModeEnabled = isEnabled;
    }

    private void ToolBar_ShapePropertiesChanged(object? sender, SceneCanvasShapePropertiesChangedEventArgs e)
    {
        if (!_SceneCanvas.IsEditingEnabled || _SceneCanvas.IsRulerModeEnabled)
        {
            return;
        }

        var selectedObject = _SceneCanvas.SelectedObject;
        if (selectedObject == null || selectedObject is SceneCanvasLineObject)
        {
            return;
        }

        var x = SnapToPrecision(e.X);
        var y = SnapToPrecision(e.Y);
        var z = SnapToPrecision(e.Z);
        var width = Math.Max(_SceneCanvas.PrecisionMeters, SnapToPrecision(Math.Max(0d, e.Width)));
        var height = Math.Max(_SceneCanvas.PrecisionMeters, SnapToPrecision(Math.Max(0d, e.Height)));
        var rotationRadians = DegreesToRadians(e.RotationDegrees);

        selectedObject.BoundsMeters = new Rect(x, y, width, height);
        selectedObject.PositionZMeters = z;
        selectedObject.RotationRadians = rotationRadians;
        _SceneCanvas.InvalidateObject(selectedObject);
        UpdateSelectionContext();
    }

    private void ToolBar_LinePropertiesChanged(object? sender, SceneCanvasLinePropertiesChangedEventArgs e)
    {
        if (!_SceneCanvas.IsEditingEnabled || _SceneCanvas.IsRulerModeEnabled)
        {
            return;
        }

        if (_SceneCanvas.SelectedObject is not SceneCanvasLineObject lineObject)
        {
            return;
        }

        var start = new Vector2((float)SnapToPrecision(e.StartX), (float)SnapToPrecision(e.StartY));
        var end = new Vector2((float)SnapToPrecision(e.EndX), (float)SnapToPrecision(e.EndY));
        var startZ = SnapToPrecision(e.StartZ);
        var endZ = SnapToPrecision(e.EndZ);
        var thickness = Math.Max(0.0001d, SnapToPrecision(Math.Max(0.0001d, e.Thickness)));

        lineObject.SetPoints(start, end);
        lineObject.StartPointZMeters = startZ;
        lineObject.EndPointZMeters = endZ;
        lineObject.ThicknessMeters = thickness;
        _SceneCanvas.InvalidateObject(lineObject);
        UpdateSelectionContext();
    }

    private void SceneCanvas_SelectionChanged(object? sender, EventArgs e)
    {
        UpdateSelectionContext();
    }

    private void SceneCanvas_SelectionGeometryChanged(object? sender, EventArgs e)
    {
        UpdateSelectionContext();
    }

    private void SceneCanvas_ActiveLayoutChanged(object? sender, int layoutId)
    {
        _ToolBar.ActiveLayoutId = layoutId;
        RefreshLayerPanel(rebuildItems: false);
        UpdateSelectionContext();
    }

    private void SceneCanvas_LayoutsChanged(object? sender, EventArgs e)
    {
        RefreshLayoutContext();
    }

    private void SceneCanvas_CopyRequested(object? sender, EventArgs e)
    {
        CopySelectionToClipboard();
    }

    private void SceneCanvas_CutRequested(object? sender, EventArgs e)
    {
        if (!CopySelectionToClipboard())
        {
            return;
        }

        DeleteSelection();
    }

    private void SceneCanvas_PasteRequested(object? sender, EventArgs e)
    {
        PasteClipboard();
    }

    private void SceneCanvas_DeleteRequested(object? sender, EventArgs e)
    {
        DeleteSelection();
    }

    private void SceneCanvas_CloneRequested(object? sender, EventArgs e)
    {
        CloneSelection();
    }

    private async void SceneCanvas_RenameLayoutRequested(object? sender, int layoutId)
    {
        var layout = _SceneCanvas.GetLayout(layoutId);
        if (layout == null)
        {
            return;
        }

        var layoutName = await PromptLayoutNameAsync("Rename layout", layout.Name);
        if (layoutName == null)
        {
            return;
        }

        if (_SceneCanvas.RenameLayout(layoutId, layoutName))
        {
            RefreshLayoutContext();
        }
    }

    private void SceneCanvas_MagnetEnabledChanged(object? sender, bool isEnabled)
    {
        _ToolBar.IsMagnetEnabled = isEnabled;
    }

    private void SceneCanvas_EditModeChanged(object? sender, bool isEnabled)
    {
        if (!CanPlaceLibraryObject())
        {
            EndLibraryDragPreview();
        }

        _ToolBar.IsEditModeEnabled = isEnabled;
        UpdateLayerPanelControlsState();
        UpdateObjectLibraryControlsState();
        RefreshPropertyPanel();
    }

    private void SceneCanvas_RulerModeChanged(object? sender, bool isEnabled)
    {
        if (!CanPlaceLibraryObject())
        {
            EndLibraryDragPreview();
        }

        _ToolBar.IsRulerModeEnabled = isEnabled;
        UpdateLayerPanelControlsState();
        UpdateObjectLibraryControlsState();
        RefreshPropertyPanel();
    }

    private void SceneCanvas_GridVisibilityChanged(object? sender, bool isVisible)
    {
        _ToolBar.IsGridVisible = isVisible;
    }

    private void SceneCanvas_RulersVisibilityChanged(object? sender, bool isVisible)
    {
        _ToolBar.AreRulersVisible = isVisible;
    }

    private void RefreshLayerPanel(bool rebuildItems = true)
    {
        if (rebuildItems)
        {
            var layouts = _SceneCanvas.GetLayouts();

            _isUpdatingLayerPanel = true;
            try
            {
                _layerItems.Clear();
                foreach (var layout in layouts)
                {
                    _layerItems.Add(new LayerListItem(layout.Id, layout.Name, layout.Description, layout.IsVisible));
                }
            }
            finally
            {
                _isUpdatingLayerPanel = false;
            }
        }

        SelectActiveLayerInPanel();
        UpdateLayerPanelControlsState();
    }

    private void SelectActiveLayerInPanel()
    {
        var active = _layerItems.FirstOrDefault(item => item.Id == _SceneCanvas.ActiveLayoutId);
        if (ReferenceEquals(_LayersListView.SelectedItem, active))
        {
            return;
        }

        _isUpdatingLayerPanel = true;
        try
        {
            _LayersListView.SelectedItem = active;
        }
        finally
        {
            _isUpdatingLayerPanel = false;
        }
    }

    private void UpdateLayerPanelControlsState()
    {
        var canEdit = _SceneCanvas.IsEditingEnabled && !_SceneCanvas.IsRulerModeEnabled;
        var selected = _LayersListView.SelectedItem as LayerListItem;
        var selectedIndex = selected == null ? -1 : _layerItems.IndexOf(selected);

        _AddLayerPanelButton.IsEnabled = canEdit;
        _DeleteLayerPanelButton.IsEnabled = canEdit && selected != null && _layerItems.Count > 1;
        _EditLayerPanelButton.IsEnabled = canEdit && selected != null;
        _CloneLayerPanelButton.IsEnabled = canEdit && selected != null;
        _MoveLayerUpPanelButton.IsEnabled = canEdit && selectedIndex > 0;
        _MoveLayerDownPanelButton.IsEnabled = canEdit && selectedIndex >= 0 && selectedIndex < (_layerItems.Count - 1);
    }

    private async void AddLayerPanelButton_Click(object sender, RoutedEventArgs e)
    {
        if (!_SceneCanvas.IsEditingEnabled || _SceneCanvas.IsRulerModeEnabled)
        {
            return;
        }

        var defaultName = $"Layer {_SceneCanvas.GetLayouts().Count + 1}";
        var result = await PromptLayerDetailsAsync("New layer", defaultName, string.Empty);
        if (result == null)
        {
            return;
        }

        var addedLayerId = _SceneCanvas.AddLayout(result.Value.Name);
        if (addedLayerId < 0)
        {
            return;
        }

        _SceneCanvas.UpdateLayout(addedLayerId, result.Value.Name, result.Value.Description);
        _SceneCanvas.SetLayoutVisibility(addedLayerId, true);
        _SceneCanvas.ActiveLayoutId = addedLayerId;
        RefreshLayoutContext();
    }

    private void DeleteLayerPanelButton_Click(object sender, RoutedEventArgs e)
    {
        if (_LayersListView.SelectedItem is not LayerListItem selected)
        {
            return;
        }

        if (_SceneCanvas.RemoveLayout(selected.Id))
        {
            RefreshLayoutContext();
        }
    }

    private async void EditLayerPanelButton_Click(object sender, RoutedEventArgs e)
    {
        if (_LayersListView.SelectedItem is not LayerListItem selected)
        {
            return;
        }

        var currentLayout = _SceneCanvas.GetLayout(selected.Id);
        if (currentLayout == null)
        {
            return;
        }

        var result = await PromptLayerDetailsAsync("Edit layer", currentLayout.Name, currentLayout.Description);
        if (result == null)
        {
            return;
        }

        if (_SceneCanvas.UpdateLayout(selected.Id, result.Value.Name, result.Value.Description))
        {
            RefreshLayoutContext();
        }
    }

    private void CloneLayerPanelButton_Click(object sender, RoutedEventArgs e)
    {
        if (_LayersListView.SelectedItem is not LayerListItem selected)
        {
            return;
        }

        var sourceLayout = _SceneCanvas.GetLayout(selected.Id);
        if (sourceLayout == null)
        {
            return;
        }

        var cloneName = $"{sourceLayout.Name} Copy";
        var newLayoutId = _SceneCanvas.AddLayout(cloneName);
        if (newLayoutId < 0)
        {
            return;
        }

        _SceneCanvas.UpdateLayout(newLayoutId, cloneName, sourceLayout.Description);
        _SceneCanvas.SetLayoutVisibility(newLayoutId, true);

        var sourceObjects = new List<SceneCanvasObject>();
        foreach (var sceneObject in _SceneCanvas.Objects)
        {
            if (sceneObject.Layer == sourceLayout.Id)
            {
                sourceObjects.Add(sceneObject);
            }
        }

        if (sourceObjects.Count > 0)
        {
            var nextZIndex = GetNextZIndex();
            var clonedObjects = new List<SceneCanvasObject>(sourceObjects.Count);
            foreach (var sourceObject in sourceObjects)
            {
                var clone = sourceObject.Clone();
                clone.Layer = newLayoutId;
                clone.ZIndex = nextZIndex++;
                clonedObjects.Add(clone);
            }

            _SceneCanvas.AddObjects(clonedObjects);
        }

        _SceneCanvas.ActiveLayoutId = newLayoutId;
        RefreshLayoutContext();
    }

    private void MoveLayerUpPanelButton_Click(object sender, RoutedEventArgs e)
    {
        if (!_SceneCanvas.IsEditingEnabled || _SceneCanvas.IsRulerModeEnabled)
        {
            return;
        }

        if (_LayersListView.SelectedItem is not LayerListItem selected)
        {
            return;
        }

        if (_SceneCanvas.MoveLayoutUp(selected.Id))
        {
            RefreshLayoutContext();
        }
    }

    private void MoveLayerDownPanelButton_Click(object sender, RoutedEventArgs e)
    {
        if (!_SceneCanvas.IsEditingEnabled || _SceneCanvas.IsRulerModeEnabled)
        {
            return;
        }

        if (_LayersListView.SelectedItem is not LayerListItem selected)
        {
            return;
        }

        if (_SceneCanvas.MoveLayoutDown(selected.Id))
        {
            RefreshLayoutContext();
        }
    }

    private void LayersListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (_isUpdatingLayerPanel)
        {
            return;
        }

        if (_LayersListView.SelectedItem is not LayerListItem selected)
        {
            UpdateLayerPanelControlsState();
            return;
        }

        if (!selected.IsVisible)
        {
            _SceneCanvas.SetLayoutVisibility(selected.Id, true);
        }

        _SceneCanvas.ActiveLayoutId = selected.Id;
        UpdateLayerPanelControlsState();
    }

    private void LayerVisibilityCheckBox_Changed(object sender, RoutedEventArgs e)
    {
        if (_isUpdatingLayerPanel || sender is not CheckBox checkBox || checkBox.DataContext is not LayerListItem layerItem)
        {
            return;
        }

        _SceneCanvas.SetLayoutVisibility(layerItem.Id, checkBox.IsChecked == true);
    }

    private async Task<(string Name, string Description)?> PromptLayerDetailsAsync(string title, string initialName, string initialDescription)
    {
        var nameBox = new TextBox
        {
            Text = initialName ?? string.Empty,
            MinWidth = 280
        };

        var descriptionBox = new TextBox
        {
            Text = initialDescription ?? string.Empty,
            MinWidth = 280,
            AcceptsReturn = true,
            TextWrapping = TextWrapping.Wrap,
            MinHeight = 88
        };

        var content = new StackPanel
        {
            Spacing = 8,
            Children =
            {
                new TextBlock { Text = "Name" },
                nameBox,
                new TextBlock { Text = "Description" },
                descriptionBox
            }
        };

        var dialog = new ContentDialog
        {
            XamlRoot = XamlRoot,
            Title = title,
            Content = content,
            PrimaryButtonText = "Save",
            CloseButtonText = "Cancel",
            DefaultButton = ContentDialogButton.Primary
        };

        var result = await dialog.ShowAsync();
        if (result != ContentDialogResult.Primary)
        {
            return null;
        }

        var name = nameBox.Text?.Trim();
        if (string.IsNullOrWhiteSpace(name))
        {
            return null;
        }

        return (name, descriptionBox.Text?.Trim() ?? string.Empty);
    }

    private void InitializeObjectLibrary()
    {
        _libraryItems.Clear();
        _libraryItems.Add(new ObjectLibraryItem("Basic", "Rectangle", "Basic rectangular object", ObjectLibraryKind.Rectangle));
        _libraryItems.Add(new ObjectLibraryItem("Basic", "Wide Rectangle", "Wider rectangle", ObjectLibraryKind.Rectangle)
        {
            DefaultWidthMeters = 2.2d,
            DefaultHeightMeters = 1.0d
        });
        _libraryItems.Add(new ObjectLibraryItem("Basic", "Square", "Square object", ObjectLibraryKind.Rectangle)
        {
            DefaultWidthMeters = 1.2d,
            DefaultHeightMeters = 1.2d
        });
        _libraryItems.Add(new ObjectLibraryItem("Lines", "Horizontal Line", "Line object", ObjectLibraryKind.Line)
        {
            DefaultLengthMeters = 1.8d
        });
        _libraryItems.Add(new ObjectLibraryItem("Lines", "Vertical Line", "Line object", ObjectLibraryKind.Line)
        {
            DefaultLengthMeters = 1.8d,
            DefaultLineAngleDegrees = 90d
        });
        _libraryItems.Add(new ObjectLibraryItem("SVG", "SVG Symbol", "Vector object rendered from SVG code", ObjectLibraryKind.Svg)
        {
            DefaultWidthMeters = 1.5d,
            DefaultHeightMeters = 1.5d,
            SvgCode = SceneCanvasSvgObject.DefaultSvgCode
        });
    }

    private void RebuildLibraryCategories()
    {
        var previousSelection = _LibraryCategoryComboBox.SelectedItem as string;

        _libraryCategories.Clear();
        _libraryCategories.Add("All");

        var distinctCategories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var item in _libraryItems)
        {
            if (!string.IsNullOrWhiteSpace(item.Category))
            {
                distinctCategories.Add(item.Category.Trim());
            }
        }

        foreach (var category in distinctCategories.OrderBy(value => value, StringComparer.OrdinalIgnoreCase))
        {
            _libraryCategories.Add(category);
        }

        if (!string.IsNullOrWhiteSpace(previousSelection) && _libraryCategories.Contains(previousSelection))
        {
            _LibraryCategoryComboBox.SelectedItem = previousSelection;
        }
        else
        {
            _LibraryCategoryComboBox.SelectedIndex = 0;
        }
    }

    private void ApplyObjectLibraryFilter()
    {
        var selectedCategory = _LibraryCategoryComboBox.SelectedItem as string;
        if (string.IsNullOrWhiteSpace(selectedCategory))
        {
            selectedCategory = "All";
        }

        var previousSelected = _LibraryObjectsListView.SelectedItem as ObjectLibraryItem;
        _visibleLibraryItems.Clear();

        foreach (var item in _libraryItems)
        {
            if (!string.Equals(selectedCategory, "All", StringComparison.OrdinalIgnoreCase)
                && !string.Equals(item.Category, selectedCategory, StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            _visibleLibraryItems.Add(item);
        }

        if (previousSelected != null && _visibleLibraryItems.Contains(previousSelected))
        {
            _LibraryObjectsListView.SelectedItem = previousSelected;
        }
        else if (_visibleLibraryItems.Count > 0)
        {
            _LibraryObjectsListView.SelectedIndex = 0;
        }
        else
        {
            _LibraryObjectsListView.SelectedItem = null;
        }

        UpdateObjectLibraryControlsState();
    }

    private void LibraryCategoryComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ApplyObjectLibraryFilter();
    }

    private void LibraryObjectsListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        UpdateObjectLibraryControlsState();
    }

    private void LibraryObjectsListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
    {
        _draggingLibraryItem = e.Items.OfType<ObjectLibraryItem>().FirstOrDefault()
            ?? _LibraryObjectsListView.SelectedItem as ObjectLibraryItem;

        if (_draggingLibraryItem == null || !CanPlaceLibraryObject())
        {
            e.Cancel = true;
            _draggingLibraryItem = null;
            EndLibraryDragPreview();
            return;
        }

        e.Data.RequestedOperation = DataPackageOperation.Copy;
        e.Data.SetText(_draggingLibraryItem.Name);
    }

    private void LibraryObjectsListView_DragItemsCompleted(object sender, DragItemsCompletedEventArgs e)
    {
        _draggingLibraryItem = null;
        EndLibraryDragPreview();
    }

    private void SceneCanvas_DragEnter(object sender, DragEventArgs e)
    {
        if (_draggingLibraryItem == null || !CanPlaceLibraryObject())
        {
            e.AcceptedOperation = DataPackageOperation.None;
            EndLibraryDragPreview();
            return;
        }

        e.AcceptedOperation = DataPackageOperation.Copy;
        e.DragUIOverride.IsCaptionVisible = false;
        e.DragUIOverride.IsContentVisible = false;
        e.DragUIOverride.IsGlyphVisible = false;

        TryBeginOrUpdateLibraryDragPreview(e);
    }

    private void SceneCanvas_DragOver(object sender, DragEventArgs e)
    {
        if (_draggingLibraryItem == null || !CanPlaceLibraryObject())
        {
            e.AcceptedOperation = DataPackageOperation.None;
            EndLibraryDragPreview();
            return;
        }

        e.AcceptedOperation = DataPackageOperation.Copy;
        e.DragUIOverride.IsCaptionVisible = false;
        e.DragUIOverride.IsContentVisible = false;
        e.DragUIOverride.IsGlyphVisible = false;

        TryBeginOrUpdateLibraryDragPreview(e);
    }

    private void SceneCanvas_DragLeave(object sender, DragEventArgs e)
    {
        EndLibraryDragPreview();
    }

    private void SceneCanvas_Drop(object sender, DragEventArgs e)
    {
        if (_draggingLibraryItem == null || !CanPlaceLibraryObject())
        {
            e.AcceptedOperation = DataPackageOperation.None;
            EndLibraryDragPreview();
            _draggingLibraryItem = null;
            return;
        }

        e.AcceptedOperation = DataPackageOperation.Copy;
        TryBeginOrUpdateLibraryDragPreview(e);

        var droppedObject = _SceneCanvas.GetExternalDragPreviewSnapshot();
        if (droppedObject == null)
        {
            if (_SceneCanvas.TryGetWorldPointFromCanvas(e.GetPosition(_SceneCanvas), out var center))
            {
                droppedObject = CreateSceneObjectFromLibraryItem(_draggingLibraryItem, center, _SceneCanvas.ActiveLayoutId, GetNextZIndex());
            }
        }

        EndLibraryDragPreview();
        _draggingLibraryItem = null;

        if (droppedObject == null)
        {
            return;
        }

        droppedObject.Layer = _SceneCanvas.ActiveLayoutId;
        droppedObject.ZIndex = GetNextZIndex();
        _SceneCanvas.AddObject(droppedObject);
        _SceneCanvas.SetSelection([droppedObject]);
    }

    private bool TryBeginOrUpdateLibraryDragPreview(DragEventArgs e)
    {
        if (_draggingLibraryItem == null)
        {
            return false;
        }

        var pointer = e.GetPosition(_SceneCanvas);
        var suppressMagnet = e.Modifiers.HasFlag(Windows.ApplicationModel.DataTransfer.DragDrop.DragDropModifiers.Control);

        if (_isLibraryDragPreviewActive)
        {
            _SceneCanvas.UpdateExternalDragPreview(pointer, useMagnet: true, suppressMagnet: suppressMagnet);
            return true;
        }

        var previewObject = CreateSceneObjectFromLibraryItem(_draggingLibraryItem, Vector2.Zero, _SceneCanvas.ActiveLayoutId, int.MaxValue);
        if (previewObject == null)
        {
            return false;
        }

        _SceneCanvas.BeginExternalDragPreview(previewObject, pointer, useMagnet: true, suppressMagnet: suppressMagnet);
        _isLibraryDragPreviewActive = true;
        return true;
    }

    private void EndLibraryDragPreview()
    {
        if (!_isLibraryDragPreviewActive)
        {
            return;
        }

        _isLibraryDragPreviewActive = false;
        _SceneCanvas.EndExternalDragPreview();
    }

    private void UpdateObjectLibraryControlsState()
    {
        var hasSelected = _LibraryObjectsListView.SelectedItem is ObjectLibraryItem;
        _EditLibraryObjectButton.IsEnabled = hasSelected;
        _DeleteLibraryObjectButton.IsEnabled = hasSelected;
    }

    private async void AddLibraryObjectButton_Click(object sender, RoutedEventArgs e)
    {
        var selectedCategory = _LibraryCategoryComboBox.SelectedItem as string;
        var result = await PromptObjectLibraryItemAsync(
            "Add object definition",
            selectedCategory is null || string.Equals(selectedCategory, "All", StringComparison.OrdinalIgnoreCase) ? "Basic" : selectedCategory,
            "New object",
            string.Empty,
            ObjectLibraryKind.Rectangle,
            SceneCanvasSvgObject.DefaultSvgCode);

        if (result == null)
        {
            return;
        }

        _libraryItems.Add(new ObjectLibraryItem(result.Value.Category, result.Value.Name, result.Value.Description, result.Value.Kind)
        {
            SvgCode = result.Value.SvgCode
        });
        RebuildLibraryCategories();
        _LibraryCategoryComboBox.SelectedItem = result.Value.Category;
        ApplyObjectLibraryFilter();
    }

    private async void EditLibraryObjectButton_Click(object sender, RoutedEventArgs e)
    {
        if (_LibraryObjectsListView.SelectedItem is not ObjectLibraryItem selected)
        {
            return;
        }

        var result = await PromptObjectLibraryItemAsync(
            "Edit object definition",
            selected.Category,
            selected.Name,
            selected.Description,
            selected.Kind,
            selected.SvgCode);

        if (result == null)
        {
            return;
        }

        selected.Category = result.Value.Category;
        selected.Name = result.Value.Name;
        selected.Description = result.Value.Description;
        selected.Kind = result.Value.Kind;
        selected.SvgCode = result.Value.SvgCode;

        RebuildLibraryCategories();
        _LibraryCategoryComboBox.SelectedItem = selected.Category;
        ApplyObjectLibraryFilter();
    }

    private void DeleteLibraryObjectButton_Click(object sender, RoutedEventArgs e)
    {
        if (_LibraryObjectsListView.SelectedItem is not ObjectLibraryItem selected)
        {
            return;
        }

        _libraryItems.Remove(selected);
        RebuildLibraryCategories();
        ApplyObjectLibraryFilter();
    }

    private void LibraryObjectsListView_DoubleTapped(object sender, Microsoft.UI.Xaml.Input.DoubleTappedRoutedEventArgs e)
    {
        if (_LibraryObjectsListView.SelectedItem is not ObjectLibraryItem selected)
        {
            return;
        }

        InsertLibraryObjectIntoScene(selected);
    }

    private async Task<(string Category, string Name, string Description, ObjectLibraryKind Kind, string SvgCode)?> PromptObjectLibraryItemAsync(
        string title,
        string initialCategory,
        string initialName,
        string initialDescription,
        ObjectLibraryKind initialKind,
        string? initialSvgCode)
    {
        var categoryBox = new TextBox
        {
            Text = initialCategory ?? string.Empty,
            MinWidth = 260
        };

        var nameBox = new TextBox
        {
            Text = initialName ?? string.Empty,
            MinWidth = 260
        };

        var descriptionBox = new TextBox
        {
            Text = initialDescription ?? string.Empty,
            MinWidth = 260
        };

        var kindComboBox = new ComboBox
        {
            MinWidth = 260,
            ItemsSource = new[]
            {
                ObjectLibraryKind.Rectangle,
                ObjectLibraryKind.Line,
                ObjectLibraryKind.Svg
            },
            SelectedItem = initialKind
        };

        var svgCodeBox = new TextBox
        {
            Text = string.IsNullOrWhiteSpace(initialSvgCode) ? SceneCanvasSvgObject.DefaultSvgCode : initialSvgCode!,
            MinWidth = 260,
            MinHeight = 120,
            AcceptsReturn = true,
            TextWrapping = TextWrapping.Wrap
        };

        var content = new StackPanel
        {
            Spacing = 8,
            Children =
            {
                new TextBlock { Text = "Category" },
                categoryBox,
                new TextBlock { Text = "Name" },
                nameBox,
                new TextBlock { Text = "Description" },
                descriptionBox,
                new TextBlock { Text = "Type" },
                kindComboBox,
                new TextBlock { Text = "SVG code (used only for SVG type)" },
                svgCodeBox
            }
        };

        var dialog = new ContentDialog
        {
            XamlRoot = XamlRoot,
            Title = title,
            Content = content,
            PrimaryButtonText = "Save",
            CloseButtonText = "Cancel",
            DefaultButton = ContentDialogButton.Primary
        };

        var result = await dialog.ShowAsync();
        if (result != ContentDialogResult.Primary)
        {
            return null;
        }

        var category = categoryBox.Text?.Trim();
        var name = nameBox.Text?.Trim();

        if (string.IsNullOrWhiteSpace(category) || string.IsNullOrWhiteSpace(name))
        {
            return null;
        }

        var kind = kindComboBox.SelectedItem is ObjectLibraryKind selectedKind
            ? selectedKind
            : ObjectLibraryKind.Rectangle;
        var svgCode = svgCodeBox.Text?.Trim();
        if (string.IsNullOrWhiteSpace(svgCode))
        {
            svgCode = SceneCanvasSvgObject.DefaultSvgCode;
        }

        return (category, name, descriptionBox.Text?.Trim() ?? string.Empty, kind, svgCode);
    }

    private void InsertLibraryObjectIntoScene(ObjectLibraryItem libraryItem)
    {
        var objectCenter = GetVisibleSceneCenter();
        InsertLibraryObjectIntoScene(libraryItem, objectCenter);
    }

    private void InsertLibraryObjectIntoScene(ObjectLibraryItem libraryItem, Vector2 objectCenter)
    {
        if (!CanPlaceLibraryObject() || libraryItem == null)
        {
            return;
        }

        var newObject = CreateSceneObjectFromLibraryItem(libraryItem, objectCenter, _SceneCanvas.ActiveLayoutId, GetNextZIndex());
        if (newObject == null)
        {
            return;
        }

        _SceneCanvas.AddObject(newObject);
        _SceneCanvas.SetSelection([newObject]);
    }

    private SceneCanvasObject? CreateSceneObjectFromLibraryItem(ObjectLibraryItem libraryItem, Vector2 objectCenter, int layoutId, int zIndex)
    {
        if (libraryItem == null)
        {
            return null;
        }

        var centerX = RoundTo2(objectCenter.X);
        var centerY = RoundTo2(objectCenter.Y);

        if (libraryItem.Kind == ObjectLibraryKind.Line)
        {
            var angleRadians = libraryItem.DefaultLineAngleDegrees * (Math.PI / 180d);
            var halfLength = Math.Max(0.01d, libraryItem.DefaultLengthMeters) * 0.5d;
            var deltaX = RoundTo2(Math.Cos(angleRadians) * halfLength);
            var deltaY = RoundTo2(Math.Sin(angleRadians) * halfLength);
            var start = new Vector2((float)RoundTo2(centerX - deltaX), (float)RoundTo2(centerY - deltaY));
            var end = new Vector2((float)RoundTo2(centerX + deltaX), (float)RoundTo2(centerY + deltaY));

            return new SceneCanvasLineObject(
                start,
                end,
                Math.Max(0.01d, libraryItem.DefaultThicknessMeters),
                Color.FromArgb(255, 54, 105, 201))
            {
                Layer = layoutId,
                ZIndex = zIndex
            };
        }

        if (libraryItem.Kind == ObjectLibraryKind.Svg)
        {
            var svgWidth = Math.Max(0.1d, libraryItem.DefaultWidthMeters);
            var svgHeight = Math.Max(0.1d, libraryItem.DefaultHeightMeters);
            var svgBounds = new Rect(
                RoundTo2(centerX - (svgWidth * 0.5d)),
                RoundTo2(centerY - (svgHeight * 0.5d)),
                RoundTo2(svgWidth),
                RoundTo2(svgHeight));

            return new SceneCanvasSvgObject(svgBounds, libraryItem.SvgCode)
            {
                Layer = layoutId,
                ZIndex = zIndex
            };
        }

        var width = Math.Max(0.1d, libraryItem.DefaultWidthMeters);
        var height = Math.Max(0.1d, libraryItem.DefaultHeightMeters);
        var bounds = new Rect(
            RoundTo2(centerX - (width * 0.5d)),
            RoundTo2(centerY - (height * 0.5d)),
            RoundTo2(width),
            RoundTo2(height));

        return new DemoRectangleObject(
            bounds,
            Color.FromArgb(220, 118, 178, 255),
            Color.FromArgb(255, 78, 132, 220))
        {
            Layer = layoutId,
            ZIndex = zIndex
        };
    }

    private Vector2 GetVisibleSceneCenter()
    {
        return new Vector2(
            (float)(_SceneCanvas.VisibleWorldBoundsMeters.X + (_SceneCanvas.VisibleWorldBoundsMeters.Width * 0.5d)),
            (float)(_SceneCanvas.VisibleWorldBoundsMeters.Y + (_SceneCanvas.VisibleWorldBoundsMeters.Height * 0.5d)));
    }

    private bool CanPlaceLibraryObject()
    {
        return _SceneCanvas.IsEditingEnabled && !_SceneCanvas.IsRulerModeEnabled;
    }

    private void RefreshPropertyPanel()
    {
        var selectedObject = _SceneCanvas.SelectedObject;
        var canEdit = _SceneCanvas.IsEditingEnabled && !_SceneCanvas.IsRulerModeEnabled;
        var isLine = selectedObject is SceneCanvasLineObject;
        var isShape = selectedObject != null && !isLine;

        _ShapePropertiesPanel.Visibility = isShape ? Visibility.Visible : Visibility.Collapsed;
        _LinePropertiesPanel.Visibility = isLine ? Visibility.Visible : Visibility.Collapsed;
        _PropertiesPlaceholderTextBlock.Visibility = selectedObject == null ? Visibility.Visible : Visibility.Collapsed;
        SetPanelEditableState(_ShapePropertiesPanel, canEdit);
        SetPanelEditableState(_LinePropertiesPanel, canEdit);

        if (selectedObject == null)
        {
            return;
        }

        _isUpdatingPropertyPanel = true;
        try
        {
            if (isLine && selectedObject is SceneCanvasLineObject lineObject)
            {
                SetFieldValueIfChanged(_LinePanelStartXNumberBox, RoundTo2(lineObject.StartPointMeters.X));
                SetFieldValueIfChanged(_LinePanelStartYNumberBox, RoundTo2(lineObject.StartPointMeters.Y));
                SetFieldValueIfChanged(_LinePanelStartZNumberBox, RoundTo2(lineObject.StartPointZMeters));
                SetFieldValueIfChanged(_LinePanelEndXNumberBox, RoundTo2(lineObject.EndPointMeters.X));
                SetFieldValueIfChanged(_LinePanelEndYNumberBox, RoundTo2(lineObject.EndPointMeters.Y));
                SetFieldValueIfChanged(_LinePanelEndZNumberBox, RoundTo2(lineObject.EndPointZMeters));
                SetFieldValueIfChanged(_LinePanelThicknessNumberBox, RoundTo2(lineObject.ThicknessMeters));
            }
            else
            {
                var bounds = selectedObject.BoundsMeters;
                SetFieldValueIfChanged(_ShapePanelXNumberBox, RoundTo2(bounds.X));
                SetFieldValueIfChanged(_ShapePanelYNumberBox, RoundTo2(bounds.Y));
                SetFieldValueIfChanged(_ShapePanelZNumberBox, RoundTo2(selectedObject.PositionZMeters));
                SetFieldValueIfChanged(_ShapePanelWidthNumberBox, RoundTo2(bounds.Width));
                SetFieldValueIfChanged(_ShapePanelHeightNumberBox, RoundTo2(bounds.Height));
                SetFieldValueIfChanged(_ShapePanelRotationNumberBox, RoundTo2(selectedObject.RotationRadians * (180d / Math.PI)));
            }
        }
        finally
        {
            _isUpdatingPropertyPanel = false;
        }
    }

    private void ShapePanelNumberBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args)
    {
        if (_isUpdatingPropertyPanel || !_SceneCanvas.IsEditingEnabled || _SceneCanvas.IsRulerModeEnabled)
        {
            return;
        }

        var selectedObject = _SceneCanvas.SelectedObject;
        if (selectedObject == null || selectedObject is SceneCanvasLineObject)
        {
            return;
        }

        var x = SnapToPrecision(_ShapePanelXNumberBox.Value);
        var y = SnapToPrecision(_ShapePanelYNumberBox.Value);
        var z = SnapToPrecision(_ShapePanelZNumberBox.Value);
        var width = Math.Max(_SceneCanvas.PrecisionMeters, SnapToPrecision(Math.Max(0d, _ShapePanelWidthNumberBox.Value)));
        var height = Math.Max(_SceneCanvas.PrecisionMeters, SnapToPrecision(Math.Max(0d, _ShapePanelHeightNumberBox.Value)));
        var rotationRadians = DegreesToRadians(_ShapePanelRotationNumberBox.Value);

        selectedObject.BoundsMeters = new Rect(x, y, width, height);
        selectedObject.PositionZMeters = z;
        selectedObject.RotationRadians = rotationRadians;
        _SceneCanvas.InvalidateObject(selectedObject);
        UpdateSelectionContext();
    }

    private void LinePanelNumberBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args)
    {
        if (_isUpdatingPropertyPanel || !_SceneCanvas.IsEditingEnabled || _SceneCanvas.IsRulerModeEnabled)
        {
            return;
        }

        if (_SceneCanvas.SelectedObject is not SceneCanvasLineObject lineObject)
        {
            return;
        }

        var start = new Vector2((float)SnapToPrecision(_LinePanelStartXNumberBox.Value), (float)SnapToPrecision(_LinePanelStartYNumberBox.Value));
        var end = new Vector2((float)SnapToPrecision(_LinePanelEndXNumberBox.Value), (float)SnapToPrecision(_LinePanelEndYNumberBox.Value));
        var startZ = SnapToPrecision(_LinePanelStartZNumberBox.Value);
        var endZ = SnapToPrecision(_LinePanelEndZNumberBox.Value);
        var thickness = Math.Max(0.0001d, SnapToPrecision(Math.Max(0.0001d, _LinePanelThicknessNumberBox.Value)));

        lineObject.SetPoints(start, end);
        lineObject.StartPointZMeters = startZ;
        lineObject.EndPointZMeters = endZ;
        lineObject.ThicknessMeters = thickness;
        _SceneCanvas.InvalidateObject(lineObject);
        UpdateSelectionContext();
    }

    private static void SetFieldValueIfChanged(ToolBarNumberField field, double value)
    {
        const double epsilon = 0.00001d;
        if (Math.Abs(field.Value - value) < epsilon)
        {
            return;
        }

        field.Value = value;
    }

    private static void SetPanelEditableState(Border panel, bool isEnabled)
    {
        if (panel == null)
        {
            return;
        }

        panel.IsHitTestVisible = isEnabled;
        panel.Opacity = isEnabled ? 1d : 0.62d;
    }

    private void UpdateSelectionContext()
    {
        _ToolBar.SetSelectionContext(_SceneCanvas.SelectedObject, _SceneCanvas.SelectedObjects.Count);
        RefreshPropertyPanel();
        UpdateObjectLibraryControlsState();
    }

    private void RefreshLayoutContext()
    {
        _ToolBar.SetLayouts(_SceneCanvas.GetLayouts(), _SceneCanvas.ActiveLayoutId);
        _ToolBar.ActiveLayoutId = _SceneCanvas.ActiveLayoutId;
        RefreshLayerPanel();
    }

    private bool CopySelectionToClipboard()
    {
        _clipboard.Clear();

        foreach (var sceneObject in _SceneCanvas.SelectedObjects)
        {
            _clipboard.Add(sceneObject.Clone());
        }

        return _clipboard.Count > 0;
    }

    private void PasteClipboard()
    {
        if (_clipboard.Count == 0)
        {
            return;
        }

        _pasteSequence++;
        var offset = PasteOffsetMeters * _pasteSequence;

        var pastedObjects = new List<SceneCanvasObject>(_clipboard.Count);
        var nextZIndex = GetNextZIndex();

        foreach (var sourceObject in _clipboard)
        {
            var clone = sourceObject.Clone();
            OffsetObject(clone, offset, offset);
            clone.Layer = _SceneCanvas.ActiveLayoutId;
            clone.ZIndex = nextZIndex++;
            pastedObjects.Add(clone);
        }

        _SceneCanvas.AddObjects(pastedObjects);
        _SceneCanvas.SetSelection(pastedObjects);
    }

    private void CloneSelection()
    {
        if (_SceneCanvas.SelectedObjects.Count == 0)
        {
            return;
        }

        _pasteSequence++;
        var offset = PasteOffsetMeters * _pasteSequence;

        var clonedObjects = new List<SceneCanvasObject>(_SceneCanvas.SelectedObjects.Count);
        var nextZIndex = GetNextZIndex();

        foreach (var sceneObject in _SceneCanvas.SelectedObjects)
        {
            var clone = sceneObject.Clone();
            OffsetObject(clone, offset, offset);
            clone.Layer = _SceneCanvas.ActiveLayoutId;
            clone.ZIndex = nextZIndex++;
            clonedObjects.Add(clone);
        }

        _SceneCanvas.AddObjects(clonedObjects);
        _SceneCanvas.SetSelection(clonedObjects);
    }

    private void DeleteSelection()
    {
        if (_SceneCanvas.SelectedObjects.Count == 0)
        {
            return;
        }

        _SceneCanvas.RemoveObjects(new List<SceneCanvasObject>(_SceneCanvas.SelectedObjects));
    }

    private int GetNextZIndex()
    {
        var maxZIndex = 0;
        foreach (var sceneObject in _SceneCanvas.Objects)
        {
            maxZIndex = Math.Max(maxZIndex, sceneObject.ZIndex);
        }

        return maxZIndex + 1;
    }

    private void OffsetObject(SceneCanvasObject sceneObject, double offsetX, double offsetY)
    {
        if (sceneObject is SceneCanvasLineObject lineObject)
        {
            lineObject.SetPoints(
                new Vector2(lineObject.StartPointMeters.X + (float)offsetX, lineObject.StartPointMeters.Y + (float)offsetY),
                new Vector2(lineObject.EndPointMeters.X + (float)offsetX, lineObject.EndPointMeters.Y + (float)offsetY));
            return;
        }

        var bounds = sceneObject.BoundsMeters;
        sceneObject.BoundsMeters = new Rect(
            bounds.X + offsetX,
            bounds.Y + offsetY,
            bounds.Width,
            bounds.Height);
    }

    private double SnapToPrecision(double value)
    {
        if (!double.IsFinite(value))
        {
            return 0d;
        }

        var precision = Math.Max(0.001d, _SceneCanvas.PrecisionMeters);
        return Math.Round(value / precision) * precision;
    }

    private static double DegreesToRadians(double degrees)
    {
        return degrees * (Math.PI / 180d);
    }

    private async Task<string?> PromptLayoutNameAsync(string title, string initialName)
    {
        var textBox = new TextBox
        {
            Text = initialName ?? string.Empty,
            MinWidth = 280
        };

        var dialog = new ContentDialog
        {
            XamlRoot = XamlRoot,
            Title = title,
            Content = textBox,
            PrimaryButtonText = "Save",
            CloseButtonText = "Cancel",
            DefaultButton = ContentDialogButton.Primary
        };

        var result = await dialog.ShowAsync();
        if (result != ContentDialogResult.Primary)
        {
            return null;
        }

        var name = textBox.Text?.Trim();
        return string.IsNullOrWhiteSpace(name) ? null : name;
    }

    private void GenerateScene(int objectCount)
    {
        if (objectCount < 1)
        {
            objectCount = 1;
        }

        const float spacingMeters = 2.2f;
        var columns = (int)Math.Ceiling(Math.Sqrt(objectCount));
        var rows = (int)Math.Ceiling(objectCount / (double)columns);

        var objects = new List<SceneCanvasObject>(objectCount);
        var layoutIds = new List<int>();
        foreach (var layout in _SceneCanvas.GetLayouts())
        {
            layoutIds.Add(layout.Id);
        }

        if (layoutIds.Count == 0)
        {
            layoutIds.Add(_SceneCanvas.ActiveLayoutId);
        }

        for (var i = 0; i < objectCount; i++)
        {
            var column = i % columns;
            var row = i / columns;

            var x = RoundTo2(column * spacingMeters + (_random.NextDouble() * 0.25d));
            var y = RoundTo2(row * spacingMeters + (_random.NextDouble() * 0.25d));
            var width = RoundTo2(0.7d + (_random.NextDouble() * 1.1d));
            var height = RoundTo2(0.7d + (_random.NextDouble() * 1.1d));

            if ((i % 5) == 0)
            {
                var start = new Vector2((float)x, (float)y);
                var length = RoundTo2(0.8d + (_random.NextDouble() * 1.8d));
                var angle = (float)(_random.NextDouble() * Math.PI * 2d);
                var end = new Vector2(
                    (float)RoundTo2(start.X + (MathF.Cos(angle) * (float)length)),
                    (float)RoundTo2(start.Y + (MathF.Sin(angle) * (float)length)));

                var stroke = Color.FromArgb(
                    255,
                    (byte)_random.Next(60, 240),
                    (byte)_random.Next(60, 240),
                    (byte)_random.Next(60, 240));

                var thickness = RoundTo2(0.03d + (_random.NextDouble() * 0.09d));
                var lineObject = new SceneCanvasLineObject(start, end, thickness, stroke)
                {
                    Layer = layoutIds[_random.Next(layoutIds.Count)],
                    ZIndex = i
                };

                objects.Add(lineObject);
                continue;
            }

            var fill = Color.FromArgb(
                220,
                (byte)_random.Next(30, 220),
                (byte)_random.Next(70, 240),
                (byte)_random.Next(70, 240));

            var border = Color.FromArgb(
                255,
                (byte)Math.Max(0, fill.R - 40),
                (byte)Math.Max(0, fill.G - 40),
                (byte)Math.Max(0, fill.B - 40));

            var rectangle = new DemoRectangleObject(
                boundsMeters: new Rect(x, y, width, height),
                fillColor: fill,
                borderColor: border)
            {
                Layer = layoutIds[_random.Next(layoutIds.Count)],
                ZIndex = i,
                RotationRadians = RoundTo2((_random.NextDouble() - 0.5d) * 0.45d)
            };

            objects.Add(rectangle);
        }

        _SceneCanvas.SetObjects(objects);
        _SceneCanvas.CenterOn(new Rect(0, 0, columns * spacingMeters, rows * spacingMeters));
        _clipboard.Clear();
        _pasteSequence = 0;
        RefreshLayoutContext();
        UpdateSelectionContext();
    }

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

    private bool TryGetSelectedGuideOrigin(out double x, out double y)
    {
        x = 0d;
        y = 0d;

        var selectedObject = _SceneCanvas.SelectedObject;
        if (selectedObject == null)
        {
            return false;
        }

        if (selectedObject is SceneCanvasLineObject lineObject)
        {
            x = lineObject.StartPointMeters.X;
            y = lineObject.StartPointMeters.Y;
            return double.IsFinite(x) && double.IsFinite(y);
        }

        var bounds = selectedObject.BoundsMeters;
        x = bounds.X;
        y = bounds.Y;
        return double.IsFinite(x) && double.IsFinite(y);
    }

    private sealed class LayerListItem
    {
        public LayerListItem(int id, string name, string description, bool isVisible)
        {
            Id = id;
            Name = name ?? string.Empty;
            Description = description ?? string.Empty;
            IsVisible = isVisible;
        }

        public int Id { get; }

        public string Name { get; set; }

        public string Description { get; set; }

        public bool IsVisible { get; set; }
    }

    private enum ObjectLibraryKind
    {
        Rectangle,
        Line,
        Svg
    }

    private sealed class ObjectLibraryItem
    {
        public ObjectLibraryItem(string category, string name, string description, ObjectLibraryKind kind)
        {
            Category = string.IsNullOrWhiteSpace(category) ? "Basic" : category.Trim();
            Name = string.IsNullOrWhiteSpace(name) ? "Object" : name.Trim();
            Description = description?.Trim() ?? string.Empty;
            Kind = kind;
        }

        public string Category { get; set; }

        public string Name { get; set; }

        public string Description { get; set; }

        public ObjectLibraryKind Kind { get; set; }

        public string SvgCode { get; set; } = SceneCanvasSvgObject.DefaultSvgCode;

        public double DefaultWidthMeters { get; set; } = 1.5d;

        public double DefaultHeightMeters { get; set; } = 1d;

        public double DefaultLengthMeters { get; set; } = 1.8d;

        public double DefaultThicknessMeters { get; set; } = 0.08d;

        public double DefaultLineAngleDegrees { get; set; }
    }

    private sealed class DemoRectangleObject : SceneCanvasObject
    {
        public DemoRectangleObject(Rect boundsMeters, Color fillColor, Color borderColor)
            : base(boundsMeters)
        {
            FillColor = fillColor;
            BorderColor = borderColor;
            CanMove = true;
            CanResize = true;
            CanRotate = true;
        }

        public Color FillColor { get; }

        public Color BorderColor { get; }

        public override SceneCanvasObject Clone()
        {
            var clone = new DemoRectangleObject(BoundsMeters, FillColor, BorderColor);
            CopyBasePropertiesTo(clone);
            return clone;
        }

        public override void OnDraw(SceneCanvasDrawContext context)
        {
            var x = (float)BoundsMeters.X;
            var y = (float)BoundsMeters.Y;
            var width = (float)BoundsMeters.Width;
            var height = (float)BoundsMeters.Height;

            if (width <= 0f || height <= 0f)
            {
                return;
            }

            var strokeWidth = MathF.Max(0.02f, 1f / context.ZoomPixelsPerMeter);
            var ds = context.DrawingSession;

            var previousTransform = ds.Transform;
            if (Math.Abs(RotationRadians) > 0.0001d)
            {
                var center = new Vector2(
                    (float)(BoundsMeters.X + (BoundsMeters.Width * 0.5d)),
                    (float)(BoundsMeters.Y + (BoundsMeters.Height * 0.5d)));

                var localRotation = Matrix3x2.CreateRotation((float)RotationRadians, center);
                ds.Transform = localRotation * previousTransform;
            }

            ds.FillRoundedRectangle(x, y, width, height, 0, 0, FillColor);
            ds.DrawRoundedRectangle(x, y, width, height, 0, 0, BorderColor, strokeWidth);

            if (!previousTransform.Equals(ds.Transform))
            {
                ds.Transform = previousTransform;
            }
        }
    }
}

public sealed class EmptyStringToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return value is string text && !string.IsNullOrWhiteSpace(text)
            ? Visibility.Visible
            : Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return string.Empty;
    }
}
