using AsyncApiRenderer.Data;
using System.Text.Json;
using System.Text;
using System.Text.Unicode;
using System.Text.RegularExpressions;

namespace AsyncApiRenderer.Renderer;

public class PandocMarkdownRenderer
{
    public void Render(JsonSchemaNode schema, TextWriter writer)
    {
        RenderTable(schema.GetRules(), writer);
        RenderExamples(schema.GetExamples(), writer);
    }

    private void RenderTable(IEnumerable<Rule> rules, TextWriter writer)
    {
        // writer.WriteLine("\\tymin=20pt\\tymax=200pt");
        // writer.WriteLine("\\setlength\\extrarowheight{2pt}");
        writer.WriteLine("\\begin{longtable}{|p{100pt}|p{25pt}|p{100pt}|p{160pt}|}");
        writer.WriteLine("  \\hline");
        writer.WriteLine("  Path & Flags & Type/\"\"Value & Description \\\\");
        writer.WriteLine("  \\hline");
        writer.WriteLine("  \\endfirsthead");
        writer.WriteLine();
        writer.WriteLine("  \\addlinespace");
        writer.WriteLine("  \\hline");
        writer.WriteLine("  Path & Flags & Type/\"\"Value & Description \\\\");
        writer.WriteLine("  \\hline");
        writer.WriteLine("  \\endhead");
        writer.WriteLine();
        bool hasRequired = false;
        bool hasNullable = false;
        bool hasChoice = false;
        bool hasAdditional = false;
        foreach (var rule in rules)
        {
            hasRequired |= rule.IsRequired;
            hasNullable |= rule.IsNullable;
            hasChoice |= rule.IsChoice;
            hasAdditional |= rule.isAdditional;
            writer.Write("  \\texttt{");
            writer.Write(EscapeLatex(rule.Path.TrimStart('.')).Replace(".", "\"\"."));
            writer.Write("} & ");
            if (rule.IsRequired)
                writer.Write("R");
            if (rule.IsNullable)
                writer.Write("N");
            if (rule.IsChoice)
                writer.Write("C");
            if (rule.isAdditional)
                writer.Write("A");
            writer.Write(" & ");
            var first = true;
            if (rule.Types is not null)
                foreach (var type in rule.Types)
                {
                    if (type == JsonSchemaType.Null && rule.Types.Count > 1)
                        continue;
                    if (first)
                        first = false;
                    else writer.Write(", ");
                    writer.Write("\\texttt{\\emph{");
                    writer.Write(type switch
                    {
                        JsonSchemaType.Array => "array",
                        JsonSchemaType.Boolean => "bool",
                        JsonSchemaType.Integer => "int",
                        JsonSchemaType.Null => "null",
                        JsonSchemaType.Number => "number",
                        JsonSchemaType.Object => "object",
                        JsonSchemaType.String => "string",
                        _ => type,
                    });
                    writer.Write("}}");
                }
            if (rule.Format is not null)
            {
                if (first)
                    first = false;
                else writer.Write(", ");
                writer.Write($"\\texttt{{<{rule.Format}>}}");
            }
            if (rule.Enum is not null)
                foreach (var val in rule.Enum)
                {
                    if (first)
                        first = false;
                    else writer.Write(", ");
                    writer.Write("\\texttt{{\\dq ");
                    RenderBreakpoints(val, writer);
                    writer.Write("\\dq}}");
                }
            writer.Write(" & ");
            if (rule.Description != null)
                writer.Write(EscapeLatex(rule.Description));
            writer.WriteLine(" \\\\");
            writer.WriteLine("  \\hline");
        }
        writer.WriteLine("\\end{longtable}");
        writer.WriteLine();
        if (hasRequired || hasNullable || hasChoice || hasAdditional)
        {
            writer.WriteLine("\\begin{samepage}\\textbf{Flags:}\\begin{itemize}\\tightlist");
            writer.WriteLine();
            if (hasRequired)
                writer.WriteLine("\t\\item\\emph{R}: This field is required. This path must always exists.");
            if (hasNullable)
                writer.WriteLine("\t\\item\\emph{N}: The value of this field can be \\KeywordTok{\\texttt{null}}.");
            if (hasChoice)
                writer.WriteLine("\t\\item\\emph{C}: This is one of multiple choices. You have to select one of the choices.");
            if (hasAdditional)
                writer.WriteLine("\t\\item\\emph{A}: This is an additional property. You can insert any not previously defined name to add your own properties. The schema of this property is predefined has to be used as above.");
            writer.WriteLine("\\end{itemize}\\end{samepage}");
            writer.WriteLine();
        }
    }

