using System.Reflection;
using System.Text;
using NTDLS.Helpers;
using ZelWiki.Engine.Function;
using ZelWiki.Engine.Implementation.Utility;
using ZelWiki.Engine.Library;
using ZelWiki.Engine.Library.Interfaces;
using ZelWiki.Library;
using ZelWiki.Models;
using ZelWiki.Models.DataModels;
using ZelWiki.Repository;
using Constants = ZelWiki.Engine.Library.Constants;

namespace ZelWiki.Engine.Implementation
{
    /// <summary>
    /// 处理函数调用.
    /// </summary>
    public class StandardFunctionHandler : IStandardFunctionHandler
    {
        private static FunctionPrototypeCollection? _collection;

        public FunctionPrototypeCollection Prototypes
        {
            get
            {
                if (_collection == null)
                {
                    _collection =
                        new FunctionPrototypeCollection(FunctionPrototypeCollection.WikiFunctionType.Standard);

                    #region

                    _collection.Add("##Snippet: <string>[name]");
                    _collection.Add("##Seq: <string>{key}='Default'");
                    _collection.Add("##Set: <string>[key] | <string>[value]");
                    _collection.Add("##Get: <string>[key]");
                    _collection.Add("##Color: <string>[color] | <string>[text]");
                    _collection.Add("##Tag: <string:infinite>[pageTags]");
                    _collection.Add(
                        "##SearchList: <string>[searchPhrase] | <string>{styleName(List,Full)}='Full' | <integer>{pageSize}='5' | <bool>{pageSelector}='true' | <bool>{allowFuzzyMatching}='false' | <bool>{showNamespace}='false'");
                    _collection.Add(
                        "##TagList: <string:infinite>[pageTags] | <integer>{Top}='1000' | <string>{styleName(List,Full)}='Full' | <bool>{showNamespace}='false'");
                    _collection.Add(
                        "##NamespaceGlossary: <string:infinite>[namespaces] | <integer>{Top}='1000' | <string>{styleName(List,Full)}='Full' | <bool>{showNamespace}='false'");
                    _collection.Add(
                        "##NamespaceList: <string:infinite>[namespaces] | <integer>{Top}='1000' | <string>{styleName(List,Full)}='Full' | <bool>{showNamespace}='false'");
                    _collection.Add(
                        "##TagGlossary: <string:infinite>[pageTags] | <integer>{Top}='1000' | <string>{styleName(List,Full)}='Full' | <bool>{showNamespace}='false'");
                    _collection.Add(
                        "##RecentlyModified: <integer>{Top}='1000' | <string>{styleName(List,Full)}='Full' | <bool>{showNamespace}='false'");
                    _collection.Add(
                        "##TextGlossary: <string>[searchPhrase] | <integer>{Top}='1000' | <string>{styleName(List,Full)}='Full' | <bool>{showNamespace}='false'");
                    _collection.Add("##Image: <string>[name] | <integer>{scale}='100' | <string>{altText}=''");
                    _collection.Add("##File: <string>[name] | <string>{linkText} | <bool>{showSize}='false'");
                    _collection.Add(
                        "##Related: <string>{styleName(List,Flat,Full)}='Full' | <integer>{pageSize}='10' | <bool>{pageSelector}='true'");
                    _collection.Add(
                        "##Similar: <integer>{similarity}='80' | <string>{styleName(List,Flat,Full)}='Full' | <integer>{pageSize}='10' | <bool>{pageSelector}='true'");
                    _collection.Add("##EditLink: <string>{linkText}='edit'");
                    _collection.Add("##Inject: <string>[pageName]");
                    _collection.Add("##Include: <string>[pageName]");
                    _collection.Add("##BR: <integer>{Count}='1'");
                    _collection.Add("##HR: <integer>{Height}='1'");
                    _collection.Add(
                        "##Revisions:<string>{styleName(Full,List)}='Full' | <integer>{pageSize}='5' | <bool>{pageSelector}='true' | <string>{pageName}=''");
                    _collection.Add(
                        "##Attachments:<string>{styleName(Full,List)}='Full' | <integer>{pageSize}='5' | <bool>{pageSelector}='true' | <string>{pageName}=''");
                    _collection.Add("##Title:");
                    _collection.Add("##Navigation:");
                    _collection.Add("##Name:");
                    _collection.Add("##SiteName:");
                    _collection.Add("##Namespace:");
                    _collection.Add("##Created:");
                    _collection.Add("##LastModified:");
                    _collection.Add("##AppVersion:");
                    _collection.Add(
                        "##ProfileGlossary: <integer>{Top}='1000' | <integer>{pageSize}='100' | <string>{searchToken}=''");
                    _collection.Add(
                        "##ProfileList: <integer>{Top}='1000' | <integer>{pageSize}='100' | <string>{searchToken}=''");

                    _collection.Add("##SystemEmojiCategoryList:");
                    _collection.Add("##SystemEmojiList:");

                    #endregion
                }

                return _collection;
            }
        }

