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 currentLineHeight = 0;

            SKRect backgroundRect = new SKRect(
                x,
                y,
                x + maxWidth,
                y + MeasureTextBlockHeight(textBlock)
            );

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

                canvas.DrawRect(backgroundRect, backgroundPaint);
            }

            /* 
             * Draw red bounding box around the entire text block.
             * This ensures that the bounding box is always drawn, regardless of the background color.
             */
            using (var borderPaint = new SKPaint
            {
                Color = SKColors.Red,
                Style = SKPaintStyle.Stroke,
                StrokeWidth = 2
            })
            {
                canvas.DrawRect(backgroundRect, borderPaint);
            }

            foreach (var run in textBlock.Runs)
            {
                if (run == null || string.IsNullOrEmpty(run.Text))
                    continue;

                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 fontMetrics = paint.FontMetrics;
                    float runLineHeight = (fontMetrics.Descent - fontMetrics.Ascent + fontMetrics.Leading) * (float)textBlock.LineSpacing;
                    currentLineHeight = Math.Max(currentLineHeight, runLineHeight);

                    string remainingText = run.Text;

                    while (!string.IsNullOrEmpty(remainingText))
                    {
                        float availableWidth = maxWidth - (currentX - x);
                        if (availableWidth <= 0)
                        {
                            // Move to next line if no space left
                            currentY += currentLineHeight;
                            currentX = x + (float)textBlock.HangingIndentation;
                            currentLineHeight = runLineHeight;
                            availableWidth = maxWidth - (currentX - x);
                        }

                        int count = (int)paint.BreakText(remainingText, availableWidth, out float measuredWidth);

                        if (count == 0)
                        {
                            count = 1; // Force at least one character
                        }

                        string fitText = remainingText.Substring(0, count);
                        remainingText = remainingText.Substring(count);

                        // 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
                        canvas.DrawText(fitText, textX, currentY - fontMetrics.Ascent, paint);

                        float textWidth = paint.MeasureText(fitText);
                        float textHeight = runLineHeight;

                        /* 
                         * 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 = currentY + fontMetrics.Ascent;
                        float rectWidth = textWidth;
                        float rectHeight = runLineHeight;

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

                        /* 
                         * 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 += textWidth;

                        if (!string.IsNullOrEmpty(remainingText))
                        {
                            currentY += currentLineHeight;
                            currentX = x + (float)textBlock.HangingIndentation;
                            currentLineHeight = runLineHeight;
                        }
                    }
                }
            }
        }

        /// <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);

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

            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;

            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 totalHeight = 0;
            float maxWidth = textBlock.X + (float)textBlock.LeftIndentation + (float)textBlock.RightIndentation;

            foreach (var run in textBlock.Runs)
            {
                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;

                    // Calculate text width
                    float textWidth = paint.MeasureText(run.Text);

                    // Calculate number of lines needed
                    int lines = (int)Math.Ceiling(textWidth / maxWidth);

                    totalHeight += lineHeight * lines;
                }
            }

            return totalHeight;
        }
    }
}
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
    {
        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;

            for (int i = 0; i < _document.Sections.Count; i++)
            {
                var section = _document.Sections[i];

                // Create heading block
                var headingBlock = CreateHeadingBlock(section);
                float headingHeight = MeasureBlockHeight(headingBlock);

                // Include SpacingBefore
                yPosition += (float)headingBlock.SpacingBefore;

                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;
                }

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

                for (int j = 0; j < section.Blocks.Count; j++)
                {
                    var block = section.Blocks[j];
                    float blockHeight = MeasureBlockHeight(block);

                    // Include SpacingBefore
                    yPosition += (float)block.SpacingBefore;

                    if (yPosition + blockHeight > _paperHeight - MarginBottom)
                    {
                        // Handle 'KeepWithNext' property
                        if (block.KeepWithNext && j + 1 < section.Blocks.Count)
                        {
                            // 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;
                        }
                        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;
                        }
                    }

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

        /// <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
            };

            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)
        {
            if (block is TextBlockVM textBlock)
            {
                return MeasureTextBlockHeight(textBlock);
            }
            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 totalHeight = 0;
            float maxWidth = _paperWidth - MarginLeft - MarginRight - (float)(textBlock.LeftIndentation + textBlock.RightIndentation);

            // Combine all runs into a single string for measurement
            string combinedText = string.Join(" ", textBlock.Runs.Select(run => run.Text));

            using (var paint = new SKPaint())
            {
                // Use the average font size for measurement
                paint.TextSize = (float)(textBlock.Runs.Any() ? textBlock.Runs.Average(run => run.FontSize) : 12f);
                paint.IsAntialias = true;

                // Estimate line height using average metrics
                var metrics = paint.FontMetrics;
                float lineHeight = (metrics.Descent - metrics.Ascent + metrics.Leading) * (float)textBlock.LineSpacing;

                // Measure how the text wraps within maxWidth
                int lines = 0;
                float remainingTextWidth = maxWidth;
                string[] words = combinedText.Split(' ');
                foreach (var word in words)
                {
                    float wordWidth = paint.MeasureText(word + " ");
                    if (wordWidth > remainingTextWidth)
                    {
                        lines++;
                        remainingTextWidth = maxWidth - wordWidth;
                    }
                    else
                    {
                        remainingTextWidth -= wordWidth;
                    }
                }
                // Add the last line
                lines++;

                totalHeight = lines * lineHeight;
            }

            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);
        }
    }
}

In this code for an editor view in my MVVM word processor, the red and green debug boxes (which must be retained in any solution) are showing me that the text is rendering incorrectly; furthermore, there is a huge amount of whitespace between the rendered blocks, which is wrong, and the red lines are showing me that it's because the section is being made too high, even though the red sections always start perfectly at the top of text and sometimes end perfectly at the bottom. The green boxes are being rendered above the text and outside each red/block section. I need the whitespace and text rendering fixed, but I'm not sure how. Give me the full solution to copy and paste.