// Copyright (c) Rodel. All rights reserved.
// <auto-generated />

using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using ColorCode;
using ColorCode.Common;
using ColorCode.Styling;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
using BiliCopilot.UI.Controls.Markdown.TextElements;
using Windows.UI.ViewManagement;

namespace BiliCopilot.UI.Controls.Markdown;

public static class Extensions
{
    private const string OneDarkBackground = "#282c34";
    private const string OneDarkPlainText = "#abb2bf";

    private const string OneDarkXMLDelimiter = "#e06c75";
    private const string OneDarkXMLName = "#d19a66";
    private const string OneDarkXMLAttribute = "#61afef";
    private const string OneDarkXAMLCData = "#98c379";
    private const string OneDarkXMLComment = "#5c6370";

    private const string OneDarkComment = "#5c6370";
    private const string OneDarkKeyword = "#c678dd";
    private const string OneDarkGray = "#9b9b9b";
    private const string OneDarkNumber = "#d19a66";
    private const string OneDarkClass = "#e5c07b";
    private const string OneDarkString = "#98c379";

    public const string Blue = "#FF0000FF";
    public const string White = "#FFFFFFFF";
    public const string Black = "#FF000000";
    public const string DullRed = "#FFA31515";
    public const string Yellow = "#FFFFFF00";
    public const string Green = "#FF008000";
    public const string PowderBlue = "#FFB0E0E6";
    public const string Teal = "#FF008080";
    public const string Gray = "#FF808080";
    public const string Navy = "#FF000080";
    public const string OrangeRed = "#FFFF4500";
    public const string Purple = "#FF800080";
    public const string Red = "#FFFF0000";
    public const string MediumTurqoise = "FF48D1CC";
    public const string Magenta = "FFFF00FF";
    public const string OliveDrab = "#FF6B8E23";
    public const string DarkOliveGreen = "#FF556B2F";
    public const string DarkCyan = "#FF008B8B";

    public static ILanguage ToLanguage(this FencedCodeBlock fencedCodeBlock)
    {
        return fencedCodeBlock.Info?.ToLower() switch
        {
            "aspx" => Languages.Aspx,
            "aspx - vb" => Languages.AspxVb,
            "asax" => Languages.Asax,
            "ascx" => Languages.AspxCs,
            "ashx" or "asmx" or "axd" => Languages.Ashx,
            "cs" or "csharp" or "c#" => Languages.CSharp,
            "xhtml" or "html" or "hta" or "htm" or "html.hl" or "inc" or "xht" => Languages.Html,
            "java" or "jav" or "jsh" => Languages.Java,
            "js" or "node" or "_js" or "bones" or "cjs" or "es" or "es6" or "frag" or "gs" or "jake" or "javascript" or "jsb" or "jscad" or "jsfl" or "jslib" or "jsm" or "jspre" or "jss" or "jsx" or "mjs" or "njs" or "pac" or "sjs" or "ssjs" or "xsjs" or "xsjslib" => Languages.JavaScript,
            "posh" or "pwsh" or "ps1" or "psd1" or "psm1" => Languages.PowerShell,
            "sql" or "cql" or "ddl" or "mysql" or "prc" or "tab" or "udf" or "viw" => Languages.Sql,
            "vb" or "vbhtml" or "visual basic" or "vbnet" or "vb .net" or "vb.net" => Languages.VbDotNet,
            "rss" or "xsd" or "wsdl" or "xml" or "adml" or "admx" or "ant" or "axaml" or "axml" or "builds" or "ccproj" or "ccxml" or "clixml" or "cproject" or "cscfg" or "csdef" or "csl" or "csproj" or "ct" or "depproj" or "dita" or "ditamap" or "ditaval" or "dll.config" or "dotsettings" or "filters" or "fsproj" or "fxml" or "glade" or "gml" or "gmx" or "grxml" or "gst" or "hzp" or "iml" or "ivy" or "jelly" or "jsproj" or "kml" or "launch" or "mdpolicy" or "mjml" or "mm" or "mod" or "mxml" or "natvis" or "ncl" or "ndproj" or "nproj" or "nuspec" or "odd" or "osm" or "pkgproj" or "pluginspec" or "proj" or "props" or "ps1xml" or "psc1" or "pt" or "qhelp" or "rdf" or "res" or "resx" or "rs" or "sch" or "scxml" or "sfproj" or "shproj" or "srdf" or "storyboard" or "sublime-snippet" or "sw" or "targets" or "tml" or "ui" or "urdf" or "ux" or "vbproj" or "vcxproj" or "vsixmanifest" or "vssettings" or "vstemplate" or "vxml" or "wixproj" or "workflow" or "wsf" or "wxi" or "wxl" or "wxs" or "x3d" or "xacro" or "xaml" or "xib" or "xlf" or "xliff" or "xmi" or "xml.dist" or "xmp" or "xproj" or "xspec" or "xul" or "zcml" => Languages.Xml,
            "php" or "aw" or "ctp" or "fcgi" or "php3" or "php4" or "php5" or "phps" or "phpt" => Languages.Php,
            "css" or "scss" or "less" => Languages.Css,
            "cpp" or "c++" or "cc" or "cp" or "cxx" or "h" or "h++" or "hh" or "hpp" or "hxx" or "inl" or "ino" or "ipp" or "ixx" or "re" or "tcc" or "tpp" => Languages.Cpp,
            "ts" or "tsx" or "cts" or "mts" => Languages.Typescript,
            "fsharp" or "fs" or "fsi" or "fsx" => Languages.FSharp,
            "koka" => Languages.Koka,
            "hs" or "hs-boot" or "hsc" => Languages.Haskell,
            "pandoc" or "md" or "livemd" or "markdown" or "mdown" or "mdwn" or "mdx" or "mkd" or "mkdn" or "mkdown" or "ronn" or "scd" or "workbook" => Languages.Markdown,
            "fortran" or "f" or "f77" or "for" or "fpp" => Languages.Fortran,
            "python" or "py" or "cgi" or "gyp" or "gypi" or "lmi" or "py3" or "pyde" or "pyi" or "pyp" or "pyt" or "pyw" or "rpy" or "smk" or "spec" or "tac" or "wsgi" or "xpy" => Languages.Python,
            "matlab" or "m" => Languages.MATLAB,
            _ => Languages.JavaScript,
        };
    }

