#include "/lib/Surface/BRDF.glsl"

#define PCF_SAMPLES 16 // [4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 48 64]
#define ShadowBlur 1 // [0 1 2 3 4 5]

const float shadowDistanceRenderMul = 1.0; // [-1.0 1.0]

const int shadowMapResolution = 2048;  // Shadowmap resolution [128|notrecomend| 512 1024 1514 2048 4096 8192 16384 32768 48128 84000]
const float shadowDistance = 192.0; // [64.0 80.0 96.0 112.0 128.0 160.0 192.0 224.0 256.0 320.0 384.0 512.0 768.0 1024.0 2048.0 4096.0 8192.0 16384.0 32768.0 65536.0]

const float realShadowMapRes = shadowMapResolution * MC_SHADOW_QUALITY;

//------------------------------------------------------------------------------------------------//

#include "ShadowDistortion.glsl"

vec3 WorldPosToShadowProjPosBias(in vec3 worldOffsetPos, out float distortFactor) {
    vec3 shadowClipPos = transMAD(shadowModelView, worldOffsetPos);
    shadowClipPos = projMAD(shadowProjection, shadowClipPos);

    distortFactor = DistortionFactor(shadowClipPos.xy);
    return DistortShadowSpace(shadowClipPos, distortFactor) * 0.5 + 0.5;
}

//------------------------------------------------------------------------------------------------//

vec2 BlockerSearch(in vec3 shadowProjPos, in float dither) {
    float searchDepth = 0.0;
    float sumWeight = 0.0;
    float sssDepth = 0.0;

    // Увеличиваем радиус поиска в зависимости от ShadowBlur
    float blurFactor = 0.5 + float(ShadowBlur) * 0.2; // От 1.0 (без блюра) до 3.5 (максимальный блюр)
    float searchRadius = 2.0 * shadowProjection[0].x * blurFactor;

    vec2 rot = cossin(dither * TAU) * searchRadius;
    const vec2 angleStep = cossin(TAU * 0.125);
    const mat2 rotStep = mat2(angleStep, -angleStep.y, angleStep.x);
    for (uint i = 0u; i < 8u; ++i, rot *= rotStep) {
        float fi = float(i) + dither;
        vec2 sampleCoord = shadowProjPos.xy + rot * sqrt(fi * 0.125);

        float depthSample = texelFetch(shadowtex0, ivec2(sampleCoord * realShadowMapRes), 0).x;
        float weight = step(depthSample, shadowProjPos.z);

        // Добавляем сглаживание для более плавного фейдинга
        float smoothWeight = smoothstep(0.0, 0.1 * blurFactor, shadowProjPos.z - depthSample);
        weight *= smoothWeight;

        sssDepth += max0(shadowProjPos.z - depthSample) * smoothWeight;
        searchDepth += depthSample * weight;
        sumWeight += weight;
    }

    searchDepth *= 1.0 / max(sumWeight, 1e-6);
    searchDepth = min(2.0 * (shadowProjPos.z - searchDepth) / max(searchDepth, 1e-6), 1.0);

    return vec2(searchDepth * shadowProjection[0].x * blurFactor, sssDepth * shadowProjectionInverse[2].z);
}

vec3 PercentageCloserFilter(in vec3 shadowProjPos, in float dither, in float penumbraScale) {
    shadowProjPos.z -= 1e-4 - dither * 5e-5;

    // Динамическое количество сэмплов в зависимости от ShadowBlur
    uint sampleCount = PCF_SAMPLES + uint(ShadowBlur) * 4u; // Увеличиваем сэмплы: 16, 20, 24, 28, 32, 36
    float rSteps = 1.0 / float(sampleCount);

    vec3 result = vec3(0.0);

    // Увеличиваем размер ядра сэмплирования для большего размытия
    float blurFactor = 0.5 + float(ShadowBlur) * 2.5; // От 1.0 до 3.5
    vec2 rot = cossin(dither * TAU) * penumbraScale * blurFactor;
    const vec2 angleStep = cossin(TAU * 0.125);
    const mat2 rotStep = mat2(angleStep, -angleStep.y, angleStep.x);
    for (uint i = 0u; i < sampleCount; ++i, rot *= rotStep) {
        float fi = float(i) + dither;
        vec2 sampleCoord = shadowProjPos.xy + rot * sqrt(fi * rSteps);

        float sampleDepth1 = textureLod(shadowtex1, vec3(sampleCoord, shadowProjPos.z), 0).x;

        #ifdef COLORED_SHADOWS
            ivec2 sampleTexel = ivec2(sampleCoord * realShadowMapRes);
            float sampleDepth0 = step(shadowProjPos.z, texelFetch(shadowtex0, sampleTexel, 0).x);
            if (sampleDepth0 != sampleDepth1) {
                result += pow4(texelFetch(shadowcolor0, sampleTexel, 0).rgb) * sampleDepth1;
            } else 
        #endif
        {
            // Сглаживание вклада тени для плавного фейдинга
            float shadowContrib = smoothstep(-0.05 * blurFactor, 0.05 * blurFactor, sampleDepth1 - shadowProjPos.z);
            result += shadowContrib;
        }
    }

    return result * rSteps;
}

