using System;
using Intrepid.Diagnostics;
using Intrepid.FSM;
using Intrepid.Structures;
using UnityEngine;

namespace Intrepid.Gameplay.Journey.Runtime.Loading
{
    public enum StageLoadOwnership
    {
        LoadedByJourney,
        AdoptedFromEditor
    }

    [Serializable]
    public class StageLoadingService : IState<StageTag, StageLoadingService>
    {
        private StageTag StageTag { get; }

        private StageLoadOwnership Ownership { get; }
        private bool UnloadOnExit { get; }

        private AsyncOperation LoadOperation { get; set; }
        private AsyncOperation UnloadOperation { get; set; }

        private bool HasEntered { get; set; }

        public bool IsLoadingFinished
        {
            get
            {
                if (StageTag.IsNull())
                {
                    return false;
                }

                if (StageTag.IsLoaded())
                {
                    return true;
                }

                return LoadOperation?.isDone ?? false;
            }
        }

        public bool IsUnloading => UnloadOperation is { isDone: false };

        public bool CanEnterState => true;

        public bool CanExitState => true;

        private StageLoadingService(
            StageTag stageTag,
            StageLoadOwnership ownership = StageLoadOwnership.LoadedByJourney,
            bool unloadOnExit = true)
        {
            StageTag = stageTag;
            Ownership = ownership;
            UnloadOnExit = unloadOnExit;
        }
        public static StageLoadingService CreateLoadedByJourney(StageTag stageTag)
        {
            return new StageLoadingService(stageTag);
        }

        public static StageLoadingService CreateAdoptedFromEditor(StageTag stageTag, bool unloadOnExit = true)
        {
            return new StageLoadingService(stageTag, StageLoadOwnership.AdoptedFromEditor, unloadOnExit);
        }

        public StageTag GetStateKey()
        {
            return StageTag;
        }

        public StageLoadingService Implementation()
        {
            return this;
        }

        public void OnEnterState(ParameterStore ps)
        {
            HasEntered = true;
            UnloadOperation = null;

            if (StageTag.IsNull())
            {
                DLogger.LogWarn("cannot enter stage loading state because StageTag is null.");
                return;
            }

            if (StageTag.IsLoaded())
            {
                LoadOperation = null;
                DLogger.LogDebug($"stage is already loaded, adopting loaded scene: {StageTag}");
                return;
            }

            if (Ownership == StageLoadOwnership.AdoptedFromEditor)
            {
                LoadOperation = null;
                DLogger.LogWarn($"stage was marked as adopted from editor, but scene is not loaded: {StageTag}");
                return;
            }

            if (!StageTag.IsReadyToBeLoaded())
            {
                LoadOperation = null;
                DLogger.LogWarn($"stage is not ready to be loaded: {StageTag}");
                return;
            }

            LoadOperation = StageTag.LoadAsync();

            if (LoadOperation == null)
            {
                DLogger.LogWarn($"stage load operation returned null: {StageTag}");
                return;
            }

            DLogger.LogDebug($"started loading stage: {StageTag}");
        }

        public void OnExitState()
        {
            if (StageTag.IsNull())
            {
                DLogger.LogWarn("cannot exit stage loading state because StageTag is null.");
                return;
            }

            if (!HasEntered)
            {
                DLogger.LogWarn($"stage loading state was never entered. Ignoring unload: {StageTag}");
                return;
            }

            if (!UnloadOnExit)
            {
                DLogger.LogDebug($"stage unload skipped by policy: {StageTag}");
                return;
            }

            if (IsUnloading)
            {
                DLogger.LogWarn($"stage is already unloading: {StageTag}");
                return;
            }

            if (!StageTag.IsLoaded())
            {
                DLogger.LogWarn($"stage is already unloaded. Ignoring unload: {StageTag}");
                return;
            }

            UnloadOperation = StageTag.UnloadAsync();

            if (UnloadOperation == null)
            {
                DLogger.LogWarn($"stage unload operation returned null: {StageTag}");
                return;
            }

            DLogger.LogDebug($"started unloading stage: {StageTag}");
        }
    }
}