    public static string ToAlphabetical(this int index)
    {
        var alphabetical = "abcdefghijklmnopqrstuvwxyz";
        var remainder = index;
        var stringBuilder = new StringBuilder();
        while (remainder != 0)
        {
            if (remainder > 26)
            {
                var newRemainder = remainder % 26;
                var i = (remainder - newRemainder) / 26;
                stringBuilder.Append(alphabetical[i - 1]);
                remainder = newRemainder;
            }
            else
            {
                stringBuilder.Append(alphabetical[remainder - 1]);
                remainder = 0;
            }
        }

        return stringBuilder.ToString();
    }

    public static TextPointer GetNextInsertionPosition(this TextPointer position, LogicalDirection logicalDirection)
    {
        // Check if the current position is already an insertion position
        if (position.IsAtInsertionPosition(logicalDirection))
        {
            // Return the same position
            return position;
        }
        else
        {
            // Try to find the next insertion position by moving one symbol forward
            TextPointer next = position.GetPositionAtOffset(1, logicalDirection);

            // If there is no next position, return null
            if (next == null)
            {
                return null;
            }
            else
            {
                // Recursively call this method until an insertion position is found or null is returned
                return GetNextInsertionPosition(next, logicalDirection);
            }
        }
    }

    public static bool IsAtInsertionPosition(this TextPointer position, LogicalDirection logicalDirection)
    {
        // Get the character rect of the current position
        Rect currentRect = position.GetCharacterRect(logicalDirection);

        // Try to get the next position by moving one symbol forward
        TextPointer next = position.GetPositionAtOffset(1, logicalDirection);

        // If there is no next position, return false
        if (next == null)
        {
            return false;
        }
        else
        {
            // Get the character rect of the next position
            Rect nextRect = next.GetCharacterRect(logicalDirection);

            // Compare the two rects and return true if they are different
            return !currentRect.Equals(nextRect);
        }
    }

    public static string RemoveImageSize(string url)
    {
        if (string.IsNullOrEmpty(url))
        {
            throw new ArgumentException("URL must not be null or empty", nameof(url));
        }

        // Create a regex pattern to match the URL with width and height
        var pattern = @"([^)\s]+)\s*=\s*\d+x\d+\s*";

        // Replace the matched URL with the URL only
        var result = Regex.Replace(url, pattern, "$1");

        return result;
    }

