using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;

namespace Ilumisoft.Rendering
{
    [SupportedOnRenderer(typeof(UniversalRendererData))]
    [DisallowMultipleRendererFeature("Outline")]
    public class OutlineRendererFeature : ScriptableRendererFeature
    {
        public enum InjectionPoint
        {
            BeforeRenderingTransparents = 450,
            BeforeRenderingPostProcessing = 550
        }

        public enum OutlineMode
        {
            DepthOnly,
            [InspectorName("Depth + Normal")]
            DepthNormal
        }

        public struct Settings
        {
            public OutlineMode Mode;
            public bool ScaleWithResolution;
            public int ReferenceHeight;
        }

        class OutlineRenderPass : ScriptableRenderPass
        {
            private readonly int LUMINANCE_CONTRAST = Shader.PropertyToID("_LuminanceContrast");
            private readonly int LUMINANCE_POWER = Shader.PropertyToID("_LuminancePower");
            private readonly int BACKGROUND_COLOR = Shader.PropertyToID("_BackgroundColor");
            private readonly int BACKGROUND_COLOR_OPACITY = Shader.PropertyToID("_BackgroundColorOpacity");
            private readonly int DEPTH_EDGE = Shader.PropertyToID("_DepthEdge");
            private readonly int NORMAL_EDGE = Shader.PropertyToID("_NormalEdge");
            private readonly int IS_FADE_ENABLED = Shader.PropertyToID("_IsFadeEnabled");
            private readonly int FADE_START = Shader.PropertyToID("_FadeStart");
            private readonly int FADE_END = Shader.PropertyToID("_FadeEnd");
            private readonly int REFERENCE_HEIGHT = Shader.PropertyToID("_ReferenceHeight");
            private readonly int OUTLINE_THICKNESS_ID = Shader.PropertyToID("_OutlineThickness");
            private readonly int OUTLINE_COLOR_ID = Shader.PropertyToID("_OutlineColor");

            Material material;

            Settings settings;

            public void Setup(Material material, Settings settings)
            {
                this.material = material;
                this.settings = settings;
            }

            public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
            {
                var volumeComponent = VolumeManager.instance.stack.GetComponent<OutlineVolumeComponent>();

                // Get settings from volume component
                int thickness = volumeComponent.thickness.value;
                Color outlineColor = volumeComponent.outlineColor.value;
                Color backgroundColor = volumeComponent.backgroundColor.value;
                bool isDistanceFadeEnabled = volumeComponent.distanceFade.value;
                float distanceFadeStart = volumeComponent.fadeStart.value;
                float distanceFadeDistance = volumeComponent.fadeDistance.value;
                bool backgroundFill = volumeComponent.backgroundFill.value;
                Vector2 depthEdge = volumeComponent.depthSmoothstep.value;
                Vector2 normalEdge = volumeComponent.normalSmoothstep.value;
                float luminancePower = volumeComponent.luminanceDetail.value;
                float luminanceContrast = volumeComponent.luminanceContrast.value;

                int referenceHeight = settings.ReferenceHeight;

                // Cancel if outline are disabled
                if (thickness == 0)
                {
                    return;
                }

                UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();

                var sourceHandle = resourceData.activeColorTexture;
                var descriptor = sourceHandle.GetDescriptor(renderGraph);
                descriptor.clearBuffer = false;
                descriptor.name = "_CameraColorOutline";

                if (!sourceHandle.IsValid())
                {
                    return;
                }

                // Get current image height if outlines should not be scaled by a reference resolution
                if (!settings.ScaleWithResolution)
                {
                    referenceHeight = descriptor.height;
                }

                // Set material properties
                material.SetColor(OUTLINE_COLOR_ID, outlineColor);
                material.SetInt(OUTLINE_THICKNESS_ID, thickness);
                material.SetInt(REFERENCE_HEIGHT, referenceHeight);
                material.SetInt(IS_FADE_ENABLED, isDistanceFadeEnabled ? 1 : 0);
                material.SetFloat(FADE_START, distanceFadeStart);
                material.SetFloat(FADE_END, distanceFadeStart + distanceFadeDistance);
                material.SetColor(BACKGROUND_COLOR, backgroundColor);
                material.SetFloat(BACKGROUND_COLOR_OPACITY, backgroundFill ? 1 : 0);
                material.SetVector(DEPTH_EDGE, depthEdge);
                material.SetVector(NORMAL_EDGE, normalEdge);
                material.SetFloat(LUMINANCE_POWER, luminancePower);
                material.SetFloat(LUMINANCE_CONTRAST, luminanceContrast);

                // Outline blit
                TextureHandle targetHandle = renderGraph.CreateTexture(descriptor);
                var parameters = new RenderGraphUtils.BlitMaterialParameters(sourceHandle, targetHandle, material, (int)settings.Mode);
                renderGraph.AddBlitPass(parameters, passName: "Draw Outlines");

                resourceData.cameraColor = targetHandle;
            }
        }

