using SkiaSharp.Views.Maui.Controls;
using Lucy.ViewModels;
using SkiaSharp;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Lucy.Views.Editor
{
    /// <summary>
    /// The main editor control that displays the document pages.
    /// </summary>
    public class DocumentEditor : ContentView
    {
        public static readonly BindableProperty DocumentProperty =
            BindableProperty.Create(nameof(Document), typeof(DocumentVM), typeof(DocumentEditor), null, propertyChanged: OnDocumentChanged);

        public DocumentVM Document
        {
            get => (DocumentVM)GetValue(DocumentProperty);
            set => SetValue(DocumentProperty, value);
        }

        private StackLayout _pagesStack;

        public DocumentEditor()
        {
            ///We should change this to a CollectionView that we can then apply properties to, where each PageVM is its own collection item.
            ///Furthermore, is the viewmodel being populated by the view or is it the other way around? If viewmodel relies on the view to generate the render, that's bad.
            ///We need to figure out how sections and such will reorder themselves when pages are reordered.
            ///
            ///Page rendering needs to be made asyncronous with virtualisation, I think. Performance is shit right now when we're not making edits.
            ///We'll need to be smart about enabling WYSIWYG live editing.
            
            _pagesStack = new StackLayout
            {
                Orientation = StackOrientation.Vertical,
                Spacing = 20
            };

            Content = new ScrollView
            {
                Content = _pagesStack
            };

            SetPaperSizeToA4();
        }

        private static void OnDocumentChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var editor = (DocumentEditor)bindable;

            if (oldValue is DocumentVM oldDocument)
            {
                oldDocument.PropertyChanged -= editor.OnDocumentPropertyChanged;
                if (oldDocument.Sections is INotifyCollectionChanged oldSections)
                {
                    oldSections.CollectionChanged -= editor.OnSectionsCollectionChanged;
                }
            }

            if (newValue is DocumentVM newDocument)
            {
                newDocument.PropertyChanged += editor.OnDocumentPropertyChanged;
                if (newDocument.Sections is INotifyCollectionChanged newSections)
                {
                    newSections.CollectionChanged += editor.OnSectionsCollectionChanged;
                }
            }

            editor.GeneratePageViews();
        }

        private void OnDocumentPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // You can add specific property checks here if needed
            GeneratePageViews();
        }

        private void OnSectionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // Handle additions, removals, or replacements in sections
            GeneratePageViews();
        }

        /// <summary>
        /// Generates the page views for the document.
        /// </summary>
        private void GeneratePageViews()
        {
            _pagesStack.Children.Clear();

            if (Document == null)
                return;

            var renderer = new DocumentRenderer(Document, (float)PaperWidth, (float)PaperHeight);

            foreach (var page in renderer.Pages)
            {
                var canvasView = new SKCanvasView
                {
                    WidthRequest = PaperWidth,
                    HeightRequest = PaperHeight
                };
                canvasView.PaintSurface += (sender, args) =>
                {
                    var canvas = args.Surface.Canvas;
                    canvas.Clear(SKColors.Transparent);

                    var pageRenderer = new PageRenderer();
                    pageRenderer.RenderPage(canvas, page);
                };

                _pagesStack.Children.Add(canvasView);
            }

            // TODO: Implement virtualization to improve performance for large documents.
            // What Hasn't Been Implemented: Virtualization of page rendering.
            // Next Steps: Use virtualization techniques to render only visible pages.
        }

        public double PaperWidth { get; private set; }
        public double PaperHeight { get; private set; }

        private const double Dpi = 96.0;

        /// <summary>
        /// Sets the paper size to A4 dimensions.
        /// </summary>
        private void SetPaperSizeToA4()
        {
            const double A4WidthMm = 210;
            const double A4HeightMm = 297;

            double widthInches = A4WidthMm / 25.4;
            double heightInches = A4HeightMm / 25.4;

            double widthDIPs = widthInches * Dpi;
            double heightDIPs = heightInches * Dpi;

            PaperWidth = widthDIPs;
            PaperHeight = heightDIPs;
        }
    }
}
using System.Collections.Generic;
using System.Linq;
using Lucy.ViewModels;
using Lucy.ViewModels.Blocks.Base;
using SkiaSharp;
using System;
using Lucy.ViewModels.Blocks;
using Lucy.ViewModels.Internals;
using Lucy.Enumerators;
using Lucy.Models.Blocks;
using System.Collections.ObjectModel;
using Lucy.ViewModels.Editor;
using Lucy.Models.Editor;

namespace Lucy.Views.Editor
{
    /// <summary>
    /// Handles the pagination of the document into pages and layers for the editor.
    /// </summary>
    public class DocumentRenderer
    {
        /// <summary>
        /// When opening files that try to import attributes such as spacing, the UI freaks the fuck out.
        /// When relying on default values, such as with .txt files, everything is fine.
        /// Whaaat the fuck?
        /// </summary>
        
        private readonly DocumentVM _document;
        private readonly float _paperWidth;
        private readonly float _paperHeight;

        private const float MarginLeft = 50f;
        private const float MarginTop = 50f;
        private const float MarginRight = 50f;
        private const float MarginBottom = 50f;

        public List<PageVM> Pages { get; private set; } = new List<PageVM>();

        public DocumentRenderer(DocumentVM document, float paperWidth, float paperHeight)
        {
            _document = document;
            _paperWidth = paperWidth;
            _paperHeight = paperHeight;

            GeneratePages();
        }