    public static Uri GetUri(string url, string @base)
    {
        var validUrl = RemoveImageSize(url);
        Uri result;
        if (Uri.TryCreate(validUrl, UriKind.Absolute, out result))
        {
            // the url is already absolute
            return result;
        }
        else if (!string.IsNullOrWhiteSpace(@base))
        {
            // the url is relative, so append the base
            // trim any trailing "/" from the base and any leading "/" from the url
            @base = @base?.TrimEnd('/');
            validUrl = validUrl.TrimStart('/');

            // return the base and the url separated by a single "/"
            return new Uri(@base + "/" + validUrl);
        }
        else
        {
            // the url is relative to the file system
            // add ms-appx
            validUrl = validUrl.TrimStart('/');
            return new Uri("ms-appx:///" + validUrl);
        }
    }

    public static StyleDictionary GetOneDarkProStyle()
    {
        return new StyleDictionary
                {
                    new ColorCode.Styling.Style(ScopeName.PlainText)
                    {
                        Foreground = OneDarkPlainText,
                        Background = OneDarkBackground,
                        ReferenceName = "plainText",
                    },
                    new ColorCode.Styling.Style(ScopeName.HtmlServerSideScript)
                    {
                        Background = Yellow,
                        ReferenceName = "htmlServerSideScript",
                    },
                    new ColorCode.Styling.Style(ScopeName.HtmlComment)
                    {
                        Foreground = OneDarkComment,
                        ReferenceName = "htmlComment",
                    },
                    new ColorCode.Styling.Style(ScopeName.HtmlTagDelimiter)
                    {
                        Foreground = OneDarkKeyword,
                        ReferenceName = "htmlTagDelimiter",
                    },
                    new ColorCode.Styling.Style(ScopeName.HtmlElementName)
                    {
                        Foreground = DullRed,
                        ReferenceName = "htmlElementName",
                    },
                    new ColorCode.Styling.Style(ScopeName.HtmlAttributeName)
                    {
                        Foreground = Red,
                        ReferenceName = "htmlAttributeName",
                    },
                    new ColorCode.Styling.Style(ScopeName.HtmlAttributeValue)
                    {
                        Foreground = OneDarkKeyword,
                        ReferenceName = "htmlAttributeValue",
                    },
                    new ColorCode.Styling.Style(ScopeName.HtmlOperator)
                    {
                        Foreground = OneDarkKeyword,
                        ReferenceName = "htmlOperator",
                    },
                    new ColorCode.Styling.Style(ScopeName.Comment)
                    {
                        Foreground = OneDarkComment,
                        ReferenceName = "comment",
                    },
                    new ColorCode.Styling.Style(ScopeName.XmlDocTag)
                    {
                        Foreground = OneDarkXMLComment,
                        ReferenceName = "xmlDocTag",
                    },
                    new ColorCode.Styling.Style(ScopeName.XmlDocComment)
                    {
                        Foreground = OneDarkXMLComment,
                        ReferenceName = "xmlDocComment",
                    },
                    new ColorCode.Styling.Style(ScopeName.String)
                    {
                        Foreground = OneDarkString,
                        ReferenceName = "string",
                    },
                    new ColorCode.Styling.Style(ScopeName.StringCSharpVerbatim)
                    {
                        Foreground = OneDarkString,
                        ReferenceName = "stringCSharpVerbatim",
                    },
                    new ColorCode.Styling.Style(ScopeName.Keyword)
                    {
                        Foreground = OneDarkKeyword,
                        ReferenceName = "keyword",
                    },
                    new ColorCode.Styling.Style(ScopeName.PreprocessorKeyword)
                    {
                        Foreground = OneDarkKeyword,
                        ReferenceName = "preprocessorKeyword",
                    },
                    new ColorCode.Styling.Style(ScopeName.Number)
                     {
                         Foreground = OneDarkNumber,
                         ReferenceName = "number",
                     },

                    new ColorCode.Styling.Style(ScopeName.CssPropertyName)
                     {
                         Foreground = OneDarkClass,
                         ReferenceName = "cssPropertyName",
                     },

                    new ColorCode.Styling.Style(ScopeName.CssPropertyValue)
                     {
                         Foreground = OneDarkString,
                         ReferenceName = "cssPropertyValue",
                     },

                    new ColorCode.Styling.Style(ScopeName.CssSelector)
                     {
                         Foreground = OneDarkKeyword,
                         ReferenceName = "cssSelector",
                     },

                    new ColorCode.Styling.Style(ScopeName.SqlSystemFunction)
                     {
                         Foreground = OneDarkClass,
                         ReferenceName = "sqlSystemFunction",
                     },

                    new ColorCode.Styling.Style(ScopeName.XmlAttribute)
                    {
                        Foreground = OneDarkXMLAttribute,
                        ReferenceName = "xmlAttribute",
                    },

                    new ColorCode.Styling.Style(ScopeName.XmlAttributeQuotes)
                    {
                        Foreground = OneDarkXMLDelimiter,
                        ReferenceName = "xmlAttributeQuotes",
                    },

                    new ColorCode.Styling.Style(ScopeName.XmlAttributeValue)
                    {
                        Foreground = OneDarkString,
                        ReferenceName = "xmlAttributeValue",
                    },

                    new ColorCode.Styling.Style(ScopeName.XmlCDataSection)
                    {
                        Foreground = OneDarkXAMLCData,
                        ReferenceName = "xmlCDataSection",
                    },

                    new ColorCode.Styling.Style(ScopeName.XmlComment)
                    {
                        Foreground = OneDarkXMLComment,
                        ReferenceName = "xmlComment",
                    },

                    new ColorCode.Styling.Style(ScopeName.XmlDelimiter)
                    {
                        Foreground = OneDarkXMLDelimiter,
                        ReferenceName = "xmlDelimiter",
                    },
                    new ColorCode.Styling.Style(ScopeName.XmlName)
            {
                Foreground = OneDarkXMLName,
                ReferenceName = "xmlName",
            },
                };
    }

