using System.Net;
using System.Text;
using System.Text.RegularExpressions;

namespace PlotLine.Services;

public interface IHelpMarkdownRenderer
{
    string Render(string markdown);
}

public sealed partial class HelpMarkdownRenderer(IHelpService help) : IHelpMarkdownRenderer
{
    public string Render(string markdown)
    {
        if (string.IsNullOrWhiteSpace(markdown))
        {
            return "<p class=\"muted\">This help article has no body content yet.</p>";
        }

        var lines = markdown.Replace("\r\n", "\n").Split('\n');
        var html = new StringBuilder();
        var paragraph = new List<string>();
        var listItems = new List<string>();
        var inTable = false;

        void FlushParagraph()
        {
            if (paragraph.Count == 0) return;
            html.Append("<p>").Append(RenderInline(string.Join(" ", paragraph))).AppendLine("</p>");
            paragraph.Clear();
        }

        void FlushList()
        {
            if (listItems.Count == 0) return;
            html.AppendLine("<ul>");
            foreach (var item in listItems)
            {
                html.Append("<li>").Append(RenderInline(item)).AppendLine("</li>");
            }
            html.AppendLine("</ul>");
            listItems.Clear();
        }

        void CloseTable()
        {
            if (!inTable) return;
            html.AppendLine("</tbody></table>");
            inTable = false;
        }

        for (var index = 0; index < lines.Length; index++)
        {
            var line = lines[index].Trim();
            if (string.IsNullOrWhiteSpace(line))
            {
                FlushParagraph();
                FlushList();
                CloseTable();
                continue;
            }

            if (line.StartsWith("#", StringComparison.Ordinal))
            {
                FlushParagraph();
                FlushList();
                CloseTable();
                var level = Math.Clamp(line.TakeWhile(ch => ch == '#').Count(), 1, 4);
                var text = line[level..].Trim();
                html.Append("<h").Append(level).Append(">")
                    .Append(RenderInline(text))
                    .Append("</h").Append(level).AppendLine(">");
                continue;
            }

            if (line.StartsWith("- ", StringComparison.Ordinal) || line.StartsWith("* ", StringComparison.Ordinal))
            {
                FlushParagraph();
                CloseTable();
                listItems.Add(line[2..].Trim());
                continue;
            }

            if (LooksLikeTableRow(line))
            {
                FlushParagraph();
                FlushList();
                if (index + 1 < lines.Length && LooksLikeTableSeparator(lines[index + 1].Trim()))
                {
                    if (inTable) CloseTable();
                    html.AppendLine("<table class=\"table table-sm help-markdown-table\"><thead><tr>");
                    foreach (var cell in SplitTableCells(line))
                    {
                        html.Append("<th>").Append(RenderInline(cell)).AppendLine("</th>");
                    }
                    html.AppendLine("</tr></thead><tbody>");
                    inTable = true;
                    index++;
                    continue;
                }

                if (inTable)
                {
                    html.AppendLine("<tr>");
                    foreach (var cell in SplitTableCells(line))
                    {
                        html.Append("<td>").Append(RenderInline(cell)).AppendLine("</td>");
                    }
                    html.AppendLine("</tr>");
                    continue;
                }
            }

            paragraph.Add(line);
        }

        FlushParagraph();
        FlushList();
        CloseTable();
        return html.ToString();
    }

    private static bool LooksLikeTableRow(string line) => line.Contains('|');

    private static bool LooksLikeTableSeparator(string line) =>
        line.Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
            .All(cell => cell.Length > 0 && cell.All(ch => ch is '-' or ':'));

    private static IReadOnlyList<string> SplitTableCells(string line) =>
        line.Trim('|').Split('|', StringSplitOptions.TrimEntries).ToList();

    private string RenderInline(string value)
    {
        var encoded = WebUtility.HtmlEncode(value);
        encoded = ImageRegex().Replace(encoded, match =>
        {
            var alt = match.Groups["alt"].Value;
            var url = match.Groups["url"].Value;
            return $"<img src=\"/HelpDrawer/Image?file={WebUtility.UrlEncode(url)}\" alt=\"{alt}\" class=\"help-article-image\" />";
        });
        encoded = LinkRegex().Replace(encoded, match =>
        {
            var text = match.Groups["text"].Value;
            var url = RewriteHelpUrl(match.Groups["url"].Value);
            return $"<a href=\"{url}\" target=\"_blank\" rel=\"noopener\">{text}</a>";
        });
        encoded = StrongRegex().Replace(encoded, "<strong>$1</strong>");
        encoded = EmphasisRegex().Replace(encoded, "<em>$1</em>");
        return encoded;
    }

    private string RewriteHelpUrl(string url)
    {
        var decodedUrl = WebUtility.HtmlDecode(url);
        const string articlePrefix = "/Help/Article/";
        if (decodedUrl.StartsWith(articlePrefix, StringComparison.OrdinalIgnoreCase))
        {
            var key = Uri.UnescapeDataString(decodedUrl[articlePrefix.Length..]);
            var article = help.GetByContextKey(key);
            if (article is not null)
            {
                return article.Url;
            }
        }

        const string categoryPrefix = "/Help/Category/";
        if (decodedUrl.StartsWith(categoryPrefix, StringComparison.OrdinalIgnoreCase))
        {
            var category = Uri.UnescapeDataString(decodedUrl[categoryPrefix.Length..]);
            var categoryName = help.GetByCategory(category).FirstOrDefault()?.Category;
            if (!string.IsNullOrWhiteSpace(categoryName))
            {
                return $"/help/{HelpService.Slugify(categoryName)}";
            }
        }

        return url;
    }

    [GeneratedRegex(@"!\[(?<alt>[^\]]*)\]\((?<url>[^)]+)\)")]
    private static partial Regex ImageRegex();

    [GeneratedRegex(@"\[(?<text>[^\]]+)\]\((?<url>[^)]+)\)")]
    private static partial Regex LinkRegex();

    [GeneratedRegex(@"\*\*([^*]+)\*\*")]
    private static partial Regex StrongRegex();

    [GeneratedRegex(@"\*([^*]+)\*")]
    private static partial Regex EmphasisRegex();
}