        /// <summary>
        /// Generates pages and layers based on the content of the document.
        /// </summary>
        private void GeneratePages()
        {
            Pages.Clear();
            int pageNumber = 1;

            var currentPageM = CreateNewPage(pageNumber);
            var currentPageVM = new PageVM(currentPageM);
            Pages.Add(currentPageVM);

            var currentLayerM = new LayerM { Order = 0 };
            var currentLayerVM = new LayerVM(currentLayerM);
            currentPageVM.Layers.Add(currentLayerVM);

            float yPosition = MarginTop;
            bool isFirstBlockOnPage = true; // Flag to manage SpacingBefore

            foreach (var section in _document.Sections)
            {
                // Handle PageBreakBeforeSection
                if (_document.PageBreakBeforeSection && !isFirstBlockOnPage)
                {
                    pageNumber++;
                    currentPageM = CreateNewPage(pageNumber);
                    currentPageVM = new PageVM(currentPageM);
                    Pages.Add(currentPageVM);

                    currentLayerM = new LayerM { Order = 0 };
                    currentLayerVM = new LayerVM(currentLayerM);
                    currentPageVM.Layers.Add(currentLayerVM);

                    yPosition = MarginTop;
                    isFirstBlockOnPage = true;
                }

                // Create heading block
                var headingBlock = CreateHeadingBlock(section);
                float headingHeight = MeasureBlockHeight(headingBlock, _paperWidth - MarginLeft - MarginRight - (float)(headingBlock.LeftIndentation + headingBlock.RightIndentation));

                // Apply SpacingBefore if not the first block on the page
                if (!isFirstBlockOnPage)
                {
                    yPosition += (float)headingBlock.SpacingBefore;
                }

                // Check if heading fits on the current page
                if (yPosition + headingHeight > _paperHeight - MarginBottom)
                {
                    // Start new page
                    pageNumber++;
                    currentPageM = CreateNewPage(pageNumber);
                    currentPageVM = new PageVM(currentPageM);
                    Pages.Add(currentPageVM);

                    currentLayerM = new LayerM { Order = 0 };
                    currentLayerVM = new LayerVM(currentLayerM);
                    currentPageVM.Layers.Add(currentLayerVM);

                    yPosition = MarginTop + (float)headingBlock.SpacingBefore;
                    isFirstBlockOnPage = true; // Reset flag for new page
                }

                // Set block position
                headingBlock.X = MarginLeft + (float)headingBlock.LeftIndentation;
                headingBlock.Y = yPosition;
                currentLayerVM.Blocks.Add(headingBlock);
                yPosition += headingHeight + (float)headingBlock.SpacingAfter;
                isFirstBlockOnPage = false;

                foreach (var block in section.Blocks)
                {
                    float blockHeight = MeasureBlockHeight(block, _paperWidth - MarginLeft - MarginRight - (float)(block.LeftIndentation + block.RightIndentation));

                    // Apply SpacingBefore if not the first block on the page
                    if (!isFirstBlockOnPage)
                    {
                        yPosition += (float)block.SpacingBefore;
                    }

                    // Check if block fits on the current page
                    if (yPosition + blockHeight > _paperHeight - MarginBottom)
                    {
                        // Handle 'KeepWithNext' property
                        if (block.KeepWithNext && block != section.Blocks.Last())
                        {
                            // Start new page
                            pageNumber++;
                            currentPageM = CreateNewPage(pageNumber);
                            currentPageVM = new PageVM(currentPageM);
                            Pages.Add(currentPageVM);

                            currentLayerM = new LayerM { Order = 0 };
                            currentLayerVM = new LayerVM(currentLayerM);
                            currentPageVM.Layers.Add(currentLayerVM);

                            yPosition = MarginTop + (float)block.SpacingBefore;
                            isFirstBlockOnPage = true;
                        }
                        else
                        {
                            // Start new page
                            pageNumber++;
                            currentPageM = CreateNewPage(pageNumber);
                            currentPageVM = new PageVM(currentPageM);
                            Pages.Add(currentPageVM);

                            currentLayerM = new LayerM { Order = 0 };
                            currentLayerVM = new LayerVM(currentLayerM);
                            currentPageVM.Layers.Add(currentLayerVM);

                            yPosition = MarginTop + (float)block.SpacingBefore;
                            isFirstBlockOnPage = true;
                        }
                    }

                    // Set block position
                    block.X = MarginLeft + (float)block.LeftIndentation;
                    block.Y = yPosition;
                    currentLayerVM.Blocks.Add(block);
                    yPosition += blockHeight + (float)block.SpacingAfter;
                    isFirstBlockOnPage = false;
                }
            }
        }

        /// <summary>
        /// Creates a new page model with default margins and sizes.
        /// </summary>
        private PageM CreateNewPage(int pageNumber)
        {
            return new PageM
            {
                PageNumber = pageNumber,
                Width = _paperWidth,
                Height = _paperHeight,
                MarginLeft = MarginLeft,
                MarginTop = MarginTop,
                MarginRight = MarginRight,
                MarginBottom = MarginBottom
            };
        }

        /// <summary>
        /// Creates a heading block for a section.
        /// </summary>
        private TextBlockVM CreateHeadingBlock(SectionVM section)
        {
            var headingRun = new RunVM
            {
                Text = section.Heading,
                FontSize = 24,
                Bold = true,
                FontFamily = "Arial",
                TextColor = new Color(0, 0, 0) // Black
            };

            var headingBlockModel = new TextBlockM
            {
                SpacingBefore = 20, // Adjust as needed
                SpacingAfter = 10,
                LeftIndentation = 0,
                RightIndentation = 0,
                LineSpacing = 1.2,
                Alignment = Alignment.Left,
                FirstLineIndentation = 0,
                HangingIndentation = 0
            };

            var headingBlock = new TextBlockVM(headingBlockModel);
            headingBlock.UpdateRuns(new[] { headingRun });

            return headingBlock;
        }

        /// <summary>
        /// Measures the height of a block to determine pagination.
        /// </summary>
        private float MeasureBlockHeight(BlockVM block, float maxWidth)
        {
            if (block is TextBlockVM textBlock)
            {
                return MeasureTextBlockHeight(textBlock, maxWidth);
            }
            else
            {
                // TODO: Implement measurement for other block types (e.g., images)
                // What Hasn't Been Implemented: Measurement logic for non-text blocks.
                // Next Steps: Implement measurement logic for ImageBlockVM and other custom blocks.
                return 50f; // Placeholder
            }
        }

        /// <summary>
        /// Measures the height of a text block based on its content and styling.
        /// </summary>
        private float MeasureTextBlockHeight(TextBlockVM textBlock, float maxWidth)
        {
            if (textBlock.Runs == null || !textBlock.Runs.Any())
                return 0f;

            // Aggregate all runs into a single list of words with associated run properties
            var wordsWithRuns = new List<(string Word, RunVM Run)>();
            foreach (var run in textBlock.Runs)
            {
                if (run == null || string.IsNullOrEmpty(run.Text))
                    continue;

                var words = run.Text.Split(' ');
                foreach (var word in words)
                {
                    if (!string.IsNullOrEmpty(word))
                        wordsWithRuns.Add((word + " ", run));
                }
            }

            if (!wordsWithRuns.Any())
                return 0f;

            // Initialize measurement variables
            float totalHeight = 0f;
            float currentLineWidth = 0f;
            float currentLineHeight = 0f;

            foreach (var (Word, Run) in wordsWithRuns)
            {
                using (var paint = new SKPaint())
                {
                    paint.TextSize = (float)Run.FontSize;
                    paint.Typeface = GetTypeface(Run);
                    paint.IsAntialias = true;

                    var metrics = paint.FontMetrics;
                    float lineHeight = (metrics.Descent - metrics.Ascent + metrics.Leading) * (float)textBlock.LineSpacing;

                    float wordWidth = paint.MeasureText(Word);

                    if (currentLineWidth + wordWidth > maxWidth)
                    {
                        // Move to next line
                        totalHeight += currentLineHeight;
                        currentLineWidth = wordWidth;
                        currentLineHeight = lineHeight;
                    }
                    else
                    {
                        currentLineWidth += wordWidth;
                        currentLineHeight = Math.Max(currentLineHeight, lineHeight);
                    }
                }
            }

            // Add the height of the last line
            totalHeight += currentLineHeight;

            return totalHeight;
        }

