using System;
using System.Collections.Generic;
using System.Linq;
using common.GUIUtils;
using Net.Eagle0.Eagle.Api;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace eagle {
    public class CommandPanelController : MonoBehaviour {
        private const string Eagle0pediaCommandsBaseUrl = "https://pedia.eagle0.net/commands/";

        private static readonly Dictionary<
                AvailableCommand.SealedValueOneofCase,
                string> Eagle0pediaCommandAnchors = new() {
            { AvailableCommand.SealedValueOneofCase.AlmsCommand, "alms" },
            { AvailableCommand.SealedValueOneofCase.ApprehendOutlawCommand, "apprehend-outlaw" },
            { AvailableCommand.SealedValueOneofCase.ArmTroopsCommand, "arm-troops" },
            { AvailableCommand.SealedValueOneofCase.AttackDecisionCommand, "attack-decision" },
            { AvailableCommand.SealedValueOneofCase.BattleAftermathDecisionCommand,
              "battle-aftermath-decision" },
            { AvailableCommand.SealedValueOneofCase.ControlWeatherAvailableCommand,
              "control-weather" },
            { AvailableCommand.SealedValueOneofCase.DeclineQuestCommand, "decline-quest" },
            { AvailableCommand.SealedValueOneofCase.DefendCommand, "defend" },
            { AvailableCommand.SealedValueOneofCase.DiplomacyCommand, "diplomacy" },
            { AvailableCommand.SealedValueOneofCase.DivineCommand, "divine" },
            { AvailableCommand.SealedValueOneofCase.ExileVassalCommand, "exile-vassal" },
            { AvailableCommand.SealedValueOneofCase.FeastCommand, "feast" },
            { AvailableCommand.SealedValueOneofCase.HandleCapturedHeroCommand,
              "handle-captured-hero" },
            { AvailableCommand.SealedValueOneofCase.HandleRiotCrackDownAvailableCommand,
              "handle-riot" },
            { AvailableCommand.SealedValueOneofCase.HandleRiotDoNothingAvailableCommand,
              "handle-riot" },
            { AvailableCommand.SealedValueOneofCase.HandleRiotGiveAvailableCommand, "handle-riot" },
            { AvailableCommand.SealedValueOneofCase.HeroGiftCommand, "hero-gift" },
            { AvailableCommand.SealedValueOneofCase.ImproveCommand, "improve" },
            { AvailableCommand.SealedValueOneofCase.IssueOrdersCommand, "issue-orders" },
            { AvailableCommand.SealedValueOneofCase.ManagePrisonerCommand, "manage-prisoners" },
            { AvailableCommand.SealedValueOneofCase.MarchCommand, "march" },
            { AvailableCommand.SealedValueOneofCase.OrganizeTroopsCommand, "organize-troops" },
            { AvailableCommand.SealedValueOneofCase.PleaseRecruitMeCommand, "please-recruit-me" },
            { AvailableCommand.SealedValueOneofCase.ReconCommand, "recon" },
            { AvailableCommand.SealedValueOneofCase.RecruitHeroesCommand, "recruit-heroes" },
            { AvailableCommand.SealedValueOneofCase.ResolveAllianceOfferCommand,
              "resolve-alliance-offer" },
            { AvailableCommand.SealedValueOneofCase.ResolveBreakAllianceCommand,
              "resolve-break-alliance" },
            { AvailableCommand.SealedValueOneofCase.ResolveInvitationCommand,
              "resolve-invitation" },
            { AvailableCommand.SealedValueOneofCase.ResolveRansomOfferCommand,
              "resolve-ransom-offer" },
            { AvailableCommand.SealedValueOneofCase.ResolveTributeCommand, "resolve-tribute" },
            { AvailableCommand.SealedValueOneofCase.ResolveTruceOfferCommand,
              "resolve-truce-offer" },
            { AvailableCommand.SealedValueOneofCase.RestCommand, "rest" },
            { AvailableCommand.SealedValueOneofCase.ReturnCommand, "return" },
            { AvailableCommand.SealedValueOneofCase.SendSuppliesCommand, "send-supplies" },
            { AvailableCommand.SealedValueOneofCase.StartEpidemicCommand, "start-epidemic" },
            { AvailableCommand.SealedValueOneofCase.SuppressBeastsCommand, "suppress-beasts" },
            { AvailableCommand.SealedValueOneofCase.SwearBrotherhoodCommand, "swear-kinship" },
            { AvailableCommand.SealedValueOneofCase.TradeCommand, "trade" },
            { AvailableCommand.SealedValueOneofCase.TrainCommand, "train" },
            { AvailableCommand.SealedValueOneofCase.VisitTownCommand, "visit-town" },
        };

        private readonly struct CommandInfo {
            public readonly string Title;
            public readonly string Text;

            public CommandInfo(string title, string text) {
                Title = title;
                Text = text;
            }
        }

        private static readonly Dictionary<
                AvailableCommand.SealedValueOneofCase,
                CommandInfo> CommandInfos = new() {
            { AvailableCommand.SealedValueOneofCase.AlmsCommand,
              new CommandInfo(
                      "Give Alms",
                      "Spend food to raise support in the province. Paladins are especially effective at this.") },
            { AvailableCommand.SealedValueOneofCase.ApprehendOutlawCommand,
              new CommandInfo(
                      "Apprehend Outlaw",
                      "Send a hero to deal with an outlaw disrupting the province.") },
            { AvailableCommand.SealedValueOneofCase.ArmTroopsCommand,
              new CommandInfo(
                      "Arm Troops",
                      "Improve battalion armament by spending available resources.") },
            { AvailableCommand.SealedValueOneofCase.AttackDecisionCommand,
              new CommandInfo(
                      "Attack Decision",
                      "Choose how to resolve the strategic battle decision before tactical combat begins.") },
            { AvailableCommand.SealedValueOneofCase.BattleAftermathDecisionCommand,
              new CommandInfo("Battle Aftermath", "Choose what to do after a battle has ended.") },
            { AvailableCommand.SealedValueOneofCase.ControlWeatherAvailableCommand,
              new CommandInfo(
                      "Control Weather",
                      "Use a suitable hero to affect the weather in a province.") },
            { AvailableCommand.SealedValueOneofCase.DeclineQuestCommand,
              new CommandInfo("Decline Quest", "Refuse the quest currently being offered.") },
            { AvailableCommand.SealedValueOneofCase.DefendCommand,
              new CommandInfo(
                      "Defend",
                      "Assign heroes and battalions to defend a province and choose a fallback route.") },
            { AvailableCommand.SealedValueOneofCase.DiplomacyCommand,
              new CommandInfo(
                      "Diplomacy",
                      "Send a diplomatic offer or demand to another faction.") },
            { AvailableCommand.SealedValueOneofCase.DivineCommand,
              new CommandInfo(
                      "Divine",
                      "Ask a diviner for information about the world or another faction.") },
            { AvailableCommand.SealedValueOneofCase.ExileVassalCommand,
              new CommandInfo(
                      "Exile Vassal",
                      "Remove a vassal from your faction, forcing them out of your service.") },
            { AvailableCommand.SealedValueOneofCase.FeastCommand,
              new CommandInfo(
                      "Feast",
                      "Spend food and gold to improve loyalty and morale among your heroes.") },
            { AvailableCommand.SealedValueOneofCase.HandleCapturedHeroCommand,
              new CommandInfo(
                      "Handle Captured Hero",
                      "Choose whether to recruit, imprison, exile, execute, or return a captured hero.") },
            { AvailableCommand.SealedValueOneofCase.HandleRiotCrackDownAvailableCommand,
              new CommandInfo(
                      "Handle Riot",
                      "Crack down on a riot. This may restore order, but it is harsh.") },
            { AvailableCommand.SealedValueOneofCase.HandleRiotDoNothingAvailableCommand,
              new CommandInfo("Handle Riot", "Ignore a riot and accept the consequences.") },
            { AvailableCommand.SealedValueOneofCase.HandleRiotGiveAvailableCommand,
              new CommandInfo("Handle Riot", "Give resources to the populace to calm a riot.") },
            { AvailableCommand.SealedValueOneofCase.HeroGiftCommand,
              new CommandInfo("Give to Hero", "Give gold to a hero to improve loyalty.") },
            { AvailableCommand.SealedValueOneofCase.ImproveCommand,
              new CommandInfo(
                      "Improve Province",
                      "Use a hero to improve agriculture, economy, infrastructure, or devastation in the province.") },
            { AvailableCommand.SealedValueOneofCase.IssueOrdersCommand,
              new CommandInfo(
                      "Issue Orders",
                      "Give standing orders that influence how a faction or force behaves.") },
            { AvailableCommand.SealedValueOneofCase.ManagePrisonerCommand,
              new CommandInfo(
                      "Manage Prisoners",
                      "Release, move, exile, execute, or return imprisoned heroes.") },
            { AvailableCommand.SealedValueOneofCase.MarchCommand,
              new CommandInfo(
                      "March",
                      "Move heroes, battalions, gold, and food from one province to another.") },
            { AvailableCommand.SealedValueOneofCase.OrganizeTroopsCommand,
              new CommandInfo(
                      "Organize Troops",
                      "Create, merge, split, or disband battalions in the province.") },
            { AvailableCommand.SealedValueOneofCase.PleaseRecruitMeCommand,
              new CommandInfo(
                      "Recruit Offer",
                      "Respond to a hero who wants to join your faction.") },
            { AvailableCommand.SealedValueOneofCase.ReconCommand,
              new CommandInfo("Recon", "Send a hero to gather information about a province.") },
            { AvailableCommand.SealedValueOneofCase.RecruitHeroesCommand,
              new CommandInfo(
                      "Recruit Heroes",
                      "Spend gold to recruit available heroes into your faction.") },
            { AvailableCommand.SealedValueOneofCase.ResolveAllianceOfferCommand,
              new CommandInfo(
                      "Alliance Offer",
                      "Accept or reject another faction's alliance offer.") },
            { AvailableCommand.SealedValueOneofCase.ResolveBreakAllianceCommand,
              new CommandInfo(
                      "Break Alliance",
                      "Resolve a diplomatic request to end an alliance.") },
            { AvailableCommand.SealedValueOneofCase.ResolveInvitationCommand,
              new CommandInfo(
                      "Invitation",
                      "Accept or reject an invitation involving one of your heroes.") },
            { AvailableCommand.SealedValueOneofCase.ResolveRansomOfferCommand,
              new CommandInfo(
                      "Ransom Offer",
                      "Accept or reject a ransom offer for imprisoned heroes.") },
            { AvailableCommand.SealedValueOneofCase.ResolveTributeCommand,
              new CommandInfo(
                      "Tribute Demand",
                      "Respond to another faction's demand for tribute.") },
            { AvailableCommand.SealedValueOneofCase.ResolveTruceOfferCommand,
              new CommandInfo("Truce Offer", "Accept or reject another faction's truce offer.") },
            { AvailableCommand.SealedValueOneofCase.RestCommand,
              new CommandInfo(
                      "Rest",
                      "Let heroes recover vigor and prepare for future commands.") },
            { AvailableCommand.SealedValueOneofCase.ReturnCommand,
              new CommandInfo(
                      "Return",
                      "Bring a hero back from their current assignment or situation.") },
            { AvailableCommand.SealedValueOneofCase.SendSuppliesCommand,
              new CommandInfo(
                      "Send Supplies",
                      "Send food or gold to another province with a selected hero.") },
            { AvailableCommand.SealedValueOneofCase.StartEpidemicCommand,
              new CommandInfo(
                      "Start Epidemic",
                      "Use a suitable hero to unleash disease in a province.") },
            { AvailableCommand.SealedValueOneofCase.SuppressBeastsCommand,
              new CommandInfo(
                      "Suppress Beasts",
                      "Send a hero to deal with beasts threatening a province.") },
            { AvailableCommand.SealedValueOneofCase.SwearBrotherhoodCommand,
              new CommandInfo(
                      "Swear Kinship",
                      "Bind heroes together to improve their relationship and loyalty.") },
            { AvailableCommand.SealedValueOneofCase.TradeCommand,
              new CommandInfo(
                      "Trade",
                      "Exchange food and gold using the province's buy and sell prices.") },
            { AvailableCommand.SealedValueOneofCase.TrainCommand,
              new CommandInfo("Train", "Have a hero train to improve their abilities.") },
            { AvailableCommand.SealedValueOneofCase.VisitTownCommand,
              new CommandInfo(
                      "Visit Town",
                      "Have the ruling hero visit the current province's town to unlock local commands.") },
        };

        public EagleGameController mainController;

        public CommandSelector _selector;
        public CommandSelector Selector {
            get => _selector;
        private
            set {
                if (_selector != value) {
                    if (_selector != null) _selector.Hide();
                    _selector = value;
                    if (_selector != null) _selector.Show();
                }
            }
        }
        public SelectedCommand SelectedCommand => Selector == null ? null : Selector.Command;

        public TMP_Text headerLabel;
        public Button commitCommandButton;
        public TextMeshProUGUI commitButtonText;
        public Button infoButton;
        public TextMeshProUGUI underAttackText;
        public Transform specializationLayout;
        public IGameModel Model { get; set; }
        public ErrorHandler errorHandler;
        public CommandWarningPanelController commandWarningPanelController;
        public CommandWarningPanelController commandInfoPanelController;

        private AvailableCommand _availableCommand;
        private bool _commandInfoShowing;
        public AvailableCommand AvailableCommand {
            get => _availableCommand;
        private
            set {
                if (_availableCommand == null || !_availableCommand.Equals(value)) {
                    _availableCommand = value;
                }
            }
        }

        public void RefreshUI() {
            if (Selector != null) { Selector.RefreshUI(); }
        }

        public void UpdateSelectorLayoutMode(LayoutMode mode) {
            if (Selector != null && Selector.CurrentLayoutMode != mode) {
                if (!Selector.SupportsLayoutMode(mode) && AvailableCommand != null) {
                    var replacementSelector = commandSelectors.Find(
                            sel => sel.IsAppropriateFor(AvailableCommand) &&
                                   sel.SupportsLayoutMode(mode));
                    if (replacementSelector != null) {
                        Selector = replacementSelector;
                        Selector.CurrentLayoutMode = mode;
                        Selector.UpdateAvailableCommand(Model, AvailableCommand);
                        headerLabel.text = Selector.HeaderString;
                        RebuildHeaderLayout();
                        return;
                    }
                }
                Selector.CurrentLayoutMode = mode;
                Selector.OnLayoutModeChanged();
            }
        }

        public void SetAvailableCommandAndSelector(AvailableCommand cmd, CommandSelector sel) {
            // Check if command help is currently showing before changing selector
            bool helpWasShowing = _commandInfoShowing;

            AvailableCommand = cmd;
            Selector = sel;

            try {
                if (AvailableCommand == null) {
                    Selector = null;
                    headerLabel.text = null;
                    UpdateCommandControls();
                    RebuildHeaderLayout();
                    return;
                }

                // Pass layout mode to selector before setting up UI
                Selector.CurrentLayoutMode = mainController.CurrentLayoutMode;
                Selector.UpdateAvailableCommand(Model, AvailableCommand);
                headerLabel.text = Selector.HeaderString;
                UpdateCommandControls();
                RebuildHeaderLayout();

                // If help was showing for previous command, show help for new command
                if (helpWasShowing) { ShowCurrentCommandHelp(); }
            } catch (Exception e) {
                Debug.LogException(e);
                throw;
            }
        }

        private void RebuildHeaderLayout() {
            // The title's width depends on the active header controls. Rebuild both now so the
            // old text geometry cannot render for a frame in the new layout.
            LayoutRebuilder.ForceRebuildLayoutImmediate(
                    (RectTransform)headerLabel.transform.parent);
            headerLabel.ForceMeshUpdate();
        }

        private void UpdateCommandControls() {
            if (Selector == null) {
                commitCommandButton.gameObject.SetActive(false);
                if (infoButton != null) { infoButton.gameObject.SetActive(false); }
                return;
            }

            bool clearWarningPanel = !commitCommandButton.gameObject.activeSelf;

            if (Selector.WarnOnCommitButton) {
                commitButtonText.text = $"<color=#FF0000FF>{Selector.CommitButtonString}</color>";
            } else {
                commitButtonText.text = Selector.CommitButtonString;
            }
            commitCommandButton.gameObject.SetActive(true);
            commitCommandButton.interactable = Selector.EnableCommitButton;

            if (infoButton != null) { infoButton.gameObject.SetActive(true); }

            if (clearWarningPanel) {
                MainQueue.Q.EnqueueForNextUpdate(() => {
                    commandWarningPanelController.ClearWarning();
                    _commandInfoShowing = false;
                });
            }
        }

        /// <summary>
        /// Shows help for the current command (used when switching commands while help is open).
        /// </summary>
        private void ShowCurrentCommandHelp() {
            if (Selector == null) return;

            var info = GetCurrentCommandInfo();
            commandInfoPanelController.SetInfo(info.Title, info.Text, (RectTransform)transform);
            _commandInfoShowing = true;
        }

        private void ToggleCurrentCommandHelp() {
            if (_commandInfoShowing) {
                commandInfoPanelController.ClearWarning();
                _commandInfoShowing = false;
            } else {
                ShowCurrentCommandHelp();
            }
        }

        public void InfoButtonHovered() {
            if (Selector == null) return;

            ShowCurrentCommandHelp();
        }

        public void InfoButtonExited() {
            if (!_commandInfoShowing) return;
            commandInfoPanelController.ClearWarning();
            _commandInfoShowing = false;
        }

        private CommandInfo GetCurrentCommandInfo() {
            if (Selector != null && CommandInfos.TryGetValue(Selector.CommandType, out var info)) {
                return info;
            }

            return new CommandInfo(
                    Selector?.HeaderString ?? "Command",
                    "Open the command reference for complete details.");
        }

        public List<CommandSelector> AppropriateSelectors(AvailableCommand cmd) =>
                commandSelectors.FindAll(
                        sel => sel.IsAppropriateFor(cmd) &&
                               sel.SupportsLayoutMode(mainController.CurrentLayoutMode));

        public List<GUIContent> ButtonContentsForCommand(AvailableCommand cmd) {
            return (from sel in AppropriateSelectors(cmd)
                    where sel.ButtonImage != null
                    select new GUIContent(sel.ButtonImage, sel.HeaderString)).ToList();
        }

        private List<CommandSelector> commandSelectors;

        public void Awake() {
            commandSelectors = new List<CommandSelector>();

            foreach (Transform child in specializationLayout) {
                var sel = child.gameObject.GetComponent<CommandSelector>();
                if (sel != null) { commandSelectors.Add(sel); }
            }

            foreach (var kvp in commandSelectors) {
                kvp.gameObject.SetActive(true);
                kvp.Hide();
            }
        }

        public void Start() {
            foreach (var kvp in commandSelectors) { kvp.gameObject.SetActive(false); }
        }

        public void Update() {
            // '?' key (Shift + /) toggles help panel when a command is selected
            if (Selector != null && GameInput.QuestionMarkPressed()) { ToggleCurrentCommandHelp(); }
        }

        public void PointerEnteredCommitButton() {
            if (Selector != null) {
                if (!Selector.EnableCommitButton) {
                    _commandInfoShowing = false;
                    if (string.IsNullOrEmpty(Selector.DisabledCommitButtonReason)) {
                        commandWarningPanelController.SetWarning(
                                "Invalid",
                                "CROSBY NEEDS TO PUT SOME TEXT HERE");
                    } else {
                        commandWarningPanelController.SetWarning(
                                "Invalid",
                                Selector.DisabledCommitButtonReason);
                    }
                } else if (Selector.WarnOnCommitButton) {
                    _commandInfoShowing = false;
                    commandWarningPanelController.SetWarning(
                            "Are you sure?",
                            Selector.CommitWarningText);
                }
            }
        }

        public void PointerExitedCommitButton() { commandWarningPanelController.ClearWarning(); }

        /// <summary>
        /// Called when the info button is clicked. Opens the full command reference.
        /// </summary>
        public void InfoButtonClicked() {
            if (Selector == null) return;

            string url = GetCurrentCommandPediaUrl();
            if (url != null) { Application.OpenURL(url); }
        }

        private string GetCurrentCommandPediaUrl() {
            if (Selector == null) return null;

            return Eagle0pediaCommandAnchors.TryGetValue(Selector.CommandType, out var anchor)
                           ? $"{Eagle0pediaCommandsBaseUrl}#{anchor}"
                           : Eagle0pediaCommandsBaseUrl;
        }

        public void OnGUI() {
            var eventType = Event.current.type;
            try {
                UpdateCommandControls();
            } catch (Exception e) {
                Debug.Log($"Event type is {eventType}");
                errorHandler.Add(e);
            }
        }
    }
}
