using System;
using System.Collections.Generic;
using System.Linq;
using common;
using common.GUIUtils;
using Net.Eagle0.Eagle.Api;
using Net.Eagle0.Eagle.Common;
using Net.Eagle0.Eagle.Views;
using Shardok;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace eagle {
    using FactionId = Int32;
    using ProvinceId = Int32;

    public class EagleGameController : MonoBehaviour {
        public SoundManager soundManager;

        public RollPanelController rollPanelController;

        public TextMeshProUGUI roundStatusLabel;
        public TextMeshProUGUI widthLabel;
        public TextMeshProUGUI heightLabel;

        public GameObject alwaysOnLeftColumn;
        public GameObject factionsAndMovingArmiesRow;
        public GameObject wideScreenRightColumn;
        public GameObject wideScreenBottomRow;

        public ProvinceInfoPanelController provinceInfoPanelController;
        public FreeHeroesTableController freeHeroesTableController;
        public FactionsTableController factionsTableController;
        public MovingArmiesTableController movingArmiesTableController;
        public HeroesAndBattalionsPanelController heroesAndBattalionsPanelController;
        public MapController mapController;
        public CommandButtonPanelController commandButtonPanelController;
        public DominionPanelController hideableDominionPanelController;
        public DominionPanelController wideDominionPanelController;
        private DominionPanelController _dominionPanelController;
        public ChronicleCanvasController chronicleCanvasController;

        public ProvinceId? SelectedProvince => mapController.SelectedProvinceId;

        public ErrorHandler errorHandler;
        public NotificationPanel notificationPanel;
        public GameObject popupCanvas;

        public Button showChronicleButton;
        public Button commitCommandButton;
        public Button goToBattleButton;
        public TextMeshProUGUI goToBattleButtonText;
        public Button gameIdButton;
        public Button nextActiveProvinceButton;

        public MouseHandler mouseHandlerPrefab;

        public GameObject commandPanel;
        private CommandPanelController _commandPanelController;

        public PleaseRecruitMeCommandSelector pleaseRecruitMeCommandSelector;
        public ResolveTruceCommandSelector resolveTruceCommandSelector;
        public ResolveAllianceCommandSelector resolveAllianceCommandSelector;
        public ResolveBreakAllianceCommandSelector resolveBreakAllianceCommandSelector;
        public ResolveInvitationCommandSelector resolveInvitationCommandSelector;
        public ResolveRansomOfferCommandSelector resolveRansomOfferCommandSelector;
        public CommandWarningPanelController commandWarningPanelController;

        private double _aspectRatio;
        private bool IsWideScreen => _aspectRatio > 2.0;

        private int _lastWidth;
        private int _lastHeight;

        GameModelUpdater ModelUpdater { get; set; }
        IGameModel Model { get; set; }

        private DynamicFactionTextUpdater factionTextUpdater = new DynamicFactionTextUpdater();
        private IGameModel _newModel = null;

        public Canvas shardokCanvas;

        private KeyValuePair<ProvinceId, OneProvinceAvailableCommands> NextActiveProvinceKeyPair =>
                Model.AvailableCommandsByProvince.First(
                        kvp => kvp.Key == Model.SuggestedProvinceId);

        void Start() {
            gameIdButton.GetComponentInChildren<Text>().text = "";
            mapController.targetedProvincesChangedCallback = MapControllerChangedTarget;
            mapController.hoveredProvinceChangedCallback = MapControllerChangedHover;
            popupCanvas.SetActive(true);
            _dominionPanelController = hideableDominionPanelController;
            _dominionPanelController.gameObject.SetActive(false);
            showChronicleButton.gameObject.SetActive(false);

            errorHandler.gameObject.SetActive(true);

            ArrangeLayout();
        }

        private void ArrangeLayout() {
            var newWidth = Screen.width;
            var newHeight = Screen.height;

            if (newWidth == _lastWidth && newHeight == _lastHeight) { return; }

            widthLabel.text = newWidth.ToString();
            heightLabel.text = newHeight.ToString();

            _lastWidth = newWidth;
            _lastHeight = newHeight;

            _aspectRatio = (float)newWidth / (float)newHeight;

            if (IsWideScreen) {
                factionsTableController.gameObject.transform.SetParent(
                        wideScreenRightColumn.transform);
                factionsTableController.GetComponent<LayoutElement>().flexibleWidth = 1;
                movingArmiesTableController.gameObject.transform.SetParent(
                        wideScreenRightColumn.transform);
                movingArmiesTableController.GetComponent<LayoutElement>().flexibleWidth = 1;
                wideDominionPanelController.gameObject.SetActive(true);
                hideableDominionPanelController.gameObject.SetActive(false);
                _dominionPanelController = wideDominionPanelController;
                _dominionPanelController.Model = Model;

                factionsAndMovingArmiesRow.SetActive(false);

                provinceInfoPanelController.WideScreen = true;
            } else {
                factionsTableController.gameObject.transform.SetParent(
                        factionsAndMovingArmiesRow.transform);
                factionsTableController.GetComponent<LayoutElement>().flexibleWidth = 0;
                movingArmiesTableController.gameObject.transform.SetParent(
                        factionsAndMovingArmiesRow.transform);
                movingArmiesTableController.GetComponent<LayoutElement>().flexibleWidth = 0;
                wideDominionPanelController.gameObject.SetActive(false);
                _dominionPanelController = hideableDominionPanelController;
                _dominionPanelController.Model = Model;
                factionsAndMovingArmiesRow.SetActive(true);

                provinceInfoPanelController.WideScreen = false;
            }
        }

        public void StopAll() {
            _newModel = null;
            ModelUpdater.StopListeningForUpdates();
            ModelUpdater.UpdateAction = null;
            ModelUpdater = null;
            SwapModel();
            Model = null;
            chronicleCanvasController.Entries = new List<ChronicleEntry>();
            SetMusic();
        }

        void MapControllerChangedTarget(List<ProvinceId> newTarget) {
            if (_commandPanelController.Selector != null) {
                _commandPanelController.Selector.TargetedProvinces = newTarget;
                freeHeroesTableController.UpdateTables();
                movingArmiesTableController.UpdateTables();
                factionsTableController.UpdateTables();
                heroesAndBattalionsPanelController.UpdateTables();
            }
        }

        void MapControllerChangedHover(ProvinceId? newHover) {
            _dominionPanelController.ProvinceHovered(newHover);
            movingArmiesTableController.ProvinceHovered(newHover);
            factionsTableController.ProvinceHovered(newHover);
        }

        void Update() {
            ArrangeLayout();
            if (_newModel != null) { SwapModel(); }

            if (Model != null) {
                if (_commandPanelController.Selector == null) {
                    if (Model.AvailableCommandsByProvince.Count > 0 &&
                        Model.AvailableCommandsByProvince.ContainsKey(0)) {
                        mapController.ProvincesWithCommands = new List<ProvinceId>();
                    } else {
                        mapController.ProvincesWithCommands =
                                Model.AvailableCommandsByProvince.Keys.ToList();
                    }
                    mapController.TargetedProvinces = new List<ProvinceId>();
                } else {
                    mapController.TargetedProvinces =
                            _commandPanelController.Selector.TargetedProvinces;
                    mapController.ProvincesWithCommands = new List<ProvinceId>();
                }
            }

            if (Input.GetButtonUp("Windowed Mode")) {
#if UNITY_EDITOR
                // do nothing
#else
                Screen.fullScreen = !Screen.fullScreen;
#endif
            }
        }

        public void ReloadFactionDisplaySetting() {
            SetHeaderString();
            mapController.ReloadFactionDisplaySetting();
            factionsTableController.UpdateTables();
            _commandPanelController.RefreshUI();
        }

        void OnApplicationPause(bool pause) {
            if (ModelUpdater == null) { return; }
            if (pause) {
                ModelUpdater.StopListeningForUpdates();
            } else {
                ModelUpdater.StartListeningForUpdates();
            }
        }

#if UNITY_EDITOR
        void OnPlayModeStateChanged(PlayModeStateChange state) {}
#endif

        public void SetUpGame(
                long gameId,
                int? playerId,
                PersistentClientConnection persistentClientConnection) {
            ModelUpdater = new GameModelUpdater(
                    gameId,
                    playerId,
                    persistentClientConnection,
                    rollPanelController) {
                UpdateAction = ModelUpdated,
                NoteRecipient = (title, text, llmId, pids, displayedHeroes) =>
                        notificationPanel.AddNote(title, text, llmId, pids, displayedHeroes)
            };

            commitCommandButton.gameObject.SetActive(false);

            _commandPanelController = commandPanel.GetComponent<CommandPanelController>();
            _commandPanelController.Model = Model;

            resolveTruceCommandSelector.submissionAction = sc => PostCommittedCommand(0, sc);
            resolveAllianceCommandSelector.submissionAction = sc => PostCommittedCommand(0, sc);
            resolveBreakAllianceCommandSelector.submissionAction = sc =>
                    PostCommittedCommand(0, sc);
            resolveInvitationCommandSelector.submissionAction = sc => PostCommittedCommand(0, sc);
            resolveRansomOfferCommandSelector.submissionAction = sc => PostCommittedCommand(0, sc);
            pleaseRecruitMeCommandSelector.submissionAction = sc => PostCommittedCommand(0, sc);

            ModelUpdater.ErrorHandler = errorHandler;

            MainQueue.Q.EnqueueForNextUpdate(() => { ModelUpdater.StartListeningForUpdates(); });

#if UNITY_EDITOR
            EditorApplication.playModeStateChanged += (state) => OnPlayModeStateChanged(state);
#endif
        }

        private void SelectRecommendedCommand(ProvinceId? provinceId) {
            OneProvinceAvailableCommands opac;
            if (provinceId is {} pid &&
                Model.AvailableCommandsByProvince.TryGetValue(pid, out opac)) {
                SelectButtonIndex(opac.SuggestedCommandIndex);  // probably wrong
            } else {
                _commandPanelController.SetAvailableCommandAndSelector(null, null);
                mapController.TargetedProvinces = new List<ProvinceId>();
            }
        }

        public void SelectNextActiveProvince() {
            mapController.SelectedProvinceId = NextActiveProvinceKeyPair.Key;
        }

        public void SelectButtonIndex(int buttonIndex) {
            var selector =
                    Model.AvailableCommandsForProvince(SelectedProvince.Value)
                            .SelectMany(cmd => _commandPanelController.AppropriateSelectors(cmd))
                            .ElementAt(buttonIndex);

            var newType = selector.CommandType;
            var cmd = Model.AvailableCommandsForProvince(SelectedProvince.Value)
                              .Find(c => c.SealedValueCase == newType);
            var commandIndex =
                    Model.AvailableCommandsForProvince(SelectedProvince.Value).IndexOf(cmd);

            _commandPanelController.SetAvailableCommandAndSelector(cmd, selector);
            mapController.ProvincesWithCommands = new List<ProvinceId>();
            movingArmiesTableController.UpdateTables();
            factionsTableController.UpdateTables();
            heroesAndBattalionsPanelController.UpdateTables();
        }

        private void UpdateButtons() {
            if (SelectedProvince.HasValue) {
                commandButtonPanelController.SetButtonContents(
                        Model.AvailableCommandsForProvince(SelectedProvince.Value)
                                .SelectMany(
                                        cmd => _commandPanelController.ButtonContentsForCommand(
                                                cmd))
                                .ToArray());
            } else {
                commandButtonPanelController.ClearButtonContents();
            }
        }

        private void SetNextActiveProvinceButton() {
            nextActiveProvinceButton.gameObject.SetActive(
                    _commandPanelController.Selector == null && Model != null &&
                    Model.AvailableCommandsByProvince.Count > 0);
        }

        public void ProvinceWasSelected(ProvinceId? pid) {
            if (pid.HasValue) {
                SelectRecommendedCommand(pid.Value);
                var currentProvince = Model.Provinces[pid.Value];
                provinceInfoPanelController.Province = currentProvince;
                freeHeroesTableController.CurrentProvince = currentProvince;
                movingArmiesTableController.CurrentProvince = currentProvince;
                factionsTableController.CurrentProvince = currentProvince;
                heroesAndBattalionsPanelController.CurrentProvince = currentProvince;
            } else {
                SelectRecommendedCommand(null);
                provinceInfoPanelController.Province = null;
                freeHeroesTableController.CurrentProvince = null;
                movingArmiesTableController.CurrentProvince = null;
                factionsTableController.CurrentProvince = null;
                heroesAndBattalionsPanelController.CurrentProvince = null;
            }

            UpdateButtons();
            SetNextActiveProvinceButton();
            _dominionPanelController.ForceUpdate();
            SetMusic();
        }

        private void PrefetchHeadshotForHeroes(List<HeroView> heroViews) {
            ResourceFetcher.headshotFetcher.Prefetch(heroViews.Select(h => h.ImagePath));
        }

        private void PrefetchHeadshots(IGameModel model) {
            var heroList = model.ActiveFactions.Values
                                   .Select(faction => model.Heroes[faction.FactionHeadId])
                                   .ToList();

            foreach (ProvinceView province in model.Provinces.Values) {
                if (province.FullInfo is FullProvinceInfo fpi) {
                    heroList.AddRange(fpi.RulingFactionHeroIds.Select(hid => model.Heroes[hid]));
                    heroList.AddRange(fpi.UnaffiliatedHeroes.Select(uh => uh.HeroId)
                                              .Select(hid => model.Heroes[hid]));
                }
            }

            PrefetchHeadshotForHeroes(heroList);
        }

        private void SetMusic() {
            if (Model == null) {
                soundManager.CurrentMusicType = SoundManager.MusicType.MusicNone;
                return;
            }
            if (SelectedProvince is ProvinceId pid) {
                var province = Model.Provinces[pid];
                if (province.RulingFactionId is FactionId fid && fid == Model.PlayerId &&
                    province.FullInfo.RulerIsTraveling) {
                    soundManager.CurrentMusicType = SoundManager.MusicType.MusicTraveling;
                    return;
                }
                if (Model.AvailableCommandsForProvince(pid).Exists(
                            x => x.HandleCapturedHeroCommand != null)) {
                    soundManager.CurrentMusicType = SoundManager.MusicType.MusicVictory;
                    return;
                }
            }

            if (Model.RunningShardokGameModels.Count > 0) {
                soundManager.CurrentMusicType = SoundManager.MusicType.MusicBattle;
            } else {
                switch (Model.CurrentDate.Month) {
                    case 12:
                    case 1:
                    case 2:
                        soundManager.CurrentMusicType = SoundManager.MusicType.MusicWinter;
                        break;

                    case 3:
                    case 4:
                    case 5:
                        soundManager.CurrentMusicType = SoundManager.MusicType.MusicSpring;
                        break;

                    case 6:
                    case 7:
                    case 8:
                        soundManager.CurrentMusicType = SoundManager.MusicType.MusicSummer;
                        break;

                    case 9:
                    case 10:
                    case 11:
                        soundManager.CurrentMusicType = SoundManager.MusicType.MusicAutumn;
                        break;

                    default:
                        soundManager.CurrentMusicType = SoundManager.MusicType.MusicSpring;
                        break;
                }
            }
        }

        void SetHeaderString() {
            var date = Model.CurrentDate;
            if (date != null) {
                string dateString = DisplayNames.DateString(date);
                if (Model.PlayerId.HasValue) {
                    var faction = Model.MaybeDestroyedFaction(Model.PlayerId.Value);
                    factionTextUpdater.SetFactionTextWithCustomFormat(
                            roundStatusLabel,
                            faction,
                            Model,
                            $"{dateString} - {{FactionLeaderName}} ({{Prestige}})",
                            $"{dateString} - Observer");
                } else {
                    roundStatusLabel.text = $"{dateString} - Observer";
                }
            }
        }

        void SwapModel() {
            if (_newModel == Model) { return; }

            showChronicleButton.gameObject.SetActive(
                    _newModel != null && _newModel.ChronicleEntries.Count > 1);

            if (_newModel != null &&
                _newModel.ChronicleEntries.Count != chronicleCanvasController.Entries.Count) {
                chronicleCanvasController.Entries = _newModel.ChronicleEntries;
            }

            // If we already had commands, and we still have commands, don't actually swap the
            // model.
            if (Model != null && _newModel != null && _newModel.AvailableCommandsByProvince.Any() &&
                Model.AvailableCommandsByProvince.Any()) {
                return;
            }

            var oldModel = Model;
            Model = _newModel;

            provinceInfoPanelController.Model = _newModel;
            freeHeroesTableController.Model = _newModel;
            movingArmiesTableController.Model = _newModel;
            factionsTableController.Model = _newModel;
            heroesAndBattalionsPanelController.Model = _newModel;
            _commandPanelController.Model = _newModel;
            _dominionPanelController.Model = _newModel;
            mapController.Model = _newModel;

            if (Model == null) { return; }

            PrefetchHeadshots(Model);

            SetMusic();

            if ((oldModel == null || oldModel.CommandToken != _newModel.CommandToken) &&
                Model.AvailableCommandsByProvince.Count > 0) {
                if (oldModel != null && oldModel.AvailableCommandsByProvince.Count > 0) {
                    string str = "We got replacement commands, but we already had commands. " +
                                 oldModel.CommandToken + " " + _newModel.CommandToken;

                    var oldProvinces = oldModel.AvailableCommandsByProvince.Keys.ToHashSet();
                    var newProvinces = _newModel.AvailableCommandsByProvince.Keys.ToHashSet();

                    if (oldProvinces != newProvinces) {
                        var oldNotNew = oldProvinces.Except(newProvinces);
                        if (oldNotNew.Any()) {
                            str += $"\nWe used to have commands for province {oldNotNew} but now we don't";
                        }

                        var newNotOld = newProvinces.Except(oldProvinces);
                        if (newNotOld.Any()) {
                            str += $"\nWe now have commands for provinces {newNotOld} where we didn't before";
                        }
                    }
                    foreach (var kv in oldModel.AvailableCommandsByProvince) {
                        if (_newModel.AvailableCommandsByProvince.ContainsKey(kv.Key)) {
                            var oldCommands = kv.Value;
                            var newCommands = _newModel.AvailableCommandsByProvince[kv.Key];
                            if (oldCommands.Commands.Count != newCommands.Commands.Count) {
                                str += $"\nWe had {oldCommands.Commands.Count} but now we have {newCommands.Commands.Count}";
                            }

                            foreach (var cmd in oldCommands.Commands) {
                                if (!newCommands.Commands.Contains(cmd)) {
                                    str += $"\nWe had {cmd} but now we don't";
                                }
                            }

                            foreach (var cmd in newCommands.Commands) {
                                if (!oldCommands.Commands.Contains(cmd)) {
                                    str += $"\nWe have {cmd}, we didn't before";
                                }
                            }
                        }
                    }
                    throw new InvalidOperationException(str);
                }

                var firstCommand = Model.AvailableCommandsByProvince.First().Value.Commands.First();
                switch (firstCommand.SealedValueCase) {
                    case AvailableCommand.SealedValueOneofCase.ResolveTruceOfferCommand:
                        resolveTruceCommandSelector.Model = Model;
                        resolveTruceCommandSelector.AvailableCommand = firstCommand;
                        _commandPanelController.SetAvailableCommandAndSelector(null, null);
                        mapController.TargetedProvinces = new List<ProvinceId>();
                        mapController.SelectedProvinceId = null;
                        break;

                    case AvailableCommand.SealedValueOneofCase.ResolveAllianceOfferCommand:
                        resolveAllianceCommandSelector.Model = Model;
                        resolveAllianceCommandSelector.AvailableCommand = firstCommand;
                        _commandPanelController.SetAvailableCommandAndSelector(null, null);
                        mapController.TargetedProvinces = new List<ProvinceId>();
                        mapController.SelectedProvinceId = null;
                        break;

                    case AvailableCommand.SealedValueOneofCase.ResolveBreakAllianceCommand:
                        resolveBreakAllianceCommandSelector.Model = Model;
                        resolveBreakAllianceCommandSelector.AvailableCommand = firstCommand;
                        _commandPanelController.SetAvailableCommandAndSelector(null, null);
                        mapController.TargetedProvinces = new List<ProvinceId>();
                        mapController.SelectedProvinceId = null;
                        break;

                    case AvailableCommand.SealedValueOneofCase.ResolveInvitationCommand:
                        resolveInvitationCommandSelector.Model = Model;
                        resolveInvitationCommandSelector.AvailableCommand = firstCommand;
                        _commandPanelController.SetAvailableCommandAndSelector(null, null);
                        mapController.TargetedProvinces = new List<ProvinceId>();
                        mapController.SelectedProvinceId = null;
                        break;

                    case AvailableCommand.SealedValueOneofCase.ResolveRansomOfferCommand:
                        resolveRansomOfferCommandSelector.Model = Model;
                        resolveRansomOfferCommandSelector.AvailableCommand = firstCommand;
                        _commandPanelController.SetAvailableCommandAndSelector(null, null);
                        mapController.TargetedProvinces = new List<ProvinceId>();
                        mapController.SelectedProvinceId = null;
                        break;

                    case AvailableCommand.SealedValueOneofCase.PleaseRecruitMeCommand:
                        pleaseRecruitMeCommandSelector.Model = Model;
                        pleaseRecruitMeCommandSelector.AvailableCommand = firstCommand;
                        _commandPanelController.SetAvailableCommandAndSelector(null, null);
                        mapController.TargetedProvinces = new List<ProvinceId>();
                        mapController.SelectedProvinceId = null;
                        break;

                    default:
                        // most command types will be here
                        var newlySelectedProvinceId = NextActiveProvinceKeyPair.Key;
                        mapController.SelectedProvinceId = newlySelectedProvinceId;
                        break;
                }
            }

            freeHeroesTableController.UpdateTables();
            movingArmiesTableController.UpdateTables();
            factionsTableController.UpdateTables();
            heroesAndBattalionsPanelController.UpdateTables();

            Canvas.ForceUpdateCanvases();
            SetHeaderString();

            goToBattleButton.gameObject.SetActive(Model.RunningShardokGameModels.Any());
            goToBattleButtonText.text =
                    Model.RunningShardokGameModels.Values.Any(sgm => sgm.PlayerId != -1)
                            ? "Battle!"
                            : "Observe Battle";

            _newModel = null;
            SetNextActiveProvinceButton();

            if (Model.AvailableCommandsByProvince.Count > 0 &&
                Model.AvailableCommandsByProvince.ContainsKey(0)) {
                mapController.ProvincesWithCommands = new List<ProvinceId>();
            } else {
                mapController.ProvincesWithCommands =
                        Model.AvailableCommandsByProvince.Keys.ToList();
            }
        }

        void ModelUpdated(IGameModel nm) {
            MainQueue.Q.Enqueue(() => { _newModel = nm; });
        }

        private void PostCommittedCommand(ProvinceId provinceId, SelectedCommand selectedCommand) {
            ModelUpdater.PostCommand(provinceId: provinceId, command: selectedCommand)
                    .ContinueWith(response => {
                        if (response.IsFaulted) { errorHandler.Add(response.Exception); }
                    });
        }

        private void DoCommitCommand() {
            PostCommittedCommand(
                    provinceId: mapController.SelectedProvinceId.GetValueOrDefault(),
                    _commandPanelController.SelectedCommand);
            commitCommandButton.gameObject.SetActive(false);
            _commandPanelController.SetAvailableCommandAndSelector(null, null);
            mapController.TargetedProvinces = new List<ProvinceId>();
            commandButtonPanelController.ClearButtonContents();
            freeHeroesTableController.UpdateTables();
            movingArmiesTableController.UpdateTables();
            factionsTableController.UpdateTables();
            heroesAndBattalionsPanelController.UpdateTables();
        }

        public void ChronicleButtonClicked() {
            chronicleCanvasController.gameObject.SetActive(true);
        }

        public void CommitCommandButtonClicked() {
            commandWarningPanelController.ClearWarning();
            DoCommitCommand();
        }

        public void GoToBattle() {
            // Hide the button now, so if we get an exception below we can at least recover
            goToBattleButton.gameObject.SetActive(false);

            ShardokGameModel selectedModel;

            var runningBattles = Model.RunningShardokGameModels.Values;
            var myBattles = runningBattles.Where(sgm => sgm.PlayerId != -1);
            if (myBattles.Any()) {
                selectedModel = myBattles.First();
            } else if (runningBattles.Any()) {
                selectedModel = runningBattles.First();
            } else {
                throw new ApplicationException(
                        "Go to Battle button clicked, but there are no battles");
            }

            var shardokModel = Model.ShardokGameModels[selectedModel.ShardokGameId];

            shardokCanvas.gameObject.SetActive(true);
            shardokCanvas.GetComponent<ShardokGameController>().SetUpGame(shardokModel);
            gameObject.SetActive(false);
        }

        public void GameIdButtonClicked() {
            var textField = gameIdButton.GetComponentInChildren<Text>();
            textField.text = textField.text == "" ? ModelUpdater.GameId.ToString("X") : "";
        }

        private void OnDestroy() { factionTextUpdater?.Dispose(); }
    }
}