        /// <summary>
        /// Retrieves the appropriate typeface based on the run's styling.
        /// </summary>
        private SKTypeface GetTypeface(RunVM run)
        {
            var style = new SKFontStyle(
                run.Bold ? SKFontStyleWeight.Bold : SKFontStyleWeight.Normal,
                SKFontStyleWidth.Normal,
                run.Italic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright);

            return SKTypeface.FromFamilyName(run.FontFamily ?? "Arial", style);
        }
    }

    ///PLAN
    ///Since we want to do layer- and grid-based content layouts, do the following.
    ///Each page is repaginated into PageM (page model) and PageVM (page view model) lists in sequential order.
    ///Each page has a layer stack; layer stacks contain references to block objects, such as TextBlockVM, ImageBlockVM, etc., and these are shown in a nested list under each layer.
    ///Lists are ordered top to bottom, with left to right being preferred if there are any clashes.
    ///When adding document objects, such as lines to separate headers from the document's body, these are added as objects such as LineM/LineVM and so on.
    ///Make it modular to apply document- or page-wide styles for these objects, allowing for LaTeX-styled templating **AND** Word-styled WYSIWYG editing.
    ///Every inserted template object has default values and modified values, and both are stored so a user can reset back to defaults.
    ///Any changes to an object or layout forces a repagination.
    ///We can then also allow for a toolbar for objects, such as textblocks, images, lines, etc., whilst also allowing for programmatic insertion.
    ///If an image is on the same layer as text, then the text can dynamically wrap around it; else, it goes in front or behind text, depending on order in the layer stack.
    ///Enable virtualisation of each PageM/PageVM to allow for performant rendering, only rendering what we need at the time.
    ///Templates define default values, meanwhile edits define modified values.
    ///This would also allow for either per-page or per-document margins, depending on need; it would be best to have a margin UI, with drag controls, that have a toggle for either "document margins" or "page margins".
    ///To allow for caret editing, a return/new paragraph character creates a new textblock object (for user friendliness, an Enter-inserted textblock would use the layout values of the previous block as its modified values).
    ///Use the "Locked" bool in each blockvm to determine if a block's attributes can be modified, meaning only default values are used, and they can't have their layout or contents changed.
    /// 
    ///Would the editor and renderer benefit from being MVVM themselves? To allow for these objects?
    ///
    ///Also, for performance's sake, pages should repaginate from the end of the document up to the modified block if editing in the document body. Anything that comes before won't be affected by layout unless the paragraph needs to run onto the next page with the edits, surely?
}
using SkiaSharp;
using Lucy.ViewModels.Blocks;
using Lucy.ViewModels.Blocks.Base;
using Lucy.Extensions;
using System.Collections.Generic;
using System;
using System.Linq;
using Lucy.ViewModels.Internals;
using Lucy.Enumerators;
using Lucy.ViewModels.Editor;

namespace Lucy.Views.Editor
{
    /// <summary>
    /// Renders a page onto the SKCanvas, drawing all layers and blocks.
    /// </summary>
    public class PageRenderer
    {
        public void RenderPage(SKCanvas canvas, PageVM page)
        {
            DrawPageBackground(canvas, page);
            canvas.Save();

            try
            {
                SetContentClipping(canvas, page);
                DrawGridLines(canvas, page);

                foreach (var layer in page.Layers.OrderBy(l => l.Order))
                {
                    foreach (var block in layer.Blocks)
                    {
                        RenderBlock(canvas, block, page);
                    }
                }
            }
            finally
            {
                canvas.Restore();
            }

            DrawPageNumber(canvas, page);
        }

        private void RenderBlock(SKCanvas canvas, BlockVM block, PageVM page)
        {
            if (block is TextBlockVM textBlock)
            {
                RenderTextBlock(canvas, textBlock, page);
            }
            // TODO: Handle other block types (e.g., images)
            // What Hasn't Been Implemented: Rendering logic for non-text blocks.
            // Next Steps: Implement rendering methods for ImageBlockVM and other custom blocks.
        }

