using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Media;
using Markdig.Extensions.Tables;
using Markdig.Syntax;
using MarkdownEditor.Constants;

namespace MarkdownEditor.Views.Renderers;

/// <summary>
/// Coordinates multiple markdown block renderers to render a complete markdown document.
/// Uses a chain of responsibility pattern to delegate rendering to appropriate renderers.
/// </summary>
public class MarkdownRendererCoordinator
{
    private readonly List<IMarkdownBlockRenderer> _renderers;
    private readonly InlineRenderer _inlineRenderer;
    private readonly bool _isDarkMode;
    private readonly double _fontScale;

    /// <summary>
    /// Initializes a new instance of the <see cref="MarkdownRendererCoordinator"/> class.
    /// </summary>
    /// <param name="isDarkMode">Whether the current theme is dark mode.</param>
    /// <param name="fontScale">Font scale factor for text sizing (1.0 = 14px base).</param>
    public MarkdownRendererCoordinator(bool isDarkMode = true, double fontScale = 1.0)
    {
        _isDarkMode = isDarkMode;
        _fontScale = fontScale;
        _inlineRenderer = new InlineRenderer(isDarkMode, fontScale);
        
        _renderers = new List<IMarkdownBlockRenderer>
        {
            new HeadingRenderer(fontScale),
            new CodeBlockRenderer(isDarkMode, fontScale),
            new ParagraphRenderer(_inlineRenderer),
            new ThematicBreakRenderer(isDarkMode),
            new ListRenderer(this),      // Pass coordinator for nested rendering
            new QuoteRenderer(this),     // Pass coordinator for nested rendering
            new TableRenderer(this, isDarkMode, fontScale)      // Pass coordinator for nested rendering
        };
    }

    /// <summary>
    /// Renders a markdown block by delegating to the appropriate renderer.
    /// </summary>
    /// <param name="block">The markdown block to render.</param>
    /// <returns>The rendered Avalonia control, or null if no renderer can handle the block.</returns>
    public Control? RenderBlock(Block block)
    {
        foreach (var renderer in _renderers)
        {
            if (renderer.CanRender(block))
            {
                return renderer.Render(block);
            }
        }

        return null;
    }

    /// <summary>
    /// Renders multiple markdown blocks into a collection of controls.
    /// </summary>
    /// <param name="blocks">The markdown blocks to render.</param>
    /// <returns>A collection of rendered controls.</returns>
    public IEnumerable<Control> RenderBlocks(IEnumerable<Block> blocks)
    {
        var controls = new List<Control>();
        foreach (var block in blocks)
        {
            var control = RenderBlock(block);
            if (control != null)
            {
                controls.Add(control);
            }
        }
        return controls;
    }

    /// <summary>
    /// Renders all markdown blocks into a single SelectableTextBlock's Inlines collection.
    /// This enables text selection across multiple blocks.
    /// </summary>
    /// <param name="blocks">The markdown blocks to render.</param>
    /// <param name="inlines">The inline collection to populate.</param>
    public void RenderBlocksToInlines(IEnumerable<Block> blocks, InlineCollection inlines)
    {
        bool isFirstBlock = true;
        
        foreach (var block in blocks)
        {
            // Add spacing between blocks (except before the first one)
            if (!isFirstBlock)
            {
                inlines.Add(new LineBreak());
                inlines.Add(new LineBreak());
            }
            isFirstBlock = false;

            switch (block)
            {
                case HeadingBlock heading:
                    RenderHeadingToInlines(heading, inlines);
                    break;

                case ParagraphBlock paragraph:
                    _inlineRenderer.BuildInlines(paragraph.Inline, inlines);
                    break;

                case CodeBlock codeBlock:
                    RenderCodeBlockToInlines(codeBlock, inlines);
                    break;

                case ListBlock list:
                    RenderListToInlines(list, inlines);
                    break;

                case QuoteBlock quote:
                    RenderQuoteToInlines(quote, inlines);
                    break;

                case Table table:
                    RenderTableToInlines(table, inlines);
                    break;

                case ThematicBreakBlock:
                    inlines.Add(new Run(new string('─', 50))
                    {
                        Foreground = new SolidColorBrush(Color.Parse(_isDarkMode ? "#666666" : "#CCCCCC"))
                    });
                    break;
            }
        }
    }

    private void RenderHeadingToInlines(HeadingBlock heading, InlineCollection inlines)
    {
        var fontSize = GetHeadingFontSize(heading.Level) * _fontScale;
        var text = MarkdownTextHelper.ExtractText(heading.Inline);
        
        // Render heading text with inline formatting preserved
        if (heading.Inline != null)
        {
            foreach (var inline in heading.Inline)
            {
                if (inline is Markdig.Syntax.Inlines.LiteralInline literal)
                {
                    inlines.Add(new Run(literal.Content.ToString())
                    {
                        FontSize = fontSize,
                        FontWeight = FontWeight.Bold
                    });
                }
                else if (inline is Markdig.Syntax.Inlines.ContainerInline container)
                {
                    // Handle nested formatting (bold, italic, etc.)
                    RenderInlineToInlines(container, inlines, fontSize, FontWeight.Bold);
                }
            }
        }
        else
        {
            inlines.Add(new Run(text)
            {
                FontSize = fontSize,
                FontWeight = FontWeight.Bold
            });
        }
    }

