#nullable enable
using Nox.Game;
using Nox.Platform;
using Nox.Game.UI;
using Jovian.SaveSystem;
using Jovian.InGameLogging;
using System.Collections.Generic;
using ZLinq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

namespace Nox.Core {
    /// <summary>
    /// The Game Mode Game State which is the base state in which all GameModes need to run.
    /// Game Modes are any modes that involve gameplay and gameplay related scenes. This state is where the game runs.
    /// </summary>
    public class GameModeGameState : IGameState {
        private readonly GameDataState gameDataState;
        private readonly PlatformSettings platformSettings;
        private readonly PlayModeSettings bootstrapSettings;
        private readonly ISaveSystem saveSystem;
        private readonly ISceneTransition sceneTransition;
        private readonly AdventureData adventureData;
        private readonly IGameLogStore gameLogStore;

        private readonly Dictionary<PlayMode, IPlayMode?> playModeCache = new();

        private IMenuView? pauseMenuView;
        private IPlayMode? playMode;
        private IPlayMode? suspendedPlayMode;
        private bool isTransitioning;

        private PlayMode currentPlaymode;
        private bool stateEntered;
        private AsyncOperationHandle<AdventureSettings> advSettingsHandler;
        private AdventureSettings? adventureSettings;

        public bool IsGameStateInitialized {
            get => stateEntered && (playMode == null || playMode.IsGameModeInitialized);
        }

        public GameModeGameState(GameDataState gameDataState,
            BootstrapReferences bootstrapReferences,
            PlatformSettings platformSettings,
            ISaveSystem saveSystem,
            ISceneTransition sceneTransition,
            AdventureData adventureData,
            IGameLogStore gameLogStore) {
            this.gameDataState = gameDataState;
            this.platformSettings = platformSettings;
            this.saveSystem = saveSystem;
            this.sceneTransition = sceneTransition;
            this.adventureData = adventureData;
            this.gameLogStore = gameLogStore;

            bootstrapSettings = Addressables.LoadAssetAsync<PlayModeSettings>(bootstrapReferences.playModeSettings).WaitForCompletion();
        }

        public void EnterGameState() {
            if(gameDataState.ActivePlayMode == PlayMode.None) {
                var sceneReference = Object.FindFirstObjectByType<SceneReference>();
                if(bootstrapSettings.gameModeData.AsValueEnumerable().Any(g => g.playMode == sceneReference.playMode)) {
                    gameDataState.ChangePlayMode(sceneReference.playMode);
                }
            }
            advSettingsHandler = Addressables.LoadAssetAsync<AdventureSettings>("AdventureSettings");
            adventureSettings = advSettingsHandler.WaitForCompletion();
            InitializeGameViews();
            currentPlaymode = gameDataState.ActivePlayMode;
            playMode?.EnterPlayMode();
            Debug.Log($"Initialized Game Mode: {gameDataState.ActivePlayMode}");
            stateEntered = true;
        }
        private void InitializeGameViews() {
            SwitchGameMode();
            pauseMenuView ??= new PauseMenuView(gameDataState, saveSystem, CaptureNoxSaveData);
        }

        private NoxSavedDataSet? CaptureNoxSaveData() {
            var adventure = FindAdventurePlayMode();
            var saveData = adventure?.CaptureNoxSaveData();
            if(saveData != null) {
                saveData.gameLogData = gameLogStore.GetSaveData();
            }
            return saveData;
        }

        private AdventurePlayMode? FindAdventurePlayMode() {
            if(playMode is AdventurePlayMode active) {
                return active;
            }
            if(suspendedPlayMode is AdventurePlayMode suspended) {
                return suspended;
            }
            if(playModeCache.TryGetValue(PlayMode.Adventure, out var cached) && cached is AdventurePlayMode fromCache) {
                return fromCache;
            }
            return null;
        }

        private void SwitchGameMode() {
            var activeParty = gameDataState.ActiveParty;

            if(playModeCache.TryGetValue(gameDataState.ActivePlayMode, out var cached)) {
                playMode = cached;
                return;
            }

            playMode = gameDataState.ActivePlayMode switch {
                PlayMode.Adventure => new AdventurePlayMode(platformSettings, activeParty, bootstrapSettings, gameDataState, saveSystem, adventureSettings, adventureData),
                PlayMode.PauseMenu => new PauseMenuPlayMode(platformSettings, gameDataState, pauseMenuView!),
                PlayMode.Town => new TownPlayMode(platformSettings, activeParty),
                PlayMode.Rest => new RestPlayMode(platformSettings, activeParty),
                PlayMode.Combat => new CombatPlayMode(platformSettings, activeParty),
                _ => playMode
            };

            playModeCache[gameDataState.ActivePlayMode] = playMode;
        }

        public GameState Tick() {
            if(isTransitioning) {
                return GameState.GameMode;
            }

            if(ShouldTransition()) {
                HandlePlayModeTransition();
                return GameState.GameMode;
            }

            if(playMode is { IsGameModeInitialized: true }) {
                playMode.Tick();
            }

            return GameState.GameMode;
        }

        private bool ShouldTransition() {
            return currentPlaymode != gameDataState.ActivePlayMode
                && gameDataState.ActivePlayMode != PlayMode.None;
        }

        private bool IsPauseTransition() {
            return gameDataState.ActivePlayMode == PlayMode.PauseMenu
                || (currentPlaymode == PlayMode.PauseMenu && suspendedPlayMode != null);
        }

        private void HandlePlayModeTransition() {
            if(IsPauseTransition()) {
                ExecutePlayModeSwitch();
                return;
            }

            isTransitioning = true;
            sceneTransition.FadeOut(() => {
                ExecutePlayModeSwitch();
                sceneTransition.FadeIn(() => {
                    isTransitioning = false;
                });
            });
        }

        private void ExecutePlayModeSwitch() {
            var enteringPause = gameDataState.ActivePlayMode == PlayMode.PauseMenu;
            var resumingFromPause = currentPlaymode == PlayMode.PauseMenu
                && suspendedPlayMode != null;

            if(enteringPause) {
                suspendedPlayMode = playMode;
            }

            playMode?.ExitGameMode();
            currentPlaymode = gameDataState.ActivePlayMode;

            if(resumingFromPause) {
                playMode = suspendedPlayMode;
                suspendedPlayMode = null;
                playMode?.EnterPlayMode();
                return;
            }

            SwitchGameMode();
            playMode?.EnterPlayMode();
        }

        public void LateTick() {
            if(playMode is { IsGameModeInitialized: true }) {
                playMode.LateTick();
            }
        }
        public void Dispose() {
            suspendedPlayMode?.Dispose();
            playMode?.Dispose();
        }
        public void ExitGameState() {
            suspendedPlayMode?.ExitGameMode();
            suspendedPlayMode?.Dispose();
            suspendedPlayMode = null;
            playMode?.ExitGameMode();
            playMode?.Dispose();
            playMode = null;

            foreach(var cached in playModeCache.Values) {
                if(cached != null && cached != playMode && cached != suspendedPlayMode) {
                    cached.Dispose();
                }
            }
            playModeCache.Clear();

            stateEntered = false;
            gameDataState.ChangePlayMode(PlayMode.None);
            advSettingsHandler.Release();
        }
    }
}