        private void RenderTextBlock(SKCanvas canvas, TextBlockVM textBlock, PageVM page)
        {
            float x = textBlock.X;
            float y = textBlock.Y;
            float maxWidth = page.Width - page.MarginLeft - page.MarginRight - (float)(textBlock.LeftIndentation + textBlock.RightIndentation);
            float currentX = x + (float)textBlock.FirstLineIndentation;
            float currentY = y;
            float totalHeight = MeasureTextBlockHeight(textBlock, maxWidth);

            // Background rectangle covers the entire text block height
            SKRect backgroundRect = new SKRect(
                x,
                y,
                x + maxWidth,
                y + totalHeight
            );

            // Background color
            if (textBlock.BackgroundColour != null)
            {
                using (var backgroundPaint = new SKPaint
                {
                    Color = textBlock.BackgroundColour.ToSKColor(),
                    Style = SKPaintStyle.Fill
                })
                {
                    canvas.DrawRect(backgroundRect, backgroundPaint);
                }
            }

            // Draw red bounding box around the entire text block.
            using (var borderPaint = new SKPaint
            {
                Color = SKColors.Red,
                Style = SKPaintStyle.Stroke,
                StrokeWidth = 2
            })
            {
                canvas.DrawRect(backgroundRect, borderPaint);
            }

            // Aggregate all runs into a single list of words with associated run properties
            var wordsWithRuns = new List<(string Word, RunVM Run)>();
            foreach (var run in textBlock.Runs)
            {
                if (run == null || string.IsNullOrEmpty(run.Text))
                    continue;

                var words = run.Text.Split(' ');
                foreach (var word in words)
                {
                    if (!string.IsNullOrEmpty(word))
                        wordsWithRuns.Add((word + " ", run));
                }
            }

            if (!wordsWithRuns.Any())
                return;

            // Initialize rendering variables
            currentX = x + (float)textBlock.FirstLineIndentation;
            currentY = y;
            float lineHeight = 0f;
            float runAscent = 0f;
            float runDescent = 0f;

            foreach (var (Word, Run) in wordsWithRuns)
            {
                using (var paint = new SKPaint())
                {
                    paint.TextSize = (float)Run.FontSize;
                    paint.Color = Run.TextColor.ToSKColor();
                    paint.IsAntialias = true;
                    paint.Typeface = GetTypeface(Run);

                    // Text alignment
                    switch (textBlock.Alignment)
                    {
                        case Alignment.Left:
                            paint.TextAlign = SKTextAlign.Left;
                            break;
                        case Alignment.Centre:
                            paint.TextAlign = SKTextAlign.Center;
                            break;
                        case Alignment.Right:
                            paint.TextAlign = SKTextAlign.Right;
                            break;
                            // Justify alignment can be implemented with additional logic
                    }

                    var metrics = paint.FontMetrics;
                    lineHeight = (metrics.Descent - metrics.Ascent + metrics.Leading) * (float)textBlock.LineSpacing;
                    runAscent = metrics.Ascent;
                    runDescent = metrics.Descent;

                    float wordWidth = paint.MeasureText(Word);

                    // Check if the word fits in the current line
                    if (currentX + wordWidth > x + maxWidth)
                    {
                        // Move to next line
                        currentY += lineHeight;
                        currentX = x + (float)textBlock.HangingIndentation;
                    }

                    // Adjust X position based on alignment
                    float textX = currentX;
                    if (paint.TextAlign == SKTextAlign.Center)
                    {
                        textX = x + maxWidth / 2;
                    }
                    else if (paint.TextAlign == SKTextAlign.Right)
                    {
                        textX = x + maxWidth;
                    }

                    // Draw the text at the current position
                    float baselineY = currentY - runAscent;
                    canvas.DrawText(Word, textX, baselineY, paint);

                    float textWidth = paint.MeasureText(Word);

                    /*
                     * Calculate the bounding rectangle for the run.
                     * This rectangle surrounds the currently drawn text segment.
                     */
                    float rectX = paint.TextAlign == SKTextAlign.Left ? textX :
                                  paint.TextAlign == SKTextAlign.Center ? textX - (textWidth / 2) :
                                  textX - textWidth;

                    float rectY = baselineY + runAscent;
                    float rectBottom = baselineY + runDescent;
                    float rectWidth = textWidth;
                    float rectHeight = runDescent - runAscent;

                    var runRect = new SKRect(rectX, rectY, rectX + rectWidth, rectBottom);

                    /*
                     * Draw green bounding box around the run.
                     * This helps in visually identifying the boundaries of each text run.
                     */
                    using (var runBorderPaint = new SKPaint
                    {
                        Color = SKColors.Green,
                        Style = SKPaintStyle.Stroke,
                        StrokeWidth = 1
                    })
                    {
                        canvas.DrawRect(runRect, runBorderPaint);
                    }

                    currentX += wordWidth;
                }
            }
        }

        /// <summary>
        /// Retrieves the appropriate typeface based on the run's styling.
        /// </summary>
        private SKTypeface GetTypeface(RunVM run)
        {
            var style = new SKFontStyle(
                run.Bold ? SKFontStyleWeight.Bold : SKFontStyleWeight.Normal,
                SKFontStyleWidth.Normal,
                run.Italic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright);

            return SKTypeface.FromFamilyName(run.FontFamily ?? "Arial", style);
        }

        /// <summary>
        /// Draws the background and border of the page.
        /// </summary>
        private void DrawPageBackground(SKCanvas canvas, PageVM page)
        {
            var pageRect = new SKRect(0, 0, page.Width, page.Height);

            using (var pagePaint = new SKPaint
            {
                Color = SKColors.White,
                Style = SKPaintStyle.Fill
            })
            {
                canvas.DrawRect(pageRect, pagePaint);
            }

            using (var borderPaint = new SKPaint
            {
                Color = SKColors.Black,
                Style = SKPaintStyle.Stroke,
                StrokeWidth = 2
            })
            {
                canvas.DrawRect(pageRect, borderPaint);
            }
        }

        /// <summary>
        /// Sets the clipping region to the content area within the margins.
        /// </summary>
        private void SetContentClipping(SKCanvas canvas, PageVM page)
        {
            var clipRect = new SKRect(
                page.MarginLeft,
                page.MarginTop,
                page.Width - page.MarginRight,
                page.Height - page.MarginBottom
            );

            canvas.ClipRect(clipRect, SKClipOperation.Intersect, antialias: true);
        }

        /// <summary>
        /// Draws grid lines within the content area for alignment assistance.
        /// </summary>
        private void DrawGridLines(SKCanvas canvas, PageVM page)
        {
            // Grid settings
            const int DesiredColumns = 10;
            const int DesiredRows = 10;

            float contentLeft = page.MarginLeft;
            float contentRight = page.Width - page.MarginRight;
            float contentTop = page.MarginTop;
            float contentBottom = page.Height - page.MarginBottom;

            float contentWidth = contentRight - contentLeft;
            float contentHeight = contentBottom - contentTop;

            float spacingX = contentWidth / DesiredColumns;
            float spacingY = contentHeight / DesiredRows;

            using (var gridPaint = new SKPaint
            {
                Color = SKColors.LightGray,
                StrokeWidth = 1,
                Style = SKPaintStyle.Stroke,
                IsAntialias = true
            })
            {
                // Draw vertical grid lines
                for (int i = 0; i <= DesiredColumns; i++)
                {
                    float x = contentLeft + i * spacingX;
                    canvas.DrawLine(x, contentTop, x, contentBottom, gridPaint);
                }

                // Draw horizontal grid lines
                for (int i = 0; i <= DesiredRows; i++)
                {
                    float y = contentTop + i * spacingY;
                    canvas.DrawLine(contentLeft, y, contentRight, y, gridPaint);
                }
            }
        }

        /// <summary>
        /// Draws the page number at the bottom center of the page.
        /// </summary>
        private void DrawPageNumber(SKCanvas canvas, PageVM page)
        {
            using (var paint = new SKPaint())
            {
                paint.TextSize = 14;
                paint.Color = SKColors.Gray;
                paint.IsAntialias = true;
                paint.Typeface = SKTypeface.FromFamilyName("Arial");
                paint.TextAlign = SKTextAlign.Center;

                string pageNumberText = $"Page {page.PageNumber}";
                float x = page.Width / 2;
                float y = page.Height - page.MarginBottom / 2 - paint.FontMetrics.Descent;

                canvas.DrawText(pageNumberText, x, y, paint);
            }
        }