//------------------------------------------------------------------------------------------------//

float ScreenSpaceShadow(in vec3 viewPos, in vec3 rayPos, in float dither, in float sssAmount) {
    vec3 lightVector = mat3(gbufferModelView) * worldLightVector;

    vec3 position = ViewToScreenSpace(lightVector * abs(viewPos.z) * 0.1 + viewPos);
    vec3 screenDir = normalize(position - rayPos);

    float absorption = pow(sssAmount, sqrt(length(viewPos)) * 0.5);

    screenDir.xy *= screenSize;
    rayPos.xy *= screenSize;

    vec3 rayStep = screenDir * max(0.01, 0.05 - sssAmount * 0.05) * gbufferProjection[1][1] * rcp(12.0);
    rayPos += rayStep * (1.0 - sssAmount + dither);

    float zTolerance = 0.025;
    float shadow = 1.0;

    for (uint i = 0u; i < 12u; ++i, rayPos += rayStep) {
        if (clamp(rayPos.xy, vec2(0.0), screenSize) != rayPos.xy) break;
        if (rayPos.z >= 1.0) break;

        float depth = texelFetch(depthtex0, ivec2(rayPos.xy), 0).x;

        if (depth < rayPos.z) {
            float linearSample = GetDepthLinear(depth);
            float currentDepth = GetDepthLinear(rayPos.z);

            if (abs(linearSample - currentDepth) / currentDepth < zTolerance) {
                shadow *= absorption;
            }
        }

        if (shadow < 1e-2) break;
    }

    return shadow;
}

#if defined DISTANT_HORIZONS
float ScreenSpaceShadowDH(in vec3 viewPos, in vec3 rayPos, in float dither, in float sssAmount) {
    vec3 lightVector = mat3(gbufferModelView) * worldLightVector;

    vec3 position = ViewToScreenSpaceDH(lightVector * abs(viewPos.z) * 0.1 + viewPos);
    vec3 screenDir = normalize(position - rayPos);

    float absorption = pow(sssAmount, sqrt(length(viewPos)) * 0.5);

    screenDir.xy *= screenSize;
    rayPos.xy *= screenSize;

    vec3 rayStep = screenDir * max(0.01, 0.05 - sssAmount * 0.05) * dhProjection[1][1] * rcp(12.0);
    rayPos += rayStep * (1.0 - sssAmount + dither);

    float zTolerance = 0.025;
    float shadow = 1.0;

    for (uint i = 0u; i < 12u; ++i, rayPos += rayStep) {
        if (clamp(rayPos.xy, vec2(0.0), screenSize) != rayPos.xy) break;

        float depth = texelFetch(dhDepthTex0, ivec2(rayPos.xy), 0).x;

        if (depth < rayPos.z) {
            float linearSample = GetDepthLinearDH(depth);
            float currentDepth = GetDepthLinearDH(rayPos.z);

            if (abs(linearSample - currentDepth) / currentDepth < zTolerance) {
                shadow *= absorption;
            }
        }

        if (shadow < 1e-2) break;
    }

    return shadow;
}
#endif

float CalculateFakeBouncedLight(in vec3 normal) {
    normal.y = -normal.y;
    vec3 bounceVector = normalize(worldLightVector + vec3(0.0, 1.0, 0.0));
    float bounce = saturate(dot(normal, bounceVector) * 0.4 + 0.6);

    return bounce * (2.0 - bounce) * 3e-2;
}

vec3 CalculateSubsurfaceScattering(in vec3 albedo, in float sssAmount, in float sssDepth, in float LdotV) {
    vec3 coeff = albedo * inversesqrt(GetLuminance(albedo) + 1e-6);
    coeff = oneMinus(0.75 * saturate(coeff)) * (28.0 / sssAmount);

    vec3 subsurfaceScattering = expf(0.375 * coeff * sssDepth) * HenyeyGreensteinPhase(-LdotV, 0.6);
    subsurfaceScattering += expf(0.125 * coeff * sssDepth) * (0.33 * HenyeyGreensteinPhase(-LdotV, 0.35) + 0.17 * rPI);

    return subsurfaceScattering * sssAmount * PI;
}