    private void RenderInlineToInlines(Markdig.Syntax.Inlines.ContainerInline container, InlineCollection inlines, double fontSize, FontWeight fontWeight)
    {
        foreach (var inline in container)
        {
            if (inline is Markdig.Syntax.Inlines.LiteralInline literal)
            {
                inlines.Add(new Run(literal.Content.ToString())
                {
                    FontSize = fontSize,
                    FontWeight = fontWeight
                });
            }
            else if (inline is Markdig.Syntax.Inlines.EmphasisInline emphasis)
            {
                var text = MarkdownTextHelper.ExtractText(emphasis);
                var run = new Run(text)
                {
                    FontSize = fontSize,
                    FontWeight = fontWeight
                };
                
                if (emphasis.DelimiterCount == 2)
                {
                    run.FontWeight = FontWeight.Bold;
                }
                else
                {
                    run.FontStyle = FontStyle.Italic;
                }
                inlines.Add(run);
            }
            else if (inline is Markdig.Syntax.Inlines.ContainerInline nested)
            {
                RenderInlineToInlines(nested, inlines, fontSize, fontWeight);
            }
        }
    }

    private void RenderCodeBlockToInlines(CodeBlock codeBlock, InlineCollection inlines)
    {
        var text = codeBlock is FencedCodeBlock fenced
            ? fenced.Lines.ToString()
            : codeBlock.Lines.ToString();

        inlines.Add(new Run(text)
        {
            FontFamily = new FontFamily("Consolas,Courier New,monospace"),
            FontSize = 14 * _fontScale,
            Foreground = new SolidColorBrush(Color.Parse(_isDarkMode ? "#E8E8E8" : "#333333")),
            Background = new SolidColorBrush(Color.Parse(_isDarkMode ? "#2D2D2D" : "#F0F0F0"))
        });
    }

    private void RenderListToInlines(ListBlock list, InlineCollection inlines)
    {
        int index = 1;
        foreach (var item in list)
        {
            if (item is ListItemBlock listItem)
            {
                var bullet = list.IsOrdered ? $"{index}. " : "• ";
                inlines.Add(new Run(bullet));
                
                foreach (var childBlock in listItem)
                {
                    if (childBlock is ParagraphBlock paragraph)
                    {
                        _inlineRenderer.BuildInlines(paragraph.Inline, inlines);
                    }
                    else if (childBlock is HeadingBlock heading)
                    {
                        RenderHeadingToInlines(heading, inlines);
                    }
                    else
                    {
                        // For other block types in list items, recursively render them
                        RenderBlockToInlines(childBlock, inlines);
                    }
                }
                
                inlines.Add(new LineBreak());
                index++;
            }
        }
    }

    private void RenderQuoteToInlines(QuoteBlock quote, InlineCollection inlines)
    {
        foreach (var childBlock in quote)
        {
            if (childBlock is ParagraphBlock paragraph)
            {
                inlines.Add(new Run("> "));
                _inlineRenderer.BuildInlines(paragraph.Inline, inlines);
                inlines.Add(new LineBreak());
            }
        }
    }

    private void RenderTableToInlines(Table table, InlineCollection inlines)
    {
        // Render table as plain text for selection purposes
        foreach (var row in table)
        {
            if (row is TableRow tableRow)
            {
                bool firstCell = true;
                foreach (var cell in tableRow)
                {
                    if (!firstCell) inlines.Add(new Run(" | "));
                    firstCell = false;
                    
                    if (cell is TableCell tableCell)
                    {
                        // Table cells contain child blocks, not inline content directly
                        foreach (var childBlock in tableCell)
                        {
                            RenderBlockToInlines(childBlock, inlines);
                        }
                    }
                }
                inlines.Add(new LineBreak());
            }
        }
    }

    private void RenderBlockToInlines(Block block, InlineCollection inlines)
    {
        // Helper method to render any block type to inlines
        switch (block)
        {
            case ParagraphBlock paragraph:
                _inlineRenderer.BuildInlines(paragraph.Inline, inlines);
                break;
            case HeadingBlock heading:
                RenderHeadingToInlines(heading, inlines);
                break;
            case CodeBlock codeBlock:
                RenderCodeBlockToInlines(codeBlock, inlines);
                break;
            default:
                // For other block types, try to extract text if they have inline content
                Markdig.Syntax.Inlines.ContainerInline? inlineContent = null;
                if (block is ParagraphBlock p) inlineContent = p.Inline;
                else if (block is HeadingBlock h) inlineContent = h.Inline;
                
                var text = MarkdownTextHelper.ExtractText(inlineContent);
                if (!string.IsNullOrEmpty(text))
                {
                    inlines.Add(new Run(text));
                }
                break;
        }
    }

    private static int GetHeadingFontSize(int level) => level switch
    {
        1 => AppConstants.FontSizes.Heading1,
        2 => AppConstants.FontSizes.Heading2,
        3 => AppConstants.FontSizes.Heading3,
        4 => AppConstants.FontSizes.Heading4,
        5 => AppConstants.FontSizes.Heading5,
        _ => AppConstants.FontSizes.Heading6
    };
}