        /// <summary>
        /// Measures the height of a text block for rendering.
        /// </summary>
        private float MeasureTextBlockHeight(TextBlockVM textBlock, float maxWidth)
        {
            if (textBlock.Runs == null || !textBlock.Runs.Any())
                return 0f;

            // Aggregate all runs into a single list of words with associated run properties
            var wordsWithRuns = new List<(string Word, RunVM Run)>();
            foreach (var run in textBlock.Runs)
            {
                if (run == null || string.IsNullOrEmpty(run.Text))
                    continue;

                var words = run.Text.Split(' ');
                foreach (var word in words)
                {
                    if (!string.IsNullOrEmpty(word))
                        wordsWithRuns.Add((word + " ", run));
                }
            }

            if (!wordsWithRuns.Any())
                return 0f;

            // Initialize measurement variables
            float totalHeight = 0f;
            float currentLineWidth = 0f;
            float currentLineHeight = 0f;

            foreach (var (Word, Run) in wordsWithRuns)
            {
                using (var paint = new SKPaint())
                {
                    paint.TextSize = (float)Run.FontSize;
                    paint.Typeface = GetTypeface(Run);
                    paint.IsAntialias = true;

                    var metrics = paint.FontMetrics;
                    float lineHeight = (metrics.Descent - metrics.Ascent + metrics.Leading) * (float)textBlock.LineSpacing;

                    float wordWidth = paint.MeasureText(Word);

                    if (currentLineWidth + wordWidth > maxWidth)
                    {
                        // Move to next line
                        totalHeight += currentLineHeight;
                        currentLineWidth = wordWidth;
                        currentLineHeight = lineHeight;
                    }
                    else
                    {
                        currentLineWidth += wordWidth;
                        currentLineHeight = Math.Max(currentLineHeight, lineHeight);
                    }
                }
            }

            // Add the height of the last line
            totalHeight += currentLineHeight;

            return totalHeight;
        }
    }
}
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Lucy.Models;
using Lucy.ViewModels.Blocks;
using Lucy.ViewModels.Base;
using Lucy.ViewModels.Blocks.Base;
using Lucy.Models.Blocks;
using Lucy.ViewModels.Editor;
using DocumentFormat.OpenXml.Drawing.Charts;

namespace Lucy.ViewModels
{
    public class DocumentVM : BaseVM
    {
        private DocumentM Model;

        private ObservableCollection<SectionVM> _sections;
        public ObservableCollection<SectionVM> Sections
        {
            get => _sections;
            set
            {
                if (value == null) throw new ArgumentException("View-bound collections should not be null.");

                if (SetProperty(ref _sections, value)) 
                {
                    Model.Sections = _sections.Select(s => s.GetModel()).ToList();
                }
            }
        }

        private string _filename;
        public string Filename
        {
            get => _filename;
            set
            {
                if (string.IsNullOrEmpty(value)) throw new ArgumentException("Filenames cannot be empty.");

                if (SetProperty(ref _filename, value))
                {
                    Model.Filename = value;
                }
            }
        }

        private string _title;
        public string Title
        {
            get => _title;
            set
            {
                //if (string.IsNullOrEmpty(value)) throw new ArgumentException("Titles cannot be empty.");

                if (SetProperty(ref _title, value))
                {
                    Model.Title = value;
                }
            }
        }

        private string _author;
        public string Author
        {
            get => _author;
            set
            {
                //if (string.IsNullOrEmpty(value)) throw new ArgumentException("Authors cannot be empty.");

                if (SetProperty(ref _author, value))
                {
                    Model.Author = value;
                }
            }
        }

        private string _subject;
        public string Subject
        {
            get => _subject;
            set
            {
                //if (string.IsNullOrEmpty(value)) throw new ArgumentException("Subjects cannot be empty.");

                if (SetProperty(ref _subject, value))
                {
                    Model.Subject = value;
                }
            }
        }

        private ObservableCollection<string> _keywords;
        public ObservableCollection<string> Keywords
        {
            get => _keywords;
            set
            {
                if (value == null) value = new ObservableCollection<string>();

                if (_keywords != value)
                {
                    _keywords = value;
                    Model.Keywords = _keywords.ToList();
                    OnPropertyChanged();
                }
            }
        }

        private string _themeColor;
        public string ThemeColor
        {
            get => _themeColor;
            set
            {
                if (string.IsNullOrEmpty(value)) throw new ArgumentException("Theme Colours cannot be empty.");

                if (SetProperty(ref _themeColor, value))
                {
                    Model.ThemeColor = value;
                }
            }
        }

        private string _themeFont;
        public string ThemeFont
        {
            get => _themeFont;
            set
            {
                if (string.IsNullOrEmpty(value)) throw new ArgumentException("Theme Fonts cannot be empty.");

                if (SetProperty(ref _themeFont, value))
                {
                    Model.ThemeFont = value;
                }
            }
        }

        private string _header;
        public string Header
        {
            get => _header;
            set
            {
                if (SetProperty(ref _header, value))
                {
                    Model.Header = value;
                }
            }
        }

        private string _footer;
        public string Footer
        {
            get => _footer;
            set
            {
                if (SetProperty(ref _footer, value))
                {
                    Model.Footer = value;
                }
            }
        }

        private bool _pageBreakBeforeSection;
        public bool PageBreakBeforeSection
        {
            get => _pageBreakBeforeSection;
            set
            {
                if (SetProperty(ref _pageBreakBeforeSection, value))
                {
                    Model.PageBreakBeforeSection = value;
                }
            }
        }

        private bool _isDirty;
        public bool IsDirty
        {
            get => _isDirty;
            set
            {
                if (SetProperty(ref _isDirty, value))
                {
                    Model.IsDirty = value;
                }
            }
        }

        /// <summary>
        /// we should move this and activeblock to applicationvm
        /// </summary>
        private SectionVM _activeSection;
        public SectionVM ActiveSection
        {
            get => _activeSection;
            set
            {
                if (SetProperty(ref _activeSection, value))
                {
                    if (ActiveSection?.Blocks.Any() == true)
                    {
                        ActiveBlock = ActiveSection.Blocks.First();
                    }
                    else
                    {
                        ActiveBlock = null;
                    }
                }
            }
        }

        private BlockVM _activeBlock;
        public BlockVM ActiveBlock
        {
            get => _activeBlock;
            set => SetProperty(ref _activeBlock, value);
        }

