using UnityEngine;
using UnityEngine.Rendering.Universal;

#if UNITY_2022_3_OR_NEWER
using ExternPropertyAttributes;

namespace FlatKit {
public class FlatKitOutline : ScriptableRendererFeature {
    [Tooltip("To create new settings use 'Create > FlatKit > Outline Settings'.")]
    [Expandable]
    public OutlineSettings settings;

    private Material _effectMaterial;
    private ScreenRenderPass _fullScreenPass;
    private bool _requiresColor;
    private bool _injectedBeforeTransparents;
    private ScriptableRenderPassInput _requirements = ScriptableRenderPassInput.Color;

    private const string ShaderName = "Hidden/FlatKit/OutlineWrap";
    private static int edgeColor => Shader.PropertyToID("_EdgeColor");
    private static int thickness => Shader.PropertyToID("_Thickness");
    private static int depthThresholdMin => Shader.PropertyToID("_DepthThresholdMin");
    private static int depthThresholdMax => Shader.PropertyToID("_DepthThresholdMax");
    private static int normalThresholdMin => Shader.PropertyToID("_NormalThresholdMin");
    private static int normalThresholdMax => Shader.PropertyToID("_NormalThresholdMax");
    private static int colorThresholdMin => Shader.PropertyToID("_ColorThresholdMin");
    private static int colorThresholdMax => Shader.PropertyToID("_ColorThresholdMax");
    private static int fadeRangeStart => Shader.PropertyToID("_FadeRangeStart");
    private static int fadeRangeEnd => Shader.PropertyToID("_FadeRangeEnd");

    /// <summary>
    /// Access the runtime effect material to override outline parameters at runtime
    /// without mutating the Settings asset.
    ///
    /// Shader (2022.3+ path): "Hidden/FlatKit/OutlineWrap"
    ///
    /// Common shader properties/keywords:
    /// - Floats: _Thickness, _DepthThresholdMin/_Max, _NormalThresholdMin/_Max, _ColorThresholdMin/_Max,
    ///           _FadeRangeStart, _FadeRangeEnd
    /// - Colors: _EdgeColor
    /// - Keywords: OUTLINE_USE_DEPTH, OUTLINE_USE_NORMALS, OUTLINE_USE_COLOR,
    ///             OUTLINE_ONLY, RESOLUTION_INVARIANT_THICKNESS, OUTLINE_FADE_OUT
    ///
    /// Note: Inspector changes on the Settings asset may overwrite your values if applied later.
    /// </summary>
    public Material EffectMaterial => _effectMaterial;

    public override void Create() {
        // Settings.
        {
            if (settings == null) return;
            settings.onSettingsChanged = null;
            settings.onReset = null;
            settings.onSettingsChanged += SetMaterialProperties;
            settings.onReset += SetMaterialProperties;
        }

        // Material.
        {
#if UNITY_EDITOR
            settings.effectMaterial = SubAssetMaterial.GetOrCreate(settings, ShaderName);
            if (settings.effectMaterial == null) return;
#endif
            _effectMaterial = settings.effectMaterial;
            SetMaterialProperties();
        }

        {
            _fullScreenPass = new ScreenRenderPass {
                renderPassEvent = settings.renderEvent,
            };

            _requirements = ScriptableRenderPassInput.Color; // Needed for the full-screen blit.
            if (settings.useDepth) _requirements |= ScriptableRenderPassInput.Depth;
            if (settings.useNormals) _requirements |= ScriptableRenderPassInput.Normal;
            if (settings.fadeWithDistance) _requirements |= ScriptableRenderPassInput.Depth;
            ScriptableRenderPassInput modifiedRequirements = _requirements;

            _requiresColor = (_requirements & ScriptableRenderPassInput.Color) != 0;
            _injectedBeforeTransparents = settings.renderEvent <= RenderPassEvent.BeforeRenderingTransparents;

            if (_requiresColor && !_injectedBeforeTransparents) {
                modifiedRequirements ^= ScriptableRenderPassInput.Color;
            }

            _fullScreenPass.ConfigureInput(modifiedRequirements);
        }
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) {
        if (settings == null || !settings.applyInSceneView && renderingData.cameraData.isSceneViewCamera) return;
        if (renderingData.cameraData.isPreviewCamera) return;
        if (_effectMaterial == null) return;

        _fullScreenPass.Setup(_effectMaterial, _requiresColor, _injectedBeforeTransparents, "Flat Kit Outline",
            renderingData);
        renderer.EnqueuePass(_fullScreenPass);
    }