        private static Page? GetPageFromPathInfo(string routeData)
        {
            routeData = NamespaceNavigation.CleanAndValidate(routeData);
            var page = PageRepository.GetPageRevisionByNavigation(routeData);
            return page;
        }

        private static void MergeUserVariables(ref IZelEngineState state, Dictionary<string, string> items)
        {
            foreach (var item in items)
            {
                state.Variables[item.Key] = item.Value;
            }
        }

        private static void MergeSnippets(ref IZelEngineState state, Dictionary<string, string> items)
        {
            foreach (var item in items)
            {
                state.Snippets[item.Key] = item.Value;
            }
        }

        /// <summary>
        /// 当匹配到合适的原型时，调用它来处理函数调用。
        /// </summary>
        /// <param name="state"></param>
        /// <param name="function"></param>
        /// <param name="scopeBody"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public HandlerResult Handle(IZelEngineState state, FunctionCall function, string? scopeBody = null)
        {
            switch (function.Name.ToLower())
            {
                case "profileglossary":
                {
                    if (!GlobalConfiguration.EnablePublicProfiles)
                    {
                        return new HandlerResult("公共配置文件已禁用.");
                    }

                    var html = new StringBuilder();
                    var refTag = state.GetNextQueryToken();
                    var pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
                    var pageSize = function.Parameters.Get<int>("pageSize");
                    var searchToken = function.Parameters.Get<string>("searchToken");
                    var topCount = function.Parameters.Get<int>("top");
                    var profiles = UsersRepository.GetAllPublicProfilesPaged(pageNumber, pageSize, searchToken);

                    var glossaryName = "glossary_" + new Random().Next(0, 1000000).ToString();
                    var alphabet = profiles.Select(p => p.AccountName.Substring(0, 1).ToUpper()).Distinct();

                    if (profiles.Any())
                    {
                        html.Append("<center>");
                        foreach (var alpha in alphabet)
                        {
                            html.Append("<a href=\"#" + glossaryName + "_" + alpha + "\">" + alpha + "</a>&nbsp;");
                        }

                        html.Append("</center>");

                        html.Append("<ul>");
                        foreach (var alpha in alphabet)
                        {
                            html.Append("<li><a name=\"" + glossaryName + "_" + alpha + "\">" + alpha + "</a></li>");

                            html.Append("<ul>");
                            foreach (var profile in profiles.Where(p =>
                                         p.AccountName.ToLower().StartsWith(alpha.ToLower())))
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/Profile/{profile.Navigation}/Public\">{profile.AccountName}</a>");
                                html.Append("</li>");
                            }

                            html.Append("</ul>");
                        }

                        html.Append("</ul>");
                    }

                    return new HandlerResult(html.ToString());
                }
                
                case "profilelist":
                {
                    if (!GlobalConfiguration.EnablePublicProfiles)
                    {
                        return new HandlerResult("公共配置文件已禁用.");
                    }

                    var html = new StringBuilder();
                    var refTag = state.GetNextQueryToken();
                    var pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
                    var pageSize = function.Parameters.Get<int>("pageSize");
                    var searchToken = function.Parameters.Get<string>("searchToken");
                    var profiles = UsersRepository.GetAllPublicProfilesPaged(pageNumber, pageSize, searchToken);

                    if (profiles.Any())
                    {
                        html.Append("<ul>");

                        foreach (var profile in profiles)
                        {
                            html.Append(
                                $"<li><a href=\"{GlobalConfiguration.BasePath}/Profile/{profile.Navigation}/Public\">{profile.AccountName}</a>");
                            html.Append("</li>");
                        }

                        html.Append("</ul>");
                    }

                    if (profiles.Count > 0 && profiles.First().PaginationPageCount > 1)
                    {
                        html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString,
                            profiles.First().PaginationPageCount));
                    }