        public DocumentVM()
        {
            Model = new DocumentM();

            Sections = new ObservableCollection<SectionVM>();
        }

        public DocumentVM(DocumentM model)
        {
            Model = model ?? throw new ArgumentNullException(nameof(model));

            _filename = Model.Filename;
            _title = Model.Title;
            _author = Model.Author;
            _subject = Model.Subject;
            _keywords = new ObservableCollection<string>(Model.Keywords);
            _themeColor = Model.ThemeColor;
            _themeFont = Model.ThemeFont;
            _header = Model.Header;
            _footer = Model.Footer;
            _pageBreakBeforeSection = Model.PageBreakBeforeSection;
            _isDirty = Model.IsDirty;

            var sections = Model.Sections ?? new List<SectionM>();
            Sections = new ObservableCollection<SectionVM>(sections.Select(sectionModel => new SectionVM(sectionModel)));
        }

        // Method to initialize an empty document
        public void InitializeEmptyDocument()
        {
            if (Sections.Count == 0)
            {
                var section = new SectionVM(new SectionM { Heading = "New Section" });
                var textBlock = new TextBlockVM(new TextBlockM());
                section.AddBlock(textBlock);
                Sections.Add(section);
                ActiveSection = section;
                ActiveBlock = section.Blocks.First();
            }
        }

        // Method to add a section
        public void AddSection(SectionVM section)
        {
            Sections.Add(section);
            Model.Sections.Add(section.GetModel());
            IsDirty = true;
        }

        // Method to remove a section
        public void RemoveSection(SectionVM section)
        {
            Sections.Remove(section);
            Model.Sections.Remove(section.GetModel());
            if (ActiveSection == section)
            {
                ActiveSection = Sections.FirstOrDefault();
                ActiveBlock = ActiveSection?.Blocks.FirstOrDefault();
            }
            IsDirty = true;
        }

        // Method to add a block to the active section
        public void AddBlockToActiveSection(BlockVM block)
        {
            ActiveSection?.AddBlock(block);
            IsDirty = true;
        }

        // Method to get all text blocks
        public List<TextBlockVM> AllTextBlocks()
        {
            return Sections.SelectMany(section => section.Blocks.OfType<TextBlockVM>()).ToList();
        }

        // Internal method to get the Model (if needed elsewhere in the ViewModel layer)
        internal DocumentM GetModel()
        {
            return Model;
        }
    }
}using Lucy.Controls;
using Lucy.Enumerators;
using Lucy.Models.Blocks;
using Lucy.Models.Internals;
using Lucy.ViewModels.Blocks.Base;
using Lucy.ViewModels.Internals;
using System;
using System.Collections.ObjectModel;
using System.Linq;

namespace Lucy.ViewModels.Blocks
{
    public class TextBlockVM : BlockVM
    {
        public ObservableCollection<RunVM> Runs { get; private set; }

        private bool _hidden;
        public bool Hidden
        {
            get => _hidden;
            set
            {
                if (SetProperty(ref _hidden, value))
                {
                    _hidden = value;
                }
            }
        }

        private Color _backgroundColour;
        public Color BackgroundColour
        {
            get => _backgroundColour;
            set
            {
                if (SetProperty(ref _backgroundColour, value))
                {
                    ((TextBlockM)Model).BackgroundColour = value;
                }
            }
        }

        private double _lineSpacing;
        public double LineSpacing
        {
            get => _lineSpacing;
            set
            {
                if (value <= 0) throw new ArgumentException("Lines must retain space between one another.");

                if (SetProperty(ref _lineSpacing, value))
                {
                    ((TextBlockM)Model).LineSpacing = value;
                }
            }
        }

        private double _firstLineIndentation;
        public double FirstLineIndentation
        {
            get => _firstLineIndentation;
            set
            {
                if (SetProperty(ref _firstLineIndentation, value))
                {
                    ((TextBlockM)Model).FirstLineIndentation = value;
                }
            }
        }

        private double _hangingIndentation;
        public double HangingIndentation
        {
            get => _hangingIndentation;
            set
            {
                if (SetProperty(ref _hangingIndentation, value))
                {
                    ((TextBlockM)Model).HangingIndentation = value;
                }
            }
        }

        private TextDirection _direction;
        public TextDirection Direction
        {
            get => _direction;
            set
            {
                if (SetProperty(ref _direction, value))
                {
                    ((TextBlockM)Model).Direction = value;
                }
            }
        }

        public override bool Render { get; protected set; } = true;

        // Constructors
        public TextBlockVM() : this(new TextBlockM())
        {
            Runs = new ObservableCollection<RunVM>();
        }

        public TextBlockVM(TextBlockM model) : base(model)
        {
            if (model == null)
                throw new ArgumentNullException(nameof(model), "Model cannot be null.");

            _backgroundColour = model.BackgroundColour;
            _lineSpacing = model.LineSpacing;
            _firstLineIndentation = model.FirstLineIndentation;
            _hangingIndentation = model.HangingIndentation;
            _direction = model.Direction;

            var runs = model.Runs ?? new List<RunM>();
            Runs = new ObservableCollection<RunVM>
            (
                runs.Select(runModel => new RunVM(runModel))
            );
        }

        // Methods to manage the Runs collection
        public void AddRun(RunVM run)
        {
            Runs.Add(run);
            ((TextBlockM)Model).Runs.Add(run.GetModel());
        }

        public void RemoveRun(RunVM run)
        {
            Runs.Remove(run);
            ((TextBlockM)Model).Runs.Remove(run.GetModel());
        }

        public void ClearRuns()
        {
            Runs.Clear();
            ((TextBlockM)Model).Runs.Clear();
        }

        public void UpdateRuns(IEnumerable<RunVM> runs)
        {
            Runs = new ObservableCollection<RunVM>(runs);
            // Update the Model's Runs
            ((TextBlockM)Model).Runs = runs.Select(r => r.GetModel()).ToList();
            OnPropertyChanged(nameof(Runs));
        }

        public string Text
        {
            get => string.Join("", Runs.Select(r => r.Text));
            set
            {
                if (Text != value)
                {
                    ClearRuns();
                    var runModel = new RunM { Text = value };
                    var runVM = new RunVM(runModel);
                    Runs.Add(runVM);
                    ((TextBlockM)Model).Runs.Add(runModel);
                    OnPropertyChanged();
                }
            }
        }
    }
}using Lucy.Models;
using Lucy.Models.Blocks;
using Lucy.ViewModels.Base;
using Lucy.ViewModels.Blocks;
using Lucy.ViewModels.Blocks.Base;
using System;
using System.Collections.ObjectModel;
using System.Linq;