    public static HtmlElementType TagToType(this string tag)
    {
        return tag.ToLower() switch
        {
            "address" or "article" or "aside" or "details" or "blockquote" or "canvas" or "dd" or "div" or "dl" or "dt" or "fieldset" or "figcaption" or "figure" or "footer" or "form" or "h1" or "h2" or "h3" or "h4" or "h5" or "h6" or "header" or "hr" or "li" or "main" or "nav" or "noscript" or "ol" or "p" or "pre" or "section" or "table" or "tfoot" or "ul" => HtmlElementType.Block,
            _ => HtmlElementType.Inline,
        };
    }

    public static bool IsHeading(this string tag)
    {
        var headings = new List<string>() { "h1", "h2", "h3", "h4", "h5", "h6" };
        return headings.Contains(tag.ToLower());
    }

    public static Size GetSvgSize(string svgString)
    {
        // Parse the SVG string as an XML document
        var svgDocument = XDocument.Parse(svgString);

        // Get the root element of the document
        var svgElement = svgDocument.Root;

        // Get the height and width attributes of the root element
        var heightAttribute = svgElement.Attribute("height");
        var widthAttribute = svgElement.Attribute("width");

        // Convert the attribute values to double
        _ = double.TryParse(heightAttribute?.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var height);
        _ = double.TryParse(widthAttribute?.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var width);

        // Return the height and width as a tuple
        return new(width, height);
    }

    public static Size GetMarkdownImageSize(LinkInline link)
    {
        if (link == null || !link.IsImage)
        {
            throw new ArgumentException("Link must be an image", nameof(link));
        }

        var url = link.Url;
        if (string.IsNullOrEmpty(url))
        {
            throw new ArgumentException("Link must have a valid URL", nameof(link));
        }

        // Try to parse the width and height from the URL
        var parts = url?.Split('=');
        if (parts?.Length == 2)
        {
            var dimensions = parts[1].Split('x');
            if (dimensions.Length == 2 && int.TryParse(dimensions[0], out var width) && int.TryParse(dimensions[1], out var height))
            {
                return new(width, height);
            }
        }

        // not using this one as it's seems to be from the HTML renderer
        //// Try to parse the width and height from the special attributes
        // var attributes = link.GetAttributes();
        // if (attributes != null && attributes.Properties != null)
        // {
        //    var width = attributes.Properties.FirstOrDefault(p => p.Key == "width")?.Value;
        //    var height = attributes.Properties.FirstOrDefault(p => p.Key == "height")?.Value;
        //    if (!string.IsNullOrEmpty(width) && !string.IsNullOrEmpty(height) && int.TryParse(width, out int w) && int.TryParse(height, out int h))
        //    {
        //        return new(w, h);
        //    }
        // }

        // Return default values if no width and height are found
        return new(0, 0);
    }

    public static SolidColorBrush GetAccentColorBrush()
    {
        // Create a UISettings object to get the accent color
        var uiSettings = new UISettings();

        // Get the accent color as a Color value
        var accentColor = uiSettings.GetColorValue(UIColorType.Accent);

        // Create a SolidColorBrush from the accent color
        var accentBrush = new SolidColorBrush(accentColor);

        return accentBrush;
    }
}