    protected override void Dispose(bool disposing) {
        _fullScreenPass?.Dispose();
    }

    private void SetMaterialProperties() {
        if (_effectMaterial == null) return;

        const string depthKeyword = "OUTLINE_USE_DEPTH";
        RendererFeatureUtils.SetKeyword(_effectMaterial, depthKeyword, settings.useDepth);

        const string normalsKeyword = "OUTLINE_USE_NORMALS";
        RendererFeatureUtils.SetKeyword(_effectMaterial, normalsKeyword, settings.useNormals);

        const string colorKeyword = "OUTLINE_USE_COLOR";
        RendererFeatureUtils.SetKeyword(_effectMaterial, colorKeyword, settings.useColor);

        const string outlineOnlyKeyword = "OUTLINE_ONLY";
        RendererFeatureUtils.SetKeyword(_effectMaterial, outlineOnlyKeyword, settings.outlineOnly);

        const string resolutionInvariantKeyword = "RESOLUTION_INVARIANT_THICKNESS";
        RendererFeatureUtils.SetKeyword(_effectMaterial, resolutionInvariantKeyword, settings.resolutionInvariant);

        const string fadeWithDistanceKeyword = "OUTLINE_FADE_OUT";
        RendererFeatureUtils.SetKeyword(_effectMaterial, fadeWithDistanceKeyword, settings.fadeWithDistance);

        _effectMaterial.SetColor(edgeColor, settings.edgeColor);
        _effectMaterial.SetFloat(thickness, settings.thickness);

        _effectMaterial.SetFloat(depthThresholdMin, settings.minDepthThreshold);
        _effectMaterial.SetFloat(depthThresholdMax, settings.maxDepthThreshold);

        _effectMaterial.SetFloat(normalThresholdMin, settings.minNormalsThreshold);
        _effectMaterial.SetFloat(normalThresholdMax, settings.maxNormalsThreshold);

        _effectMaterial.SetFloat(colorThresholdMin, settings.minColorThreshold);
        _effectMaterial.SetFloat(colorThresholdMax, settings.maxColorThreshold);

        _effectMaterial.SetFloat(fadeRangeStart, settings.fadeRangeStart);
        _effectMaterial.SetFloat(fadeRangeEnd, settings.fadeRangeEnd);
    }
}
}
#else
namespace FlatKit {
public class FlatKitOutline : ScriptableRendererFeature {
    [Tooltip("To create new settings use 'Create > FlatKit > Outline Settings'.")]
    public OutlineSettings settings;

    [SerializeField, HideInInspector]
    private Material _effectMaterial;

    private BlitTexturePass _blitTexturePass;

    private static readonly string OutlineShaderName = "Hidden/FlatKit/OutlineFilter";
    private static readonly int EdgeColor = Shader.PropertyToID("_EdgeColor");
    private static readonly int Thickness = Shader.PropertyToID("_Thickness");
    private static readonly int DepthThresholdMin = Shader.PropertyToID("_DepthThresholdMin");
    private static readonly int DepthThresholdMax = Shader.PropertyToID("_DepthThresholdMax");
    private static readonly int NormalThresholdMin = Shader.PropertyToID("_NormalThresholdMin");
    private static readonly int NormalThresholdMax = Shader.PropertyToID("_NormalThresholdMax");
    private static readonly int ColorThresholdMin = Shader.PropertyToID("_ColorThresholdMin");
    private static readonly int ColorThresholdMax = Shader.PropertyToID("_ColorThresholdMax");