namespace Lucy.ViewModels
{
    public class SectionVM : BaseVM
    {
        private SectionM Model;

        public ObservableCollection<BlockVM> Blocks { get; private set; }
        public ObservableCollection<SectionVM> Subsections { get; private set; }

        private string _heading;
        public string Heading
        {
            get => _heading;
            set
            {
                ///implement the label types in this class so we can have different tags for different formats, e.g., "scenes must always have (x)"
                if (string.IsNullOrEmpty(value)) throw new ArgumentException("Sections must always have headings.");

                if (SetProperty(ref _heading, value))
                {
                    Model.Heading = value;
                }
            }
        }

        public SectionVM()
        {
            Model = new SectionM();
        }

        public SectionVM(SectionM section)
        {
            Model = section ?? throw new ArgumentNullException(nameof(section));

            _heading = Model.Heading;

            Blocks = new ObservableCollection<BlockVM>();
            foreach (var block in Model.Blocks)
            {
                if (block is TextBlockM textBlockM)
                {
                    Blocks.Add(new TextBlockVM(textBlockM));
                }
                else if (block is ImageBlockM imageBlockM)
                {
                    Blocks.Add(new ImageBlockVM(imageBlockM));
                }
            }

            var subsections = Model.Subsections ?? new List<SectionM>();
            Subsections = new ObservableCollection<SectionVM>(subsections.Select(subsectionM => new SectionVM(subsectionM)));
        }

        public void AddBlock(BlockVM block)
        {
            Blocks.Add(block);
            Model.AddBlock(block.GetModel());
        }

        public void RemoveBlock(BlockVM block)
        {
            Blocks.Remove(block);
            Model.RemoveBlock(block.GetModel());
        }

        public void AddSubsection(SectionVM subsection)
        {
            Subsections.Add(subsection);
            Model.Subsections.Add(subsection.GetModel());
        }

        public void RemoveSubsection(SectionVM subsection)
        {
            Subsections.Remove(subsection);
            Model.Subsections.Remove(subsection.GetModel());
        }

        // Insert a section before this section in its parent context
        public void InsertBefore(SectionVM newSection, object parent)
        {
            if (parent is SectionVM parentSection)
            {
                // Insert within a parent section
                var parentSections = parentSection.Subsections;
                int index = parentSections.IndexOf(this);
                if (index >= 0)
                {
                    parentSections.Insert(index, newSection);
                    parentSection.Model.Subsections.Insert(index, newSection.GetModel());
                }
            }
            else if (parent is DocumentVM document)
            {
                // Insert within the document's main section list
                var sections = document.Sections;
                int index = sections.IndexOf(this);
                if (index >= 0)
                {
                    sections.Insert(index, newSection);
                    document.GetModel().Sections.Insert(index, newSection.GetModel());
                }
            }
        }

        // Insert a section after this section in its parent context
        public void InsertAfter(SectionVM newSection, object parent)
        {
            if (parent is SectionVM parentSection)
            {
                // Insert within a parent section
                var parentSections = parentSection.Subsections;
                int index = parentSections.IndexOf(this);
                if (index >= 0)
                {
                    parentSections.Insert(index + 1, newSection);
                    parentSection.Model.Subsections.Insert(index + 1, newSection.GetModel());
                }
            }
            else if (parent is DocumentVM document)
            {
                // Insert within the document's main section list
                var sections = document.Sections;
                int index = sections.IndexOf(this);
                if (index >= 0)
                {
                    sections.Insert(index + 1, newSection);
                    document.GetModel().Sections.Insert(index + 1, newSection.GetModel());
                }
            }
        }

        // Internal method to get the Model (if needed elsewhere in the ViewModel layer)
        internal SectionM GetModel()
        {
            return Model;
        }
    }
}using Lucy.Enumerators;
using Lucy.Models.Blocks;
using Lucy.ViewModels.Base;
using System;

namespace Lucy.ViewModels.Blocks.Base
{
    public abstract class BlockVM : BaseVM
    {
        // Make Model protected
        protected BlockM Model { get; }

        public abstract bool Render { get; protected set; }
        public float X { get; set; }
        public float Y { get; set; }

        protected BlockVM(BlockM model)
        {
            Model = model ?? throw new ArgumentNullException(nameof(model), "Model cannot be null.");

            _spacingBefore = Model.SpacingBefore;
            _spacingAfter = Model.SpacingAfter;
            _leftIndentation = Model.LeftIndentation;
            _rightIndentation = Model.RightIndentation;
            _keepWithNext = Model.KeepWithNext;
            _keepLinesTogether = Model.KeepLinesTogether;
            _alignment = Model.Alignment;
        }

        // Backing fields and properties
        private double _spacingBefore;
        public double SpacingBefore
        {
            get => _spacingBefore;
            set
            {
                if (value < 0) throw new ArgumentException("Spacing cannot be a negative value.");

                if (SetProperty(ref _spacingBefore, value))
                {
                    Model.SpacingBefore = value;
                }
            }
        }

        private double _spacingAfter;
        public double SpacingAfter
        {
            get => _spacingAfter;
            set
            {
                if (value < 0) throw new ArgumentException("Spacing cannot be a negative value.");

                if (SetProperty(ref _spacingAfter, value))
                {
                    Model.SpacingAfter = value;
                }
            }
        }

        private double _leftIndentation;
        public double LeftIndentation
        {
            get => _leftIndentation;
            set
            {
                if (value < 0) throw new ArgumentException("Indentation cannot be a negative value.");

                if (SetProperty(ref _leftIndentation, value))
                {
                    Model.LeftIndentation = value;
                }
            }
        }

        private double _rightIndentation;
        public double RightIndentation
        {
            get => _rightIndentation;
            set
            {
                if (value < 0) throw new ArgumentException("Indentation cannot be a negative value.");

                if (SetProperty(ref _rightIndentation, value))
                {
                    Model.RightIndentation = value;
                }
            }
        }

        private bool _keepWithNext;
        public bool KeepWithNext
        {
            get => _keepWithNext;
            set
            {
                if (SetProperty(ref _keepWithNext, value))
                {
                    Model.KeepWithNext = value;
                }
            }
        }

        private bool _keepLinesTogether;
        public bool KeepLinesTogether
        {
            get => _keepLinesTogether;
            set
            {
                if (SetProperty(ref _keepLinesTogether, value))
                {
                    Model.KeepLinesTogether = value;
                }
            }
        }

