using Microsoft.Maui.Graphics;
using Lucy.ViewModels.Editor;

namespace Lucy.Views.Editor
{
    public class PageDrawable : IDrawable
    {
        private readonly PageVM _page;

        public PageDrawable(PageVM page)
        {
            _page = page;
        }

        public void Draw(ICanvas canvas, RectF dirtyRect)
        {
            var pageRenderer = new PageRenderer();
            pageRenderer.RenderPage(canvas, _page);
        }
    }
}using Lucy.ViewModels.Blocks;
using Lucy.ViewModels.Blocks.Base;
using System.Collections.Generic;
using System;
using System.Linq;
using Lucy.ViewModels.Internals;
using Lucy.Enumerators;
using Lucy.ViewModels.Editor;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Text; // Add this using directive

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

            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.RestoreState();
            }

            DrawPageNumber(canvas, page);
        }

        private void RenderBlock(ICanvas 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(ICanvas 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);

            // Create a TextLayout
            var textLayout = new Microsoft.Maui.Graphics.Text.TextLayout
            {
                MaxWidth = maxWidth,
                MaxHeight = page.Height - y - page.MarginBottom,
                LineHeight = (float)textBlock.LineSpacing,
                HorizontalAlignment = GetHorizontalAlignment(textBlock.Alignment)
            };

            // Build the text with formatting
            foreach (var run in textBlock.Runs)
            {
                if (run == null || string.IsNullOrEmpty(run.Text))
                    continue;

                var fontAttributes = GetFontAttributes(run);
                var font = new Microsoft.Maui.Graphics.Font(run.FontFamily ?? "Arial", (int)run.FontSize, fontAttributes);

                textLayout.Append(run.Text, font, run.TextColor);
            }

            // Background rectangle covers the entire text block height
            SizeF textSize = textLayout.GetSize();

            RectF backgroundRect = new RectF(
                x,
                y,
                x + maxWidth,
                y + textSize.Height
            );

            // Background color
            if (textBlock.BackgroundColour != null)
            {
                canvas.FillColor = textBlock.BackgroundColour; // Assuming it's already a Microsoft.Maui.Graphics.Color
                canvas.FillRectangle(backgroundRect);
            }

            // Draw red bounding box around the entire text block.
            canvas.StrokeColor = Colors.Red;
            canvas.StrokeSize = 2;
            canvas.DrawRectangle(backgroundRect);

            // Draw the text layout
            canvas.SaveState();
            canvas.Translate(x, y);
            textLayout.Draw(canvas, new PointF(0, 0));
            canvas.RestoreState();
        }

        private FontStyleType GetFontAttributes(RunVM run)
        {
            if (run.Bold && run.Italic)
                return FontStyleType.Oblique; // Use Oblique as BoldItalic is not available
            else if (run.Bold)
                return FontStyleType.Normal;
            else if (run.Italic)
                return FontStyleType.Italic;
            else
                return FontStyleType.Normal;
        }

        private HorizontalAlignment GetHorizontalAlignment(Alignment alignment)
        {
            return alignment switch
            {
                Alignment.Left => HorizontalAlignment.Left,
                Alignment.Centre => HorizontalAlignment.Center,
                Alignment.Right => HorizontalAlignment.Right,
                _ => HorizontalAlignment.Left,
            };
        }

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

            canvas.FillColor = Colors.White;
            canvas.FillRectangle(pageRect);

            canvas.StrokeColor = Colors.Black;
            canvas.StrokeSize = 2;
            canvas.DrawRectangle(pageRect);
        }

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

        /// <summary>
        /// Draws grid lines within the content area for alignment assistance.
        /// </summary>
        private void DrawGridLines(ICanvas 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;

            canvas.StrokeColor = Colors.LightGray;
            canvas.StrokeSize = 1;

            // Draw vertical grid lines
            for (int i = 0; i <= DesiredColumns; i++)
            {
                float x = contentLeft + i * spacingX;
                canvas.DrawLine(x, contentTop, x, contentBottom);
            }

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

        /// <summary>
        /// Draws the page number at the bottom center of the page.
        /// </summary>
        private void DrawPageNumber(ICanvas canvas, PageVM page)
        {
            var font = new Microsoft.Maui.Graphics.Font("Arial", 14);
            canvas.Font = font;
            canvas.FontColor = Colors.Gray;

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

            canvas.DrawString(pageNumberText, x, y, HorizontalAlignment.Center);
        }

        ///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 System.Collections.Generic;
using System.Linq;
using Lucy.ViewModels;
using Lucy.ViewModels.Blocks.Base;
using System;
using Lucy.ViewModels.Blocks;
using Lucy.ViewModels.Internals;
using Lucy.Enumerators;
using Lucy.Models.Blocks;
using Lucy.ViewModels.Editor;
using Lucy.Models.Editor;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Text; // Add this using directive

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 = Colors.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;

            // Create a TextLayout
            var textLayout = new Microsoft.Maui.Graphics.Text.TextLayout
            {
                MaxWidth = maxWidth,
                MaxHeight = double.PositiveInfinity,
                LineHeight = (float)textBlock.LineSpacing,
                HorizontalAlignment = GetHorizontalAlignment(textBlock.Alignment)
            };

            // Build the text with formatting
            foreach (var run in textBlock.Runs)
            {
                if (run == null || string.IsNullOrEmpty(run.Text))
                    continue;

                var fontAttributes = GetFontAttributes(run);
                var font = new Microsoft.Maui.Graphics.Font(run.FontFamily ?? "Arial", (int)run.FontSize, fontAttributes);

                textLayout.Append(run.Text, font, run.TextColor);
            }

            // Measure the text layout
            SizeF textSize = textLayout.GetSize();

            return textSize.Height;
        }

        private FontStyleType GetFontAttributes(RunVM run)
        {
            if (run.Bold && run.Italic)
                return FontStyleType.Oblique; // Use Oblique as BoldItalic is not available
            else if (run.Bold)
                return FontStyleType.Normal;
            else if (run.Italic)
                return FontStyleType.Italic;
            else
                return FontStyleType.Normal;
        }

        private HorizontalAlignment GetHorizontalAlignment(Alignment alignment)
        {
            return alignment switch
            {
                Alignment.Left => HorizontalAlignment.Left,
                Alignment.Centre => HorizontalAlignment.Center,
                Alignment.Right => HorizontalAlignment.Right,
                _ => HorizontalAlignment.Left,
            };
        }
    }
}using Microsoft.Maui.Controls;
using Lucy.ViewModels;
using System.Collections.Specialized;
using System.ComponentModel;
using Microsoft.Maui.Graphics;

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 graphicsView = new GraphicsView
                {
                    WidthRequest = PaperWidth,
                    HeightRequest = PaperHeight,
                    Drawable = new PageDrawable(page)
                };

                _pagesStack.Children.Add(graphicsView);
            }

            // 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;
        }
    }
}Severity	Code	Description	Project	File	Line	Suppression State
Error (active)	CS0234	The type or namespace name 'TextLayout' does not exist in the namespace 'Microsoft.Maui.Graphics.Text' (are you missing an assembly reference?)	Lucy (MAUI) (net8.0-android), Lucy (MAUI) (net8.0-ios), Lucy (MAUI) (net8.0-maccatalyst), Lucy (MAUI) (net8.0-windows10.0.19041.0)	C:\Users\doved\source\repos\Lucy (MAUI)\Lucy (MAUI)\Views\Editor\PageRenderer.cs	63	
Error (active)	CS0234	The type or namespace name 'TextLayout' does not exist in the namespace 'Microsoft.Maui.Graphics.Text' (are you missing an assembly reference?)	Lucy (MAUI) (net8.0-windows10.0.19041.0)	C:\Users\doved\source\repos\Lucy (MAUI)\Lucy (MAUI)\Views\Editor\DocumentRenderer.cs	251	
Error (active)	CS0234	The type or namespace name 'TextLayout' does not exist in the namespace 'Microsoft.Maui.Graphics.Text' (are you missing an assembly reference?)	Lucy (MAUI) (net8.0-android), Lucy (MAUI) (net8.0-ios), Lucy (MAUI) (net8.0-maccatalyst), Lucy (MAUI) (net8.0-windows10.0.19041.0)	C:\Users\doved\source\repos\Lucy (MAUI)\Lucy (MAUI)\Views\Editor\DocumentRenderer.cs	251	
Error (active)	CS0234	The type or namespace name 'TextLayout' does not exist in the namespace 'Microsoft.Maui.Graphics.Text' (are you missing an assembly reference?)	Lucy (MAUI) (net8.0-windows10.0.19041.0)	C:\Users\doved\source\repos\Lucy (MAUI)\Lucy (MAUI)\Views\Editor\PageRenderer.cs	63	
Please fix the bugs, but retain my comments, summaries, notes, to-dos, etc.