    /// <summary>
    /// Access the runtime effect material to override outline parameters at runtime
    /// without mutating the Settings asset.
    ///
    /// Shader (legacy path): "Hidden/FlatKit/OutlineFilter"
    ///
    /// Common shader properties/keywords:
    /// - Floats: _Thickness, _DepthThresholdMin/_Max, _NormalThresholdMin/_Max, _ColorThresholdMin/_Max
    /// - Colors: _EdgeColor
    /// - Keywords: OUTLINE_USE_DEPTH, OUTLINE_USE_NORMALS, OUTLINE_USE_COLOR,
    ///             OUTLINE_ONLY, RESOLUTION_INVARIANT_THICKNESS
    ///
    /// Note: Inspector changes on the Settings asset may overwrite your values if applied later.
    /// </summary>
    public Material EffectMaterial => _effectMaterial;

    public override void Create() {
#if UNITY_EDITOR
        if (_effectMaterial == null) {
            SubAssetMaterial.AlwaysInclude(BlitTexturePass.CopyEffectShaderName);
            SubAssetMaterial.AlwaysInclude(OutlineShaderName);
        }
#endif

        if (settings == null) {
            return;
        }

        if (!CreateMaterials()) {
            return;
        }

        SetMaterialProperties();

        _blitTexturePass ??=
            new BlitTexturePass(_effectMaterial, settings.useDepth, settings.useNormals, useColor: true);
    }

    protected override void Dispose(bool disposing) {
        _blitTexturePass.Dispose();
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) {
#if UNITY_EDITOR
        if (renderingData.cameraData.isPreviewCamera) return;
        if (!settings.applyInSceneView && renderingData.cameraData.cameraType == CameraType.SceneView) return;
#endif

        SetMaterialProperties();

        _blitTexturePass.Setup(renderingData);
        _blitTexturePass.renderPassEvent = settings.renderEvent;

        renderer.EnqueuePass(_blitTexturePass);
    }

    private bool CreateMaterials() {
        if (_effectMaterial == null) {
            var effectShader = Shader.Find(OutlineShaderName);
            var blitShader = Shader.Find(BlitTexturePass.CopyEffectShaderName);
            if (effectShader == null || blitShader == null) return false;
            _effectMaterial = UnityEngine.Rendering.CoreUtils.CreateEngineMaterial(effectShader);
        }

        return _effectMaterial != null;
    }

    private void SetMaterialProperties() {
        if (_effectMaterial == null) {
            return;
        }

        const string depthKeyword = "OUTLINE_USE_DEPTH";
        if (settings.useDepth) {
            _effectMaterial.EnableKeyword(depthKeyword);
        } else {
            _effectMaterial.DisableKeyword(depthKeyword);
        }

        const string normalsKeyword = "OUTLINE_USE_NORMALS";
        if (settings.useNormals) {
            _effectMaterial.EnableKeyword(normalsKeyword);
        } else {
            _effectMaterial.DisableKeyword(normalsKeyword);
        }

        const string colorKeyword = "OUTLINE_USE_COLOR";
        if (settings.useColor) {
            _effectMaterial.EnableKeyword(colorKeyword);
        } else {
            _effectMaterial.DisableKeyword(colorKeyword);
        }

        const string outlineOnlyKeyword = "OUTLINE_ONLY";
        if (settings.outlineOnly) {
            _effectMaterial.EnableKeyword(outlineOnlyKeyword);
        } else {
            _effectMaterial.DisableKeyword(outlineOnlyKeyword);
        }

        const string resolutionInvariantKeyword = "RESOLUTION_INVARIANT_THICKNESS";
        if (settings.resolutionInvariant) {
            _effectMaterial.EnableKeyword(resolutionInvariantKeyword);
        } else {
            _effectMaterial.DisableKeyword(resolutionInvariantKeyword);
        }

        _effectMaterial.SetColor(EdgeColor, settings.edgeColor);
        _effectMaterial.SetFloat(Thickness, settings.thickness);

        _effectMaterial.SetFloat(DepthThresholdMin, settings.minDepthThreshold);
        _effectMaterial.SetFloat(DepthThresholdMax, settings.maxDepthThreshold);

        _effectMaterial.SetFloat(NormalThresholdMin, settings.minNormalsThreshold);
        _effectMaterial.SetFloat(NormalThresholdMax, settings.maxNormalsThreshold);

        _effectMaterial.SetFloat(ColorThresholdMin, settings.minColorThreshold);
        _effectMaterial.SetFloat(ColorThresholdMax, settings.maxColorThreshold);
    }
}
}
#endif