    private void RenderBreakpoints(string text, TextWriter writer)
    {
        bool canBreak = false;
        foreach (var ch in text)
        {
            var isLarger = ch >= 'A' && ch <= 'Z';
            if (isLarger && canBreak)
            {
                writer.Write("\"\"");
                canBreak = false;
            }
            if (!isLarger)
                canBreak = true;
            writer.Write(ch);
        }
    }

    private void RenderExamples(IEnumerable<JsonExampleNode> examples, TextWriter writer)
    {
        using var buffer = new MemoryStream();
        Span<char> charBuffer = stackalloc char[1024];
        int emited = 0;
        foreach (var example in examples)
        {
            if (++emited > 3)
                break;
            if (emited > 1)
            {
                writer.WriteLine();
                writer.WriteLine("---");
                writer.WriteLine();
            }
            var jsonWriter = new Utf8JsonWriter(buffer, new JsonWriterOptions
            {
                Indented = true,
            });
            writer.WriteLine("```json");

            try { example.Write(jsonWriter); }
            finally
            {
                jsonWriter.Flush();
                int byteOffset = 0;
                ReadOnlySpan<byte> bytes = buffer.ToArray();
                while (byteOffset < bytes.Length)
                {
                    var readedBytes = Encoding.UTF8.GetChars(
                        bytes.Slice(
                            byteOffset,
                            Math.Min(bytes.Length - byteOffset, charBuffer.Length >> 3)
                        ), 
                        charBuffer
                    );
                    var readedChars = Encoding.UTF8.GetCharCount(bytes.Slice(byteOffset, readedBytes));
                    byteOffset += readedBytes;
                    writer.Write(charBuffer[.. readedChars]);
                }
                buffer.Position = 0;
                buffer.SetLength(0);

                writer.WriteLine();
                writer.WriteLine("```");
            }
        }
    }
    
    public void Render(string key, Message message, TextWriter writer)
    {
        // writer.WriteLine("\\pagebreak");
        // writer.WriteLine();
        writer.Write("#### ");
        writer.WriteLine(message.Name ?? key);
        writer.WriteLine();
        writer.Write("*");
        writer.Write(message.Summary);
        writer.WriteLine("*");
        writer.WriteLine();
        writer.WriteLine(message.Description);
        writer.WriteLine();
        RenderTable(message.Payload.GetRules(), writer);
        if (message.GetResponseMessages().Any())
        {
            writer.WriteLine();
            writer.WriteLine("\\textbf{Responses:}\\nopagebreak[4]");
            writer.WriteLine();
            foreach (var response in message.GetResponseMessages())
            {
                writer.WriteLine($"- [{response}](#{Program.Prefix}{response.ToLower()})");
            }
        }
        writer.WriteLine();
        writer.WriteLine("\\textbf{Examples:}\\nopagebreak[4]");
        writer.WriteLine();
        // writer.WriteLine();
        RenderExamples(message.Payload.GetExamples(), writer);
        writer.WriteLine();
    }

    public void Render(DataFile file, TextWriter writer)
    {
        writer.Write("## ");
        writer.WriteLine(file.Info.Title);
        writer.WriteLine();
        writer.Write("> Version: ");
        writer.WriteLine(file.Info.Version);
        writer.WriteLine();
        writer.WriteLine(file.Info.Description);
        writer.WriteLine();
        foreach (var (key, channel) in file.Channels)
        {
            writer.WriteLine($"### PUB `{key}` Operation");
            Render(file, channel.Publish, writer);
            writer.WriteLine($"### SUB `{key}` Operation");
            Render(file, channel.Subscribe, writer);
        }
    }

    private void Render(DataFile file, ChannelInfo channel, TextWriter writer)
    {
        writer.WriteLine();
        writer.WriteLine(channel.Description);
        writer.WriteLine();
        writer.WriteLine("Accepts **one of** the following messages:");
        writer.WriteLine();
        foreach (var id in channel.GetMessageIds())
            if (file.Components.Messages.TryGetValue(id, out Message? message))
            {
                Render(id, message, writer);
            }
    }

    static Regex Verbatim = new Regex("`([^`]*)`", RegexOptions.Compiled);

    private string EscapeLatex(string text)
    {
        text = new StringBuilder(text.Trim())
            .Replace("\\", "\\textbackslash{}")
            .Replace("\r\n", "\n")
            .Replace("\n\n", "\\newline ")
            .Replace(" \n", " ")
            .Replace("\n ", " ")
            .Replace("\n", " ")
            .Replace("$", "\\$")
            .Replace("^", "\\^{}")
            .Replace("[", "{[}")
            .Replace("]", "{]}")
            .ToString();
        return Verbatim.Replace(text, match => $"\\texttt{{{match.Groups[1].Value}}}");
    }
}