        [Tooltip("Specifies where in the frame this pass will be injected.")]
        public InjectionPoint injectionPoint = InjectionPoint.BeforeRenderingTransparents;

        [Tooltip("Determines which scene data is used for edge detection. 'Depth Only' uses depth differences. 'Depth + Normal' uses surface angle changes as well.")]
        public OutlineMode mode = OutlineMode.DepthNormal;

        [Tooltip("When enabled, outline thickness scales proportionally with screen resolution to maintain visual consistency across different screen resolutions.")]
        public bool scaleWithResolution = true;

        [Tooltip("The vertical resolution used as a baseline for outline thickness scaling. A value of 1080 means the effect appears 1:1 at 1080p.")]
        [Min(720)]
        public int referenceHeight = 1080;

        public bool showInSceneView = false;

        Shader shader;
        Material material;
        OutlineRenderPass renderPass;

        public override void Create()
        {
            shader = Shader.Find("Hidden/Ilumisoft/PostProcessing/Outline");

            if (shader == null)
            {
                Debug.LogWarning("Outline Renderer Feature: Could not find outline shader");
                return;
            }

            material = CoreUtils.CreateEngineMaterial(shader);
            renderPass = new OutlineRenderPass();
        }

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            if (renderPass == null || material == null)
            {
                return;
            }

            if (renderingData.cameraData.cameraType == CameraType.Preview || renderingData.cameraData.cameraType == CameraType.Reflection || UniversalRenderer.IsOffscreenDepthTexture(ref renderingData.cameraData))
            {
                return;
            }

            if (renderingData.cameraData.cameraType != CameraType.Game && !(showInSceneView && renderingData.cameraData.cameraType == CameraType.SceneView))
            {
                return;
            }

            var inputRequirements = ScriptableRenderPassInput.Depth;

            if (mode == OutlineMode.DepthNormal)
            {
                inputRequirements |= ScriptableRenderPassInput.Normal;
            }

            renderPass.ConfigureInput(inputRequirements);
            renderPass.requiresIntermediateTexture = true;
            renderPass.renderPassEvent = injectionPoint switch
            {
                InjectionPoint.BeforeRenderingTransparents => RenderPassEvent.BeforeRenderingTransparents,
                InjectionPoint.BeforeRenderingPostProcessing => RenderPassEvent.BeforeRenderingPostProcessing,
                _ => RenderPassEvent.BeforeRenderingTransparents,
            };

            renderPass.Setup(material, new Settings()
            {
                Mode = mode,
                ReferenceHeight = referenceHeight,
                ScaleWithResolution = scaleWithResolution
            });

            renderer.EnqueuePass(renderPass);
        }

        protected override void Dispose(bool disposing)
        {
            CoreUtils.Destroy(material);
            renderPass = null;
        }
    }
}