                    return new HandlerResult(html.ToString());
                }
                case "attachments":
                {
                    var refTag = state.GetNextQueryToken();

                    var pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));

                    var navigation =
                        NamespaceNavigation.CleanAndValidate(function.Parameters.Get("pageName",
                            state.Page.Navigation));
                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var pageSize = function.Parameters.Get<int>("pageSize");
                    var pageSelector = function.Parameters.Get<bool>("pageSelector");
                    var attachments =
                        PageFileRepository.GetPageFilesInfoByPageNavigationAndPageRevisionPaged(navigation, pageNumber,
                            pageSize, state.Revision);
                    var html = new StringBuilder();

                    if (attachments.Count > 0)
                    {
                        html.Append("<ul>");
                        foreach (var file in attachments)
                        {
                            if (state.Revision != null)
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/Page/Binary/{state.Page.Navigation}/{file.FileNavigation}/{state.Revision}\">{file.Name}</a>");
                            }
                            else
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/Page/Binary/{state.Page.Navigation}/{file.FileNavigation}\">{file.Name} </a>");
                            }

                            if (styleName == "full")
                            {
                                html.Append($" - ({file.FriendlySize})");
                            }

                            html.Append("</li>");
                        }

                        html.Append("</ul>");

                        if (pageSelector && attachments.Count > 0 && attachments.First().PaginationPageCount > 1)
                        {
                            html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString,
                                attachments.First().PaginationPageCount));
                        }
                    }

                    return new HandlerResult(html.ToString());
                }
                case "revisions":
                {
                    if (state.Session == null)
                    {
                        throw new Exception($"session异常.");
                    }

                    var refTag = state.GetNextQueryToken();

                    var pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));

                    var navigation =
                        NamespaceNavigation.CleanAndValidate(function.Parameters.Get("pageName",
                            state.Page.Navigation));
                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var pageSize = function.Parameters.Get<int>("pageSize");
                    var pageSelector = function.Parameters.Get<bool>("pageSelector");
                    var revisions =
                        PageRepository.GetPageRevisionsInfoByNavigationPaged(navigation, pageNumber, null, null,
                            pageSize);
                    var html = new StringBuilder();

                    if (revisions.Any())
                    {
                        html.Append("<ul>");
                        foreach (var item in revisions)
                        {
                            html.Append(
                                $"<li><a href=\"{GlobalConfiguration.BasePath}/{item.Navigation}/{item.Revision}\">{item.Revision} by {item.ModifiedByUserName} on {state.Session.LocalizeDateTime(item.ModifiedDate)}</a>");

                            if (styleName == "full")
                            {
                                var thisRev =
                                    PageRepository.GetPageRevisionByNavigation(state.Page.Navigation, item.Revision);
                                var prevRev =
                                    PageRepository.GetPageRevisionByNavigation(state.Page.Navigation,
                                        item.Revision - 1);

                                var summaryText = Differentiator.GetComparisonSummary(thisRev?.Body ?? string.Empty,
                                    prevRev?.Body ?? string.Empty);

                                if (summaryText.Length > 0)
                                {
                                    html.Append(" - " + summaryText);
                                }
                            }

                            html.Append("</li>");
                        }

                        html.Append("</ul>");

                        if (pageSelector && revisions.Count > 0 && revisions.First().PaginationPageCount > 1)
                        {
                            html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString,
                                revisions.First().PaginationPageCount));
                        }
                    }

                    return new HandlerResult(html.ToString());
                }
                case "seq": //##Seq({Key})   在文档中插入序列.
                {
                    var key = function.Parameters.Get<string>("Key");

                    var sequences = state.GetStateValue("_sequences", new Dictionary<string, int>());

                    if (sequences.ContainsKey(key) == false)
                    {
                        sequences.Add(key, 0);
                    }

                    sequences[key]++;

                    return new HandlerResult(sequences[key].ToString())
                    {
                        Instructions = [Constants.HandlerResultInstruction.OnlyReplaceFirstMatch]
                    };
                }
                case "editlink": //(##EditLink(link text))
                {
                    var linkText = function.Parameters.Get<string>("linkText");
                    return new HandlerResult($"<a href=\"{GlobalConfiguration.BasePath}" +
                                             NamespaceNavigation.CleanAndValidate($"/{state.Page.Navigation}/Edit") +
                                             $"\">{linkText}</a>");
                }
                case "inject": //(PageName)
                {
                    var navigation = function.Parameters.Get<string>("pageName");

                    var page = GetPageFromPathInfo(navigation);
                    if (page != null)
                    {
                        return new HandlerResult(page.Body)
                        {
                            Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
                        };
                    }

                    throw new Exception($"未找到包含页面: [{navigation}]");
                }
                
                case "include": //(PageName)
                {
                    var navigation = function.Parameters.Get<string>("pageName");

                    var page = GetPageFromPathInfo(navigation);
                    if (page != null)
                    {
                        var childState = state.TransformChild(page);

                        MergeUserVariables(ref state, childState.Variables);
                        MergeSnippets(ref state, childState.Snippets);

                        return new HandlerResult(childState.HtmlResult)
                        {
                            Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
                        };
                    }

                    throw new Exception($"T未找到包含页面: [{navigation}]");
                }
                case "set":
                {
                    var key = function.Parameters.Get<string>("key");
                    var value = function.Parameters.Get<string>("value");

                    if (state.Variables.ContainsKey(key))
                    {
                        state.Variables[key] = value;
                    }
                    else
                    {
                        state.Variables.Add(key, value);
                    }

                    return new HandlerResult(string.Empty)
                    {
                        Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
                    };
                }
                case "get":
                {
                    var key = function.Parameters.Get<string>("key");

                    if (state.Variables.TryGetValue(key, out var variable))
                    {
                        return new HandlerResult(variable);
                    }

                    throw new Exception(
                        $"未定义变量 {key} .需要在调用Get()前设置##Set().");
                }
                case "color":
                {
                    var color = function.Parameters.Get<string>("color");
                    var text = function.Parameters.Get<string>("text");

                    return new HandlerResult($"<font color=\"{color}\">{text}</font>");
                }
                case "tag": //##tag(pipe|separated|list|of|tags)
                {
                    var tags = function.Parameters.GetList<string>("pageTags");
                    state.Tags.AddRange(tags);
                    state.Tags = state.Tags.Distinct().ToList();

                    return new HandlerResult(string.Empty)
                    {
                        Instructions = [Constants.HandlerResultInstruction.TruncateTrailingLine]
                    };
                }
                case "image": //##Image(Name, [optional:default=100]Scale, [optional:default=""]Alt-Text)
                {
                    var imageName = function.Parameters.Get<string>("name");
                    var alt = function.Parameters.Get("alttext", imageName);
                    var scale = function.Parameters.Get<int>("scale");

                    var explicitNamespace = imageName.Contains("::");
                    var isPageForeignImage = false;

                    var navigation = state.Page.Navigation;
                    if (imageName.StartsWith("http", StringComparison.InvariantCultureIgnoreCase))
                    {
                        string image =
                            $"<a href=\"{imageName}\" target=\"_blank\"><img src=\"{imageName}\" border=\"0\" alt=\"{alt}\" /></a>";
                        return new HandlerResult(image);
                    }
                    else if (imageName.Contains('/'))
                    {
                        //允许从其他页面加载附加图像.
                        var slashIndex = imageName.IndexOf("/");
                        navigation = NamespaceNavigation.CleanAndValidate(imageName.Substring(0, slashIndex));
                        imageName = imageName.Substring(slashIndex + 1);
                        isPageForeignImage = true;
                    }

                    if (explicitNamespace == false && state.Page.Namespace != null)
                    {
                        if (PageFileRepository.GetPageFileAttachmentInfoByPageNavigationPageRevisionAndFileNavigation(
                                navigation, NamespaceNavigation.CleanAndValidate(imageName), state.Revision) == null)
                        {
                            //如果图像不存在，也没有指定命名空间，但页面有命名空间，则默认为页面命名空间.
                            navigation = NamespaceNavigation.CleanAndValidate($"{state.Page.Namespace}::{imageName}");
                        }
                    }

                    if (state.Revision != null && isPageForeignImage == false)
                    {
                        var link =
                            $"/Page/Image/{navigation}/{NamespaceNavigation.CleanAndValidate(imageName)}/{state.Revision}";
                        var image =
                            $"<a href=\"{GlobalConfiguration.BasePath}{link}\" target=\"_blank\"><img src=\"{GlobalConfiguration.BasePath}{link}?Scale={scale}\" border=\"0\" alt=\"{alt}\" /></a>";
                        return new HandlerResult(image);
                    }
                    else
                    {
                        var link = $"/Page/Image/{navigation}/{NamespaceNavigation.CleanAndValidate(imageName)}";
                        var image =
                            $"<a href=\"{GlobalConfiguration.BasePath}{link}\" target=\"_blank\"><img src=\"{GlobalConfiguration.BasePath}{link}?Scale={scale}\" border=\"0\" alt=\"{alt}\" /></a>";
                        return new HandlerResult(image);
                    }
                }
                
                //显示文件下载链接
                case "file": //##file(Name | Alt-Text | [optional display file size] true/false)
                {
                    var fileName = function.Parameters.Get<string>("name");

                    var explicitNamespace = fileName.Contains("::");
                    var isPageForeignFile = false;

                    var navigation = state.Page.Navigation;
                    if (fileName.Contains('/'))
                    {
                        //允许从其他页面加载附加图像。
                        var slashIndex = fileName.IndexOf("/");
                        navigation = NamespaceNavigation.CleanAndValidate(fileName.Substring(0, slashIndex));
                        fileName = fileName.Substring(slashIndex + 1);
                        isPageForeignFile = true;
                    }

                    if (explicitNamespace == false && state.Page.Namespace != null)
                    {
                        if (PageFileRepository.GetPageFileAttachmentInfoByPageNavigationPageRevisionAndFileNavigation(
                                navigation, NamespaceNavigation.CleanAndValidate(fileName), state.Revision) == null)
                        {
                            navigation = NamespaceNavigation.CleanAndValidate($"{state.Page.Namespace}::{fileName}");
                        }
                    }

                    var attachment =
                        PageFileRepository.GetPageFileAttachmentInfoByPageNavigationPageRevisionAndFileNavigation(
                            navigation, NamespaceNavigation.CleanAndValidate(fileName), state.Revision);
                    if (attachment != null)
                    {
                        var alt = function.Parameters.Get("linkText", fileName);

                        if (function.Parameters.Get<bool>("showSize"))
                        {
                            alt += $" ({attachment.FriendlySize})";
                        }

                        if (state.Revision != null && isPageForeignFile == false)
                        {
                            var link =
                                $"/Page/Binary/{navigation}/{NamespaceNavigation.CleanAndValidate(fileName)}/{state.Revision}";
                            var image = $"<a href=\"{GlobalConfiguration.BasePath}{link}\">{alt}</a>";
                            return new HandlerResult(image);
                        }
                        else
                        {
                            var link = $"/Page/Binary/{navigation}/{NamespaceNavigation.CleanAndValidate(fileName)}";
                            var image = $"<a href=\"{GlobalConfiguration.BasePath}{link}\">{alt}</a>";
                            return new HandlerResult(image);
                        }
                    }

                    throw new Exception($"File not found [{fileName}]");
                }
                case "recentlymodified": //##RecentlyModified(TopCount)
                {
                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var takeCount = function.Parameters.Get<int>("top");
                    var showNamespace = function.Parameters.Get<bool>("showNamespace");

                    var pages = PageRepository.GetTopRecentlyModifiedPagesInfo(takeCount)
                        .OrderByDescending(o => o.ModifiedDate).ThenBy(o => o.Title).ToList();

                    var html = new StringBuilder();

                    if (pages.Any())
                    {
                        html.Append("<ul>");
                        foreach (var page in pages)
                        {
                            if (showNamespace)
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Name}</a>");
                            }
                            else
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                            }

                            if (styleName == "full")
                            {
                                if (page?.Description?.Length > 0)
                                {
                                    html.Append(" - " + page.Description);
                                }
                            }

                            html.Append("</li>");
                        }

                        html.Append("</ul>");
                    }

                    return new HandlerResult(html.ToString());
                }
                case "namespaceglossary":
                {
                    var glossaryName = "glossary_" + new Random().Next(0, 1000000).ToString();
                    var namespaces = function.Parameters.GetList<string>("namespaces");

                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var topCount = function.Parameters.Get<int>("top");
                    var pages = PageRepository.GetPageInfoByNamespaces(namespaces).Take(topCount).OrderBy(o => o.Name)
                        .ToList();
                    var html = new StringBuilder();
                    var alphabet = pages.Select(p => p.Title.Substring(0, 1).ToUpper()).Distinct();
                    var showNamespace = function.Parameters.Get<bool>("showNamespace");

                    if (pages.Any())
                    {
                        html.Append("<center>");
                        foreach (var alpha in alphabet)
                        {
                            html.Append("<a href=\"#" + glossaryName + "_" + alpha + "\">" + alpha + "</a>&nbsp;");
                        }

                        html.Append("</center>");

                        html.Append("<ul>");
                        foreach (var alpha in alphabet)
                        {
                            html.Append("<li><a name=\"" + glossaryName + "_" + alpha + "\">" + alpha + "</a></li>");

                            html.Append("<ul>");
                            foreach (var page in pages.Where(p => p.Title.ToLower().StartsWith(alpha.ToLower())))
                            {
                                if (showNamespace)
                                {
                                    html.Append(
                                        $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Name}</a>");
                                }
                                else
                                {
                                    html.Append(
                                        $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                                }

                                if (styleName == "full")
                                {
                                    if (page?.Description?.Length > 0)
                                    {
                                        html.Append(" - " + page.Description);
                                    }
                                }

                                html.Append("</li>");
                            }

                            html.Append("</ul>");
                        }

                        html.Append("</ul>");
                    }

                    return new HandlerResult(html.ToString());
                }
                case "namespacelist":
                {
                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var topCount = function.Parameters.Get<int>("top");
                    var namespaces = function.Parameters.GetList<string>("namespaces");
                    var showNamespace = function.Parameters.Get<bool>("showNamespace");

                    var pages = PageRepository.GetPageInfoByNamespaces(namespaces).Take(topCount).OrderBy(o => o.Name)
                        .ToList();
                    var html = new StringBuilder();

                    if (pages.Any())
                    {
                        html.Append("<ul>");

                        foreach (var page in pages)
                        {
                            if (showNamespace)
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Name}</a>");
                            }
                            else
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                            }

                            if (styleName == "full")
                            {
                                if (page?.Description?.Length > 0)
                                {
                                    html.Append(" - " + page.Description);
                                }
                            }

                            html.Append("</li>");
                        }

                        html.Append("</ul>");
                    }

                    return new HandlerResult(html.ToString());
                }
                case "tagglossary":
                {
                    var glossaryName = "glossary_" + new Random().Next(0, 1000000).ToString();
                    var tags = function.Parameters.GetList<string>("pageTags");

                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var topCount = function.Parameters.Get<int>("top");
                    var pages = PageRepository.GetPageInfoByTags(tags).Take(topCount).OrderBy(o => o.Name).ToList();
                    var html = new StringBuilder();
                    var alphabet = pages.Select(p => p.Title.Substring(0, 1).ToUpper()).Distinct();
                    var showNamespace = function.Parameters.Get<bool>("showNamespace");

                    if (pages.Any())
                    {
                        html.Append("<center>");
                        foreach (var alpha in alphabet)
                        {
                            html.Append("<a href=\"#" + glossaryName + "_" + alpha + "\">" + alpha + "</a>&nbsp;");
                        }

                        html.Append("</center>");

                        html.Append("<ul>");
                        foreach (var alpha in alphabet)
                        {
                            html.Append("<li><a name=\"" + glossaryName + "_" + alpha + "\">" + alpha + "</a></li>");

                            html.Append("<ul>");
                            foreach (var page in pages.Where(p => p.Title.ToLower().StartsWith(alpha.ToLower())))
                            {
                                if (showNamespace)
                                {
                                    html.Append(
                                        $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Name}</a>");
                                }
                                else
                                {
                                    html.Append(
                                        $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                                }

                                if (styleName == "full")
                                {
                                    if (page?.Description?.Length > 0)
                                    {
                                        html.Append(" - " + page.Description);
                                    }
                                }

                                html.Append("</li>");
                            }

                            html.Append("</ul>");
                        }

                        html.Append("</ul>");
                    }

                    return new HandlerResult(html.ToString());
                }
                case "textglossary":
                {
                    var glossaryName = "glossary_" + new Random().Next(0, 1000000).ToString();
                    var searchTokens = function.Parameters.Get<string>("searchPhrase")
                        .Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList();
                    var topCount = function.Parameters.Get<int>("top");
                    var showNamespace = function.Parameters.Get<bool>("showNamespace");

                    var pages = PageRepository.PageSearch(searchTokens).Take(topCount).OrderBy(o => o.Name).ToList();
                    var html = new StringBuilder();
                    var alphabet = pages.Select(p => p.Title.Substring(0, 1).ToUpper()).Distinct();
                    var styleName = function.Parameters.Get<string>("styleName").ToLower();

                    if (pages.Count() > 0)
                    {
                        html.Append("<center>");
                        foreach (var alpha in alphabet)
                        {
                            html.Append("<a href=\"#" + glossaryName + "_" + alpha + "\">" + alpha + "</a>&nbsp;");
                        }

                        html.Append("</center>");

                        html.Append("<ul>");
                        foreach (var alpha in alphabet)
                        {
                            html.Append("<li><a name=\"" + glossaryName + "_" + alpha + "\">" + alpha + "</a></li>");

                            html.Append("<ul>");
                            foreach (var page in pages.Where(p => p.Title.ToLower().StartsWith(alpha.ToLower())))
                            {
                                if (showNamespace)
                                {
                                    html.Append(
                                        $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Name}</a>");
                                }
                                else
                                {
                                    html.Append(
                                        $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                                }

                                if (styleName == "full")
                                {
                                    if (page?.Description?.Length > 0)
                                    {
                                        html.Append(" - " + page.Description);
                                    }
                                }

                                html.Append("</li>");
                            }

                            html.Append("</ul>");
                        }

                        html.Append("</ul>");
                    }

                    return new HandlerResult(html.ToString());
                }
                case "searchlist":
                {
                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var refTag = state.GetNextQueryToken();
                    var pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
                    var pageSize = function.Parameters.Get<int>("pageSize");
                    var pageSelector = function.Parameters.Get<bool>("pageSelector");
                    var allowFuzzyMatching = function.Parameters.Get<bool>("allowFuzzyMatching");
                    var searchTokens = function.Parameters.Get<string>("searchPhrase")
                        .Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList();
                    var showNamespace = function.Parameters.Get<bool>("showNamespace");

                    var pages = PageRepository.PageSearchPaged(searchTokens, pageNumber, pageSize, allowFuzzyMatching);
                    var html = new StringBuilder();

                    if (pages.Any())
                    {
                        html.Append("<ul>");

                        foreach (var page in pages)
                        {
                            if (showNamespace)
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Name}</a>");
                            }
                            else
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                            }

                            if (styleName == "full")
                            {
                                if (page?.Description?.Length > 0)
                                {
                                    html.Append(" - " + page.Description);
                                }
                            }

                            html.Append("</li>");
                        }

                        html.Append("</ul>");
                    }

                    if (pageSelector && (pageNumber > 1 || (pages.Count > 0 && pages.First().PaginationPageCount > 1)))
                    {
                        html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString,
                            pages.FirstOrDefault()?.PaginationPageCount ?? 1));
                    }

                    return new HandlerResult(html.ToString());
                }
                case "taglist":
                {
                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var topCount = function.Parameters.Get<int>("top");
                    var tags = function.Parameters.GetList<string>("pageTags");
                    var showNamespace = function.Parameters.Get<bool>("showNamespace");

                    var pages = PageRepository.GetPageInfoByTags(tags).Take(topCount).OrderBy(o => o.Name).ToList();
                    var html = new StringBuilder();

                    if (pages.Any())
                    {
                        html.Append("<ul>");

                        foreach (var page in pages)
                        {
                            if (showNamespace)
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Name}</a>");
                            }
                            else
                            {
                                html.Append(
                                    $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                            }

                            if (styleName == "full")
                            {
                                if (page?.Description?.Length > 0)
                                {
                                    html.Append(" - " + page.Description);
                                }
                            }

                            html.Append("</li>");
                        }

                        html.Append("</ul>");
                    }

                    return new HandlerResult(html.ToString());
                }
                case "similar": //##Similar()
                {
                    var refTag = state.GetNextQueryToken();

                    var similarity = function.Parameters.Get<int>("similarity");
                    var pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
                    var pageSize = function.Parameters.Get<int>("pageSize");
                    var pageSelector = function.Parameters.Get<bool>("pageSelector");
                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var html = new StringBuilder();

                    var pages = PageRepository.GetSimilarPagesPaged(state.Page.Id, similarity, pageNumber, pageSize);

                    if (styleName == "list")
                    {
                        html.Append("<ul>");
                        foreach (var page in pages)
                        {
                            html.Append(
                                $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                        }

                        html.Append("</ul>");
                    }
                    else if (styleName == "flat")
                    {
                        foreach (var page in pages)
                        {
                            if (html.Length > 0) html.Append(" | ");
                            html.Append(
                                $"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                        }
                    }
                    else if (styleName == "full")
                    {
                        html.Append("<ul>");
                        foreach (var page in pages)
                        {
                            html.Append(
                                $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a> - {page.Description}");
                        }

                        html.Append("</ul>");
                    }

                    if (pageSelector && pages.Count > 0 && pages.First().PaginationPageCount > 1)
                    {
                        html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString,
                            pages.First().PaginationPageCount));
                    }

                    return new HandlerResult(html.ToString());
                }
                case "related": //##related
                {
                    var refTag = state.GetNextQueryToken();

                    var pageNumber = int.Parse(state.QueryString[refTag].ToString().DefaultWhenNullOrEmpty("1"));
                    var pageSize = function.Parameters.Get<int>("pageSize");
                    var pageSelector = function.Parameters.Get<bool>("pageSelector");
                    var styleName = function.Parameters.Get<string>("styleName").ToLower();
                    var html = new StringBuilder();

                    var pages = PageRepository.GetRelatedPagesPaged(state.Page.Id, pageNumber, pageSize);

                    if (styleName == "list")
                    {
                        html.Append("<ul>");
                        foreach (var page in pages)
                        {
                            html.Append(
                                $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                        }

                        html.Append("</ul>");
                    }
                    else if (styleName == "flat")
                    {
                        foreach (var page in pages)
                        {
                            if (html.Length > 0) html.Append(" | ");
                            html.Append(
                                $"<a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a>");
                        }
                    }
                    else if (styleName == "full")
                    {
                        html.Append("<ul>");
                        foreach (var page in pages)
                        {
                            html.Append(
                                $"<li><a href=\"{GlobalConfiguration.BasePath}/{page.Navigation}\">{page.Title}</a> - {page.Description}");
                        }

                        html.Append("</ul>");
                    }

                    if (pageSelector && pages.Count > 0 && pages.First().PaginationPageCount > 1)
                    {
                        html.Append(PageSelectorGenerator.Generate(refTag, state.QueryString,
                            pages.First().PaginationPageCount));
                    }

                    return new HandlerResult(html.ToString());
                }
                case "lastmodified":
                {
                    if (state.Session == null)
                    {
                        throw new Exception($"Session异常.");
                    }

                    if (state.Page.ModifiedDate != DateTime.MinValue)
                    {
                        var localized = state.Session.LocalizeDateTime(state.Page.ModifiedDate);
                        return new HandlerResult($"{localized.ToShortDateString()} {localized.ToShortTimeString()}");
                    }

                    return new HandlerResult(string.Empty);
                }
                case "created":
                {
                    if (state.Session == null)
                    {
                        throw new Exception($"Session异常.");
                    }

                    if (state.Page.CreatedDate != DateTime.MinValue)
                    {
                        var localized = state.Session.LocalizeDateTime(state.Page.CreatedDate);
                        return new HandlerResult($"{localized.ToShortDateString()} {localized.ToShortTimeString()}");
                    }

                    return new HandlerResult(string.Empty);
                }
                case "appversion":
                {
                    var version = string.Join('.', (Assembly.GetExecutingAssembly()
                        .GetName().Version?.ToString() ?? "0.0.0.0").Split('.').Take(3)); //Major.Minor.Patch

                    return new HandlerResult(version);
                }
                case "name":
                {
                    return new HandlerResult(state.Page.Title);
                }
                case "sitename":
                {
                    return new HandlerResult(GlobalConfiguration.Name);
                }
                case "title":
                {
                    return new HandlerResult($"<h1>{state.Page.Title}</h1>");
                }
                case "namespace":
                {
                    return new HandlerResult(state.Page.Namespace ?? string.Empty);
                }
                case "snippet":
                {
                    var name = function.Parameters.Get<string>("name");

                    return state.Snippets.ContainsKey(name) ? new HandlerResult(state.Snippets[name]) : new HandlerResult(string.Empty);
                }
                
                case "br":
                case "nl":
                case "newline": //##NewLine([optional:default=1]count)
                {
                    var lineBreaks = new StringBuilder();
                    var count = function.Parameters.Get<int>("Count");
                    for (var i = 0; i < count; i++)
                    {
                        lineBreaks.Append("<br />");
                    }

                    return new HandlerResult(lineBreaks.ToString());
                }
                case "hr":
                {
                    var size = function.Parameters.Get<int>("height");
                    return new HandlerResult($"<hr class=\"mt-{size} mb-{size}\">");
                }
                
                case "navigation":
                {
                    return new HandlerResult(state.Page.Navigation);
                }
                case "systememojilist":
                {
                    StringBuilder html = new();

                    html.Append($"<table class=\"table table-striped table-bordered \">");

                    html.Append($"<thead>");
                    html.Append($"<tr>");
                    html.Append($"<td><strong>Name</strong></td>");
                    html.Append($"<td><strong>Image</strong></td>");
                    html.Append($"<td><strong>Shortcut</strong></td>");
                    html.Append($"</tr>");
                    html.Append($"</thead>");

                    var category = state.QueryString["Category"].ToString();

                    html.Append($"<tbody>");

                    if (string.IsNullOrWhiteSpace(category) == false)
                    {
                        var emojis = EmojiRepository.GetEmojisByCategory(category);

                        foreach (var emoji in emojis)
                        {
                            html.Append($"<tr>");
                            html.Append($"<td>{emoji.Name}</td>");
                            //html.Append($"<td><img src=\"/images/emoji/{emoji.Path}\" /></td>");
                            html.Append(
                                $"<td><img src=\"{GlobalConfiguration.BasePath}/File/Emoji/{emoji.Name.ToLower()}\" /></td>");
                            html.Append($"<td>{emoji.Shortcut}</td>");
                            html.Append($"</tr>");
                        }
                    }

                    html.Append($"</tbody>");
                    html.Append($"</table>");

                    return new HandlerResult(html.ToString())
                    {
                        Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing]
                    };
                }
                case "systememojicategorylist":
                {
                    var categories = EmojiRepository.GetEmojiCategoriesGrouped();

                    StringBuilder html = new();

                    html.Append($"<table class=\"table table-striped table-bordered \">");

                    var rowNumber = 0;

                    html.Append($"<thead>");
                    html.Append($"<tr>");
                    html.Append($"<td><strong>Name</strong></td>");
                    html.Append($"<td><strong>Count of Emojis</strong></td>");
                    html.Append($"</tr>");
                    html.Append($"</thead>");

                    foreach (var category in categories)
                    {
                        if (rowNumber == 1)
                        {
                            html.Append($"<tbody>");
                        }

                        html.Append($"<tr>");
                        html.Append(
                            $"<td><a href=\"{GlobalConfiguration.BasePath}/wiki_help::list_of_emojis_by_category?category={category.Category}\">{category.Category}</a></td>");
                        html.Append($"<td>{category.EmojiCount:N0}</td>");
                        html.Append($"</tr>");
                        rowNumber++;
                    }

                    html.Append($"</tbody>");
                    html.Append($"</table>");

                    return new HandlerResult(html.ToString())
                    {
                        Instructions = [Constants.HandlerResultInstruction.DisallowNestedProcessing]
                    };
                }
            }

            return new HandlerResult() { Instructions = [Constants.HandlerResultInstruction.Skip] };
        }
    }
}