using System;
using System.IO;
using System.Collections.Generic;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using UnityEngine.Rendering;

namespace UnityEditor.Rendering
{
    class ProbeVolumeBuildProcessor : BuildPlayerProcessor, IPostprocessBuildWithReport
    {
        const string kTempAPVStreamingAssetsPath = "TempAPVStreamingAssets";

        string GetTempAPVStreamingAssetsPath()
        {
            var libraryPath = Path.GetFullPath("Library");
            return Path.Combine(libraryPath, kTempAPVStreamingAssetsPath);
        }

        // Include an asset in the build. The mechanism for doing so depends on whether we are using StreamingAssets path.
        static void IncludeStreamableAsset(ProbeVolumeStreamableAsset asset, string basePath, bool useStreamingAsset)
        {
            if (useStreamingAsset)
            {
                asset.ClearAssetReferenceForBuild();
                CopyStreamableAsset(asset, basePath);
            }
            else
            {
                asset.EnsureAssetLoaded();
            }
        }

        // Ensure that an asset is not included in the build.
        static void StripStreambleAsset(ProbeVolumeStreamableAsset asset)
        {
            asset.ClearAssetReferenceForBuild();
        }

        static void CopyStreamableAsset(ProbeVolumeStreamableAsset asset, string basePath)
        {
            var assetPath = asset.GetAssetPath();
            if (!File.Exists(assetPath))
            {
                Debug.LogError($"Missing APV data asset {assetPath}. Please make sure that the lighting has been baked properly.");
                return;
            }
            File.Copy(assetPath, Path.Combine(basePath, asset.assetGUID + ".bytes"));
        }

        void GetProbeVolumeProjectSettings(BuildTarget target, out bool supportProbeVolume, out ProbeVolumeSHBands maxSHBands)
        {
            // Grab all assets used for the build.
            List<RenderPipelineAsset> srpAssets = new List<RenderPipelineAsset>();
            target.TryGetRenderPipelineAssets(srpAssets);

            maxSHBands = ProbeVolumeSHBands.SphericalHarmonicsL1;
            supportProbeVolume = false;

            foreach (var asset in srpAssets)
            {
                var probeVolumeEnabledRenderPipeline = asset as IProbeVolumeEnabledRenderPipeline;
                // If at least one asset needs L2 then we can return.
                if (probeVolumeEnabledRenderPipeline != null)
                {
                    supportProbeVolume |= probeVolumeEnabledRenderPipeline.supportProbeVolume;

                    if (probeVolumeEnabledRenderPipeline.maxSHBands == ProbeVolumeSHBands.SphericalHarmonicsL2)
                        maxSHBands = ProbeVolumeSHBands.SphericalHarmonicsL2;
                }
            }
        }

        // Keep track of which assets we touched during the build, so we can restore them after the build.
        private static HashSet<ProbeVolumeBakingSet> s_BakingSetsProcessedLastBuild = new();

