/// <summary>
        /// Create a new instance of the <see cref="LetterViewerMenuWrapper"/> class.
        /// </summary>
        /// <param name="mail">The mail to create the menu for.</param>
        /// <exception cref="ArgumentNullException">The specified <paramref name="mail"/> is <c>null</c>.</exception>
        public LetterViewerMenuWrapper(Mail mail)
        {
            if (mail == null)
            {
                throw new ArgumentNullException(nameof(mail));
            }

            string textContent = mail.Text.Equals("") ? " " : mail.Text;

            switch (mail)
            {
                case ItemMail itemMail:
                    this.letterMenu =  LetterViewerMenuEx2.CreateItemMailMenu(itemMail.Id, textContent, itemMail.AttachedItems);
                    break;
                case MoneyMail moneyMail:
                    this.letterMenu = LetterViewerMenuEx2.CreateMoneyMailMenu(moneyMail.Id, textContent, moneyMail.AttachedMoney, moneyMail.Currency);
                    break;
                case RecipeMail recipeMail:
                    this.letterMenu = LetterViewerMenuEx2.CreateRecipeMailMenu(recipeMail.Id, textContent, recipeMail.Recipe);
                    break;
                case QuestMail questMail:
                    this.letterMenu = LetterViewerMenuEx2.CreateQuestMailMenu(questMail.Id, textContent, questMail.QuestId, questMail.IsAutomaticallyAccepted);
                    break;
                default:
                    this.letterMenu = new LetterViewerMenuEx2(mail.Id, textContent);
                    break;
            }

            this.letterMenu.exitFunction = new IClickableMenu.onExit(OnExit);
        }
            /// <summary>
            /// Create a new instance of the <see cref="LetterViewerMenuEx2"/> class.
            /// </summary>
            /// <param name="id">The ID of the mail.</param>
            /// <param name="text">The text content of the mail.</param>
            /// <param name="money">The monetaray value attached to the mail.</param>
            /// <param name="currency">The currency of the specified <paramref name="money"/>.</param>
            /// <returns>The created <see cref="LetterViewerMenuEx2"/> instance.</returns>
            public static LetterViewerMenuEx2 CreateMoneyMailMenu(string id, string text, int money, Currency currency)
            {
                var menu = new LetterViewerMenuEx2(id, text)
                {
                    mailType = MailType.MoneyMail,

                    MoneyIncluded = money,
                    currency = currency
                };

                // Add mentary value to the player's account.
                switch (currency)
                {
                    case Currency.Money:
                        Game1.player.Money += money;
                        break;
                    case Currency.QiCoins:
                        Game1.player.clubCoins += money;
                        break;
                    case Currency.StarTokens:
                        Game1.player.festivalScore += money;
                        break;
                }

                return menu;
            }
            /// <summary>
            /// Create a new instance of the <see cref="LetterViewerMenuEx2"/> class.
            /// </summary>
            /// <param name="id">The ID of the mail.</param>
            /// <param name="text">The text content of the mail.</param>
            /// <param name="questId">The ID of the quest included in the mail.</param>
            /// <param name="isAutomaticallyAccepted">
            /// Indicates whether the included quest is automatically accepted when the mail is opened or if the
            /// player needs to manually accept it.
            /// </param>
            /// <returns>The created <see cref="LetterViewerMenuEx2"/> instance.</returns>
            public static LetterViewerMenuEx2 CreateQuestMailMenu(string id, string text, int questId, bool isAutomaticallyAccepted)
            {
                var menu = new LetterViewerMenuEx2(id, text)
                {
                    mailType        = MailType.QuestMail,
                    attachedQuestId = questId < 1 ? QUEST_ID_NO_QUEST : questId,
                };

                // If the ID does not represent an existing quest, we don't include it in the mail.
                if (menu.attachedQuestId == QUEST_ID_NO_QUEST)
                {
                    menu.QuestId = QUEST_ID_NO_QUEST;
                    return(menu);
                }

                // Add the quest to the player's quest log if it is an automatically accepted quest.
                if (isAutomaticallyAccepted)
                {
                    Game1.player.addQuest(questId);

                    menu.questAccepted = true;
                    menu.QuestId       = QUEST_ID_NO_QUEST;

                    return(menu);
                }

                // Specified quest has to be manually accepted by the player -> setup [quest accept] button in the menu.

                menu.QuestId = questId;

                string label = Game1.content.LoadString("Strings\\UI:AcceptQuest");

                menu.acceptQuestButton = new ClickableComponent(
                    new Rectangle(menu.xPositionOnScreen + menu.width / 2 - 128, menu.yPositionOnScreen + menu.height - 128,
                                  (int)Game1.dialogueFont.MeasureString(label).X + 24,
                                  (int)Game1.dialogueFont.MeasureString(label).Y + 24),
                    "")
                {
                    myID            = region_acceptQuestButton,
                    rightNeighborID = region_forwardButton,
                    leftNeighborID  = region_backButton
                };

                menu.backButton.rightNeighborID   = region_acceptQuestButton;
                menu.forwardButton.leftNeighborID = region_acceptQuestButton;

                if (!Game1.options.SnappyMenus)
                {
                    return(menu);
                }

                menu.populateClickableComponentList();
                menu.snapToDefaultClickableComponent();

                return(menu);
            }
            /// <summary>
            /// Create a new instance of the <see cref="LetterViewerMenuEx2"/> class.
            /// </summary>
            /// <param name="id">The ID of the mail.</param>
            /// <param name="text">The text content of the mail.</param>
            /// <param name="attachedItems">The items attached to the mail. Can be <c>null</c>.</param>
            /// <returns>The created <see cref="LetterViewerMenuEx2"/> instance.</returns>
            public static LetterViewerMenuEx2 CreateItemMailMenu(string id, string text, IList <Item> attachedItems)
            {
                var menu = new LetterViewerMenuEx2(id, text)
                {
                    selectedItems = new List <Item>(),
                    mailType      = MailType.ItemMail
                };

                // If the mail has attached items, add them to the LetterViewerMenu so they will be shown when the
                // mail is drawn to the screen.
                if (attachedItems?.Count > 0)
                {
                    foreach (var item in attachedItems)
                    {
                        var attachedItemComponent = new ClickableComponent(
                            new Rectangle(menu.xPositionOnScreen + menu.width / 2 - 48, menu.yPositionOnScreen + menu.height - 32 - 96, 96, 96),
                            item)
                        {
                            myID            = region_itemGrabButton,
                            leftNeighborID  = region_backButton,
                            rightNeighborID = region_forwardButton
                        };

                        menu.itemsToGrab.Add(attachedItemComponent);
                    }

                    menu.backButton.rightNeighborID   = region_itemGrabButton;
                    menu.forwardButton.leftNeighborID = region_itemGrabButton;

                    if (!Game1.options.SnappyMenus)
                    {
                        return(menu);
                    }

                    menu.populateClickableComponentList();
                    menu.snapToDefaultClickableComponent();
                }

                return(menu);
            }
            /// <summary>
            /// Create a new instance of the <see cref="LetterViewerMenuEx2"/> class.
            /// </summary>
            /// <param name="id">The ID of the mail.</param>
            /// <param name="text">The text content of the mail.</param>
            /// <param name="recipe">The recipe attached to the mail.</param>
            /// <returns>The created <see cref="LetterViewerMenuEx2"/> instance.</returns>
            /// <exception cref="InvalidOperationException">If the specified recipe was not found.</exception>
            public static LetterViewerMenuEx2 CreateRecipeMailMenu(string id, string text, RecipeData recipe)
            {
                var menu = new LetterViewerMenuEx2(id, text)
                {
                    mailType = MailType.RecipeMail,
                };

                // If there is no recipe attached to the mail -> we are done.
                if (recipe == null)
                {
                    return menu;
                }

                menu.recipe = recipe;

                // Load the relevant recipe game asset to obtain the recipe's data.
                Dictionary<string, string> recipes = null;
                switch (recipe.Type)
                {
                    case RecipeType.Cooking:
                        // If the player already received the recipe -> don't attach the recipe to the mail.
                        if (Game1.player.cookingRecipes.ContainsKey(recipe.Name))
                        {
                            monitor.Log($"The player already learned the recipe \"{recipe.Name}\"!");
                            return menu;
                        }

                        recipes = Game1.content.Load<Dictionary<string, string>>("Data\\CookingRecipes");
                        break;
                    case RecipeType.Crafting:
                        // If the player already received the recipe -> don't attach the recipe to the mail.
                        if (Game1.player.craftingRecipes.ContainsKey(recipe.Name))
                        {
                            monitor.Log($"The player already learned the recipe \"{recipe.Name}\"!");
                            return menu;
                        }

                        recipes = Game1.content.Load<Dictionary<string, string>>("Data\\CraftingRecipes");
                        break;
                }

                // If the specified recipe is not available in the loaded recipe game asset, we throw an error
                if (!recipes.TryGetValue(recipe.Name, out string recipeData))
                {
                    monitor.Log($"A recipe with the name \"{recipe.Name}\" was not found!", LogLevel.Warn);
                    throw new InvalidOperationException($"Could not find a recipe with the specified name \"{recipe.Name}\"!");
                }

                // Add the recipe to the recipes the player already obtained.

                int translatedNameIndex;
                if (recipe.Type == RecipeType.Cooking)
                {
                    translatedNameIndex = 4; // See Data/CookingRecipes{.lg-LG}.xnb files
                    menu.CookingOrCrafting = Game1.content.LoadString("Strings\\UI:LearnedRecipe_cooking");
                    Game1.player.cookingRecipes.Add(recipe.Name, 0);
                }
                else
                {
                    translatedNameIndex = 5; // See Data/CraftingRecipes{.lg-LG}.xnb files
                    menu.CookingOrCrafting = Game1.content.LoadString("Strings\\UI:LearnedRecipe_crafting");
                    Game1.player.craftingRecipes.Add(recipe.Name, 0);
                }

                // Set the name of the recipe depending on the currently selected display language.
                string[] recipeParams = recipeData.Split('/');
                if (LocalizedContentManager.CurrentLanguageCode != LocalizedContentManager.LanguageCode.en)
                {
                    if (recipeParams.Length < translatedNameIndex + 1)
                    {
                        menu.LearnedRecipe = recipe.Name;
                        monitor.Log($"There is no translated name for the recipe \"{recipe.Name}\" available! Using the recipe name as a fallback name.",
                            LogLevel.Warn);
                    }
                    else
                    {
                        // Read the translated name field from the recipe asset.
                        menu.LearnedRecipe = recipeParams[translatedNameIndex];
                    }
                }
                else
                {
                    // Language is English -> use the supplied recipe name.
                    menu.LearnedRecipe = recipe.Name;
                }

                return menu;
            }