﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ABI.CCK.Components;
using CVR.CCK.Scripts.Editor.Tools;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

namespace CVR.CCKEditor.Tools
{
    public static class CCKCommonTools
    {
        // This sessions temporary build output path.
        // Anything in this path will be deleted when the session ends.
        public static string SessionTempBuildOutputPath
        {
            get
            {
                var path = SessionState.GetString("CCK.ContentBuilder.TempBuildOutputPath", null);
                if (string.IsNullOrEmpty(path))
                {
                    path = FileUtil.GetUniqueTempPathInProject();
                    SessionState.SetString("CCK.ContentBuilder.TempBuildOutputPath", path);
                }
                return path;
            }
        }
        
        /// <summary>
        /// Gets the package info for a certain package. Returns null if it's not found.
        /// </summary>
        public static UnityEditor.PackageManager.PackageInfo GetPackageInfo(string packageName)
        {
            return AssetDatabase.FindAssets("package")
                .Select(AssetDatabase.GUIDToAssetPath)
                .Where(x => AssetDatabase.LoadAssetAtPath<TextAsset>(x) != null)
                .Select(UnityEditor.PackageManager.PackageInfo.FindForAssetPath)
                .Where(x => x != null)
                .FirstOrDefault(x => x.name == packageName);
        }
        
        public static void DeleteBundleAndManifest(string bundlePath)
        {
            TryDelete(bundlePath);
            TryDelete(bundlePath + ".manifest");
            return;
            static void TryDelete(string filePath)
            {
                try
                {
                    if (File.Exists(filePath)) File.Delete(filePath);
                }
                catch (IOException e)
                {
                    Debug.LogError($"Failed to delete file because it is in use {filePath}: {e.Message}");
                }
                catch (Exception e)
                {
                    Debug.LogError($"Failed to delete file {filePath}: {e.Message}");
                }
            }
        }
        
        public static string NormalizePath(string path) 
            => path.Replace('\\', '/');

        public static T AddComponentIfMissing<T>(this T component) where T : Component
            => component.gameObject.AddComponentIfMissing<T>();
        
        public static T AddComponentIfMissing<T>(this GameObject gameObject) where T : Component
        {
            if (!gameObject.TryGetComponent<T>(out var result)) result = gameObject.AddComponent<T>();
            return result;
        }
        
        /// <summary>
        /// Replaces a component on a gameobject at the same index of the original. This is needed only in specific
        /// cases where component order matters.
        /// </summary>
        /// <param name="oldComponent"></param>
        /// <param name="copyData"></param>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TTarget"></typeparam>
        /// <returns></returns>
        public static TTarget ReplaceComponentInPlace<TSource, TTarget>(TSource oldComponent, Action<TSource, TTarget> copyData)
            where TSource : Component
            where TTarget : Component
        {
            GameObject go = oldComponent.gameObject;

            try
            {
                if (PrefabUtility.IsPartOfAnyPrefab(go))
                    PrefabUtility.UnpackPrefabInstance(go, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
            }
            catch
            {
                // ignored
            }

            TTarget newComponent = go.AddComponent<TTarget>();

            var components = go.GetComponents<Component>();
            int oldIndex = Array.IndexOf(components, oldComponent);
            int newIndex = Array.IndexOf(components, newComponent);

            while (newIndex > oldIndex)
            {
                UnityEditorInternal.ComponentUtility.MoveComponentUp(newComponent);
                newIndex--;
            }

            copyData?.Invoke(oldComponent, newComponent);
            Object.DestroyImmediate(oldComponent);

            return newComponent;
        }
        
        public static T MoveComponentUnderComponent<T>(this Component component) where T : Component
        {
            GameObject go = component.gameObject;
            var components = go.GetComponents<Component>();
            int targetIndex = Array.IndexOf(components, component);
            int desiredIndex = Array.IndexOf(components, go.GetComponent<T>());
            if (desiredIndex == -1) throw new InvalidOperationException($"GameObject {go.name} does not have a component of type {typeof(T)}");
            while (targetIndex > desiredIndex)
            {
                UnityEditorInternal.ComponentUtility.MoveComponentUp(component);
                targetIndex--;
            }
            return component as T;
        }
        
        public static void MoveComponentsUnderneathComponent(Component target, List<Component> componentsToMove)
        {
            GameObject go = target.gameObject;
            var components = go.GetComponents<Component>();
            int desiredIndex = Array.IndexOf(components, target);
            foreach (Component component in componentsToMove)
            {
                int targetIndex = Array.IndexOf(components, component);
                if (targetIndex == -1) throw new InvalidOperationException($"GameObject {go.name} does not have a component of type {component.GetType()}");
                while (targetIndex > desiredIndex)
                {
                    UnityEditorInternal.ComponentUtility.MoveComponentUp(component);
                    targetIndex--;
                }
                desiredIndex++;
            }
        }
        
        public static T FindFirstInstanceInScene<T>(Scene scene) where T : Component
        {
            return scene.GetRootGameObjects()
                .Select(root => root.GetComponentsInChildren<T>(true).FirstOrDefault())
                .FirstOrDefault(component => component);
        }
        
        // lazy, cloudpush checks this properly anyways
        public static bool IsImageFile(string path)
        {
            string ext = Path.GetExtension(path).ToLowerInvariant();
            return ext is ".png" or ".jpg" or ".jpeg";
        }

        public static Texture2D CapturePortalPanoramic(GameObject worldGameObject)
        {
            CVRWorld cvrWorld = worldGameObject.GetComponent<CVRWorld>();
            
            Transform spawnTransform;
            if (cvrWorld && cvrWorld.spawns is { Length: > 0 } && cvrWorld.spawns[0])
                spawnTransform = cvrWorld.spawns[0].transform;
            else
                spawnTransform = worldGameObject.transform;
            
            Camera referenceCamera = null;
            if (cvrWorld.referenceCamera) cvrWorld.referenceCamera.TryGetComponent(out referenceCamera);
            
            PanoCaptureOptions captureOptions = new()
            {
                resolution = 4096,
                useShaderReplacementToCaptureDepth = false,
                captureWithDepth = true,
                referenceCamera = referenceCamera
            };
            
            Vector3 position = spawnTransform.position;
            Quaternion rotation = spawnTransform.rotation;
            
            // Add meter offset on up
            position += rotation * Vector3.up * 1.0f;

            return CVRImageCaptureHelper.CapturePreviewPanoramic(position, rotation, captureOptions);
        }
    }
}