        public override void PrepareForBuild(BuildPlayerContext buildPlayerContext)
        {
            GetProbeVolumeProjectSettings(buildPlayerContext.BuildPlayerOptions.target, out bool supportProbeVolume, out var maxSHBands);

            if (!supportProbeVolume)
                return;

            // We need to make sure the baking set map is properly initialized.
            // Since it's done only at APV init, we call it explicitely here in case APV was not used yet in this session.
            ProbeVolumeBakingSet.SyncBakingSets();

            var tempStreamingAssetsPath = GetTempAPVStreamingAssetsPath();

            // Delete previously built data. This way we remove any data from scenes that are no longer in the build.
            if (Directory.Exists(tempStreamingAssetsPath))
            {
                try
                {
                    Directory.Delete(tempStreamingAssetsPath, recursive: true);
                }
                catch
                {
                    // This can happen if for example the folder is write protected
                    // This is not ideal but don't block project build
                }
            }

            Directory.CreateDirectory(tempStreamingAssetsPath);

            s_BakingSetsProcessedLastBuild.Clear();
            foreach (var scene in buildPlayerContext.BuildPlayerOptions.scenes)
            {
                var sceneGUID = AssetDatabase.AssetPathToGUID(scene);
                var bakingSet = ProbeVolumeBakingSet.GetBakingSetForScene(sceneGUID);
                if (bakingSet != null)
                {
                    // Already processed (different scenes can belong to the same baking set).
                    if (s_BakingSetsProcessedLastBuild.Contains(bakingSet))
                        continue;

                    if (!bakingSet.cellSharedDataAsset.IsValid()) // Not baked
                        continue;

                    // APV doesn't work with WebGL, so let's warn the user.
                    if (buildPlayerContext.BuildPlayerOptions.target == BuildTarget.WebGL)
                    {
                        // WebGPU does support APV so only warn if WebGPU is not enabled.
                        GraphicsDeviceType[] apis = PlayerSettings.GetGraphicsAPIs(BuildTarget.WebGL);
                        var index = Array.FindIndex(apis, x => x == GraphicsDeviceType.WebGPU);
                        if (index == -1)
                        {
                            Debug.LogError(
                                $"The scene '{scene}' contains baked Adaptive Probe Volumes, but the build target is WebGL. " +
                                "Adaptive Probe Volumes are not supported when targeting WebGL.");
                            continue;
                        }
                    }

                    var bakingSetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(bakingSet));
                    var basePath = Path.Combine(tempStreamingAssetsPath, bakingSetGUID);

                    Directory.CreateDirectory(basePath);

                    bool useStreamingAsset = !GraphicsSettings.GetRenderPipelineSettings<ProbeVolumeGlobalSettings>().probeVolumeDisableStreamingAssets;

                    IncludeStreamableAsset(bakingSet.cellSharedDataAsset, basePath, useStreamingAsset);
                    IncludeStreamableAsset(bakingSet.cellBricksDataAsset, basePath, useStreamingAsset);
                    // For now we always strip support data in build as it's mostly unsupported.
                    // Later we'll need a proper option to strip it or not.
                    bool stripSupportData = true;
                    if (stripSupportData)
                        StripStreambleAsset(bakingSet.cellSupportDataAsset);
                    else
                        IncludeStreamableAsset(bakingSet.cellSupportDataAsset, basePath, useStreamingAsset);

                    foreach (var scenario in bakingSet.scenarios)
                    {
                        IncludeStreamableAsset(scenario.Value.cellDataAsset, basePath, useStreamingAsset);
                        if (maxSHBands == ProbeVolumeSHBands.SphericalHarmonicsL2)
                            IncludeStreamableAsset(scenario.Value.cellOptionalDataAsset, basePath, useStreamingAsset);
                        else
                            StripStreambleAsset(scenario.Value.cellOptionalDataAsset);

                        if (bakingSet.bakedProbeOcclusion)
                            IncludeStreamableAsset(scenario.Value.cellProbeOcclusionDataAsset, basePath, useStreamingAsset);
                        else
                            StripStreambleAsset(scenario.Value.cellProbeOcclusionDataAsset);
                    }

                    s_BakingSetsProcessedLastBuild.Add(bakingSet);
                }
            }

            buildPlayerContext.AddAdditionalPathToStreamingAssets(tempStreamingAssetsPath, AdaptiveProbeVolumes.kAPVStreamingAssetsPath);
        }

        public void OnPostprocessBuild(BuildReport report)
        {
            if (s_BakingSetsProcessedLastBuild == null || s_BakingSetsProcessedLastBuild.Count == 0)
                return;

            // Go over each asset reference we touched during the last build, make sure asset references are intact.
            foreach (var bakingSet in s_BakingSetsProcessedLastBuild)
            {
                bakingSet.cellBricksDataAsset.EnsureAssetLoaded();
                bakingSet.cellSharedDataAsset.EnsureAssetLoaded();
                bakingSet.cellSupportDataAsset.EnsureAssetLoaded();
                foreach (var scenario in bakingSet.scenarios)
                {
                    scenario.Value.cellDataAsset.EnsureAssetLoaded();
                    scenario.Value.cellOptionalDataAsset.EnsureAssetLoaded();
                    scenario.Value.cellProbeOcclusionDataAsset.EnsureAssetLoaded();
                }
            }

            s_BakingSetsProcessedLastBuild.Clear();
        }
    }
}