/// <summary>
        /// Finds the exact tag for the input text.
        /// Does not perform partial matching or chain parsing.
        /// </summary>
        /// <param name="tagText">The input text to search for.</param>
        /// <returns>The matching tag, or null if not found.</returns>
        public MetaTag FindTag(string tagText)
        {
            string cleaned = MetaTag.CleanTag(tagText);

            if (Tags.TryGetValue(cleaned, out MetaTag result))
            {
                return(result);
            }
            // TODO: Chain searching
            int dotIndex = cleaned.IndexOf('.');

            if (dotIndex > 0)
            {
                string tagBase = cleaned.Substring(0, dotIndex);
                string secondarySearch;
                if (tagBase == "playertag" || tagBase == "npctag")
                {
                    // TODO: Object meta, to inform of down-typing like this?
                    secondarySearch = "entitytag" + cleaned.Substring(dotIndex);
                }
                else if (!tagBase.EndsWith("tag"))
                {
                    secondarySearch = tagBase + "tag" + cleaned.Substring(dotIndex);
                }
                else
                {
                    return(null);
                }
                return(FindTag(secondarySearch));
            }
            return(null);
        }
        /// <summary>
        /// Tag meta docs user command.
        /// </summary>
        public void CMD_Tag(string[] cmds, SocketMessage message)
        {
            List <string> secondarySearches = new List <string>();

            if (cmds.Length > 0)
            {
                cmds[0] = MetaTag.CleanTag(cmds[0]);
                int dotIndex = cmds[0].IndexOf('.');
                if (dotIndex > 0)
                {
                    string tagBase   = cmds[0].Substring(0, dotIndex);
                    string tagSuffix = cmds[0].Substring(dotIndex);
                    if (!tagBase.EndsWith("tag"))
                    {
                        secondarySearches.Add(tagBase + "tag" + tagSuffix);
                    }
                    string tagBaseLow = tagBase.ToLowerFast();
                    if (tagBaseLow == "player" || tagBaseLow == "npc" || tagBaseLow == "playertag" || tagBaseLow == "npctag")
                    {
                        secondarySearches.Add("entitytag" + tagSuffix);
                    }
                    secondarySearches.Add("elementtag" + tagSuffix);
                }
            }
            int getDistanceTo(MetaTag tag)
            {
                int dist1 = StringConversionHelper.GetLevenshteinDistance(cmds[0], tag.CleanedName);
                int dist2 = StringConversionHelper.GetLevenshteinDistance(cmds[0], tag.AfterDotCleaned);
                int dist  = Math.Min(dist1, dist2);

                foreach (string secondSearch in secondarySearches)
                {
                    int dist3 = StringConversionHelper.GetLevenshteinDistance(secondSearch, tag.CleanedName);
                    dist = Math.Min(dist, dist3);
                }
                return(dist);
            }

            string findClosestTag()
            {
                int    lowestDistance = 20;
                string lowestStr      = null;

                foreach (MetaTag tag in Program.CurrentMeta.Tags.Values)
                {
                    int currentDistance = getDistanceTo(tag);
                    if (currentDistance < lowestDistance)
                    {
                        lowestDistance = currentDistance;
                        lowestStr      = tag.CleanedName;
                    }
                }
                return(lowestStr);
            }

            AutoMetaCommand(Program.CurrentMeta.Tags, MetaDocs.META_TYPE_TAG, cmds, message, secondarySearches, altFindClosest: findClosestTag,
                            altMatchOrderer: (list) => list.OrderBy(getDistanceTo).ToList());
        }