        private Alignment _alignment;
        public Alignment Alignment
        {
            get => _alignment;
            set
            {
                if (SetProperty(ref _alignment, value))
                {
                    Model.Alignment = value;
                }
            }
        }

        internal BlockM GetModel()
        {
            return Model;
        }
    }
}using Lucy.Models.Internals;
using Lucy.ViewModels.Base;
using System;

namespace Lucy.ViewModels.Internals
{
    public class RunVM : BaseVM
    {
        private RunM Model { get; }

        private string _text;
        public string Text
        {
            get => _text;
            set
            {
                if (SetProperty(ref _text, value))
                {
                    Model.Text = value;
                }
            }
        }

        private string _fontFamily;
        public string FontFamily
        {
            get => _fontFamily;
            set
            {
                if (string.IsNullOrEmpty(value)) throw new ArgumentException("Text must always have a font.");

                if (SetProperty(ref _fontFamily, value))
                {
                    Model.FontFamily = value;
                }
            }
        }

        private double _fontSize;
        public double FontSize
        {
            get => _fontSize;
            set
            {
                if (value < 0) throw new ArgumentException("Text must always be visible.");

                if (SetProperty(ref _fontSize, value))
                {
                    Model.FontSize = value;
                }
            }
        }

        private bool _bold;
        public bool Bold
        {
            get => _bold;
            set
            {
                if (SetProperty(ref _bold, value))
                {
                    Model.Bold = value;
                }
            }
        }

        private bool _italic;
        public bool Italic
        {
            get => _italic;
            set
            {
                if (SetProperty(ref _italic, value))
                {
                    Model.Italic = value;
                }
            }
        }

        private bool _underline;
        public bool Underline
        {
            get => _underline;
            set
            {
                if (SetProperty(ref _underline, value))
                {
                    Model.Underline = value;
                }
            }
        }

        private Color _textColor;
        public Color TextColor
        {
            get => _textColor;
            set
            {
                if (SetProperty(ref _textColor, value))
                {
                    Model.TextColor = value;
                }
            }
        }

        private bool _selected;
        public bool Selected
        {
            get => _selected;
            set => SetProperty(ref _selected, value);
        }

        public RunVM() : this(new RunM())
        {

        }

        public RunVM(RunM model)
        {
            Model = model ?? throw new ArgumentNullException(nameof(model), "Model cannot be null.");

            _text = Model.Text;
            _fontFamily = Model.FontFamily;
            _fontSize = Model.FontSize;
            _bold = Model.Bold;
            _italic = Model.Italic;
            _underline = Model.Underline;
            _textColor = Model.TextColor;
        }

        internal RunM GetModel()
        {
            return Model;
        }
    }
}using System.Collections.ObjectModel;
using Lucy.ViewModels.Base;
using Lucy.ViewModels.Blocks.Base;
using Lucy.Models.Editor;

namespace Lucy.ViewModels.Editor
{
    /// <summary>
    /// ViewModel for a layer within a page, containing blocks of content.
    /// </summary>
    public class LayerVM : BaseVM
    {
        private LayerM Model { get; }

        public ObservableCollection<BlockVM> Blocks { get; set; }

        private int _order;
        public int Order
        {
            get => _order;
            set
            {
                if (value < 0) throw new ArgumentException("Layer counts must always be equal to or exceed zero.");

                if (SetProperty(ref _order, value))
                {
                    Model.Order = value;
                }
            }
        }

        public LayerVM(LayerM model)
        {
            Model = model;

            _order = Model.Order;

            var blocks = Model.Blocks ?? new List<BlockVM>();
            Blocks = new ObservableCollection<BlockVM>(blocks);
        }
    }
}using System;
using System.Collections.ObjectModel;
using System.Linq;
using Lucy.ViewModels.Base;
using Lucy.Models.Editor;

namespace Lucy.ViewModels.Editor
{
    /// <summary>
    /// ViewModel for a page in the editor, used for data binding in the UI.
    /// </summary>
    public class PageVM : BaseVM
    {
        public PageM Model { get; private set; }
        public ObservableCollection<LayerVM> Layers { get; set; }

        private int _pageNumber;
        public int PageNumber
        {
            get => _pageNumber;
            set
            {
                if (value <= 0) throw new ArgumentException("Page numbers must always exceed zero.");

                if (SetProperty(ref _pageNumber, value))
                {
                    Model.PageNumber = value;
                }
            }
        }

        private float _width;
        public float Width
        {
            get => _width;
            set
            {
                if (value <= 0) throw new ArgumentException("Width values must always exceed zero.");

                if (SetProperty(ref _width, value))
                {
                    Model.Width = value;
                }
            }
        }

        private float _height;
        public float Height
        {
            get => _height;
            set
            {
                if (value <= 0) throw new ArgumentException("Height values must always exceed zero.");

                if (SetProperty(ref _height, value))
                {
                    Model.Height = value;
                }
            }
        }

        private float _marginLeft;
        public float MarginLeft
        {
            get => _marginLeft;
            set
            {
                if (value < 0) throw new ArgumentException("Margin values can never be negative.");

                if (SetProperty(ref _marginLeft, value))
                {
                    Model.MarginLeft = value;
                }
            }
        }

        private float _marginTop;
        public float MarginTop
        {
            get => _marginTop;
            set
            {
                if (value < 0) throw new ArgumentException("Margin values can never be negative.");

                if (SetProperty(ref _marginTop, value))
                {
                    Model.MarginTop = value;
                }
            }
        }

        private float _marginRight;
        public float MarginRight
        {
            get => _marginRight;
            set
            {
                if (value < 0) throw new ArgumentException("Margin values can never be negative.");

                if (SetProperty(ref _marginRight, value))
                {
                    Model.MarginRight = value;
                }
            }
        }

        private float _marginBottom;
        public float MarginBottom
        {
            get => _marginBottom;
            set
            {
                if (value < 0) throw new ArgumentException("Margin values can never be negative.");

                if (SetProperty(ref _marginBottom, value))
                {
                    Model.MarginBottom = value;
                }
            }
        }

        public PageVM(PageM model)
        {
            Model = model ?? throw new ArgumentNullException(nameof(model));

            PageNumber = Model.PageNumber;
            Width = Model.Width;
            Height = Model.Height;
            MarginLeft = Model.MarginLeft;
            MarginTop = Model.MarginTop;
            MarginRight = Model.MarginRight;
            MarginBottom = Model.MarginBottom;

            Layers = new ObservableCollection<LayerVM>(Model.Layers.Select(layer => new LayerVM(layer)));
        }
    }
}

Analyse this code for my NET MAUI 8 MVVM word processor.