struct deferredLightData {
	vec3 position;
	vec3 direction;
	vec3 color;

	float intensity;
	
	float inverseFalloff;
	float length;
	float radius;
};

struct deferredMaterialData {
	vec3 normal;
	vec3 baseColor;
	vec3 diffuse;
	vec3 position;
	vec3 vertex;
	vec3 tangent;
	vec3 specularReflectance;

	float reflectance;
	float metallic;
	float roughness;
	
	float clearCoat;
	float clearCoatRoughness;
	float clearCoatThickness;
	vec3  clearCoatColor;
	
	float geometricRoughness;
	float ambientOcclusion;
	float shadowOcclusion;
	
	float refracted_NoV;
	float index;
	
	float alpha;
};


float D_GGX(float NoH, float a) {
    float a2 = a * a;
    float f = (NoH * a2 - NoH) * NoH + 1.0;
    return a2 / (PI * f * f);
}

float D_GGX_Anisotropy(float NoH, vec3 h, vec3 x, vec3 y, float ax, float ay) {
    float XoH = dot(x, h);
    float YoH = dot(y, h);
    float d = XoH * XoH * (ax * ax) + YoH * YoH * (ay * ay) + NoH * NoH;
    return (ax * ay) / (PI * d * d);
}

vec3 F_Schlick(float VoH, vec3 specularReflectance, float f90) {
    return specularReflectance + (vec3(f90) - specularReflectance) * pow(1.0 - VoH, 5.0);
}

float V_SmithGGXCorrelated(float NoV, float NoL, float a) {
    float a2 = a * a;
    float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);
    float GGXV = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);
    return 0.5 / (GGXV + GGXL);
}

float Fd_Lambert() {
    return 1.0 / PI;
}

float F_Schlick_Scalar(float VoH, float specularReflectance, float f90) {
    return specularReflectance + (f90 - specularReflectance) * pow(1.0 - VoH, 5.0);
}

float square(float v) {
    return v * v;
}

vec3 irradianceSH(vec3 n) {
    return
        vec3(0.754553530212464, 0.748541695286661, 0.790922541330174)
        + vec3(-0.083855089181764, 0.092536341322488, 0.322767327275582) * (n.y)
        + vec3(0.308154551673257, 0.366799355358085, 0.466705760819624) * (n.z)
        + vec3(-0.188887618191928, -0.277403749518126, -0.377844811540716) * (n.x)
        + vec3(-0.252782448589491, -0.316051613736677, -0.396141020484574) * (n.y * n.x)
        + vec3(0.071362454444021, 0.159789075773366, 0.29059362717571) * (n.y * n.z)
        + vec3(-0.031040420617065, -0.031141089772695, -0.031044001883204) * (3.0 * n.z * n.z - 1.0)
        + vec3(-0.161001896026477, -0.203649521035777, -0.246641086569566) * (n.z * n.x)
        + vec3(0.045710934605387, 0.048121779682969, 0.046326375668417) * (n.x * n.x - n.y * n.y);
}

vec2 prefilteredDFGKaris(float NoV, float roughness) {
    const vec4 c0 = vec4(-1.0, -0.0275, -0.572,  0.022);
    const vec4 c1 = vec4( 1.0,  0.0425,  1.040, -0.040);
    vec4 r = roughness * c0 + c1;
    float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
    return vec2(-1.04, 1.04) * a004 + r.zw;
}

float sRGBtoLinear(float c) {
    return (c <= 0.04045) ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4);
}

vec3 sRGBtoLinear(vec3 c) {
    return vec3(sRGBtoLinear(c.r), sRGBtoLinear(c.g), sRGBtoLinear(c.b));
}

vec3 decodeEnvironmentMap(vec4 c) {
    return c.rgb; // or use sRGBtoLinear(c.rgb) if you want linear correction
}

float getSquareFalloffAttenuation(float distanceSquare, float inverseFalloff) {
    float factor = distanceSquare * inverseFalloff;
    float smoothFactor = max(1.0 - factor * factor, 0.0);
    return smoothFactor * smoothFactor;
}

float getPhotometricAttenuation(vec3 lightToPos, vec3 lightDir) {
    return 1.0;
}

float getAngleAttenuation(vec3 l, vec3 lightDir, float lightLength, float lightRadius) {
    float cd = dot(lightDir, l);
    float attenuation = clamp(cd * lightLength + lightRadius, 0.0, 1.0);
    return attenuation * attenuation;
}

vec3 beerLambert(float NoV, float NoL, vec3 alpha, float d) {
    return exp(alpha * -(d * ((NoL + NoV) / max(NoL * NoV, 1e-3))));
}

vec3 fixCubemapLookup(vec3 v, float lod) {
    vec3 r = abs(v);
    float M = max(max(v.x, v.y), v.z);
    float scale = 1.0 - exp2(lod) * (1.0 / 256.0);
    if (v.x != M) v.x *= scale;
    if (v.y != M) v.y *= scale;
    if (v.z != M) v.z *= scale;
    return v;
}

vec3 evaluateSpecularIBL(vec3 r, float roughness) {
    float lod = 5.0 * roughness;

    r = fixCubemapLookup(r, lod);

    return decodeEnvironmentMap(texture(reflectionSampler, r, lod)) * 1.8;
}

float computeSpecularambientOcclusion(float NoV, float ambientOcclusion, float roughness) {
    return clamp(pow(NoV + ambientOcclusion, exp2(-16.0 * roughness - 1.0)) - 1.0 + ambientOcclusion, 0.0, 1.0);
}

vec3 getSpecularDominantDirection(vec3 n, vec3 r, float roughness) {
    float s = 1.0 - roughness;
    return mix(n, r, s * (sqrt(s) + roughness));
}

vec3 indirectLight(deferredMaterialData materialData) {

	vec3 normal		= materialData.normal;
	vec3 vertex 	= materialData.vertex;
	
	float roughness = materialData.roughness;
	
    float NoV = max(dot(normal, vertex), 0.0);

#if ANISOTROPY == 1
    vec3 t = normalize(materialData.tangent);
    vec3 b = normalize(cross(t, normal));

    vec3 anisotropicTangent = cross(-vertex, b);
    vec3 anisotropicNormal  = cross(anisotropicTangent, b);
    vec3 bentNormal  = normalize(mix(normal, anisotropicNormal, anisotropy));

    vec3 r = reflect(vertex, bentNormal);
#else
    vec3 r = reflect(-vertex, normal);
    r = getSpecularDominantDirection(normal, r, roughness * roughness);
#endif

    float NoR = max(dot(r, normal), 0.0);

    vec3 indirectSpecular = evaluateSpecularIBL(reflect(vertex, normal), roughness);

    vec2 env = prefilteredDFGKaris(NoV, materialData.roughness);

    vec3 specularColor = materialData.specularReflectance * env.x + env.y * (1.0 - materialData.clearCoat) *
        clamp(dot(materialData.specularReflectance, vec3(50.0 * 0.33)), 0.0, 1.0);

    vec3 indirectDiffuse = max(irradianceSH(normal), 0.0) * Fd_Lambert();

    float ambientOcclusionFade = clamp(dot(normalize(normal), vertex), 0.0, 1.0);
    float ambientOcclusion = mix(1.0, materialData.ambientOcclusion, ambientOcclusionFade);
	indirectDiffuse *= ambientOcclusion;

    indirectSpecular *= computeSpecularambientOcclusion(NoV, ambientOcclusion, materialData.roughness);

    float Fcc = F_Schlick_Scalar(NoV, 0.04, 1.0);
    indirectSpecular = mix(indirectSpecular, vec3(Fcc), materialData.clearCoat);

    return indirectDiffuse * materialData.baseColor + indirectSpecular;
}



vec3 directLight( deferredMaterialData materialData, deferredLightData lightData ) {
    
	vec3 n = materialData.normal;
	vec3 v = materialData.vertex;
	
	vec3 l;
    float NoL;
    float energy;
    float attenuation;

    vec3 lightDir = normalize(lightData.direction);
    float linearRoughness = materialData.roughness * materialData.roughness;

    vec3 r = reflect(-v, n);

    if (lightType == 1.0) {
  
        l = -lightDir;

        // Disc area light
        vec3 sunDir = -lightDir;
        float e = sin(radians(0.53));
        float d = cos(radians(0.53));
        float DoR = dot(sunDir, r);
        vec3 s = r - DoR * sunDir;
        l = DoR < d ? normalize(d * sunDir + normalize(s) * e) : r;

        NoL = dot(n, l);
        energy = 1.0;
        attenuation = 1.0;
	
 
    } else if (lightType == 0.0) {
	
        vec3 posToLight = lightData.position - materialData.position;
        float distanceSquare = dot(posToLight, posToLight);
        l = normalize(posToLight);
        NoL = dot(n, l);
        energy = 50.0;
        attenuation  = getSquareFalloffAttenuation( distanceSquare, lightData.inverseFalloff );
        attenuation *= 1.0 / max(distanceSquare, 1e-4);
        attenuation *= getPhotometricAttenuation(-l, lightDir);
       
      //    attenuation *= getAngleAttenuation( l, -lightDir, lightData.length, lightData.radius );
  
    }
	

    vec3 h = normalize(v + l);

    NoL = clamp(NoL, 0.0, 1.0);
    float NoV = abs(dot(n, v)) + 1e-5;
    float NoH = clamp(dot(n, h), 0.0, 1.0);
    float LoH = clamp(dot(l, h), 0.0, 1.0);

    // specular BRDF
#if ANISOTROPY == 1
    vec3 t = normalize(v_tangent.xyz);
    vec3 b = normalize(cross(t, n));

    float aspect = inversesqrt(1.0 - anisotropy * 0.9);
    float ax = 1.0 / (linearRoughness * aspect);
    float ay = aspect / linearRoughness;

    float D = D_GGX_Anisotropy(NoH, h, t, b, ax, ay);
#else
    float D = D_GGX(NoH, linearRoughness);
#endif
    vec3  F = F_Schlick(LoH, materialData.specularReflectance, clamp(dot(materialData.specularReflectance, vec3(50.0 * 0.33)), 0.0, 1.0));
    float V = V_SmithGGXCorrelated(NoV, NoL, linearRoughness);
    vec3 Fr = (D * V) * F;

    // diffuse BRDF
    vec3 Fd = materialData.diffuse * Fd_Lambert();// * 3.6

    // clear coat
    float linearClearCoatRoughness = materialData.clearCoatRoughness * materialData.clearCoatRoughness;
    float Dcc  = D_GGX(NoH, linearClearCoatRoughness);
    float Fcc  = F_Schlick_Scalar(LoH, 0.04, 1.0) * materialData.clearCoat;
    float Vcc  = V_SmithGGXCorrelated(NoV, NoL, linearClearCoatRoughness);
    float FrCC = Dcc * Vcc * Fcc;

    vec3 refracted_l = -refract(l, n, 1.0);
    float refracted_NoL = clamp(dot(n, refracted_l), 0.0, 1.0);
    vec3 clearCoatAbsorption = mix(vec3(1.0),
        beerLambert(materialData.refracted_NoV, refracted_NoL, materialData.clearCoatColor, materialData.clearCoatThickness),
        materialData.clearCoat);

    // direct contribution
    vec3 color = (attenuation * NoL) * lightData.color * lightData.intensity * energy *
        ((Fd + Fr) * (1.0 - Fcc) * clearCoatAbsorption + FrCC);

#if TRANSLUCENT_MATERIAL == 1
    vec3 tL = l + n * translucencyDistortion;
    float tD = exp2(clamp(dot(v, -tL), 0.0, 1.0) * translucencyPower - translucencyPower) * translucencyScale;
    vec3 tT = attenuation * lightData.intensity * (tD + translucencyAmbient) * texture(translucencyThicknessMap, outUV).r;
    color.rgb += Fd * lightData.color * tT;
#endif

    // micro-shadowing
    float aperture = 2.0 * materialData.ambientOcclusion * materialData.ambientOcclusion * materialData.shadowOcclusion;
    float microShadow = clamp(abs(dot(l, n)) + aperture - 1.0, 0.0, 1.0);
    color.rgb *= microShadow;

    return color;
}


	vec4 physically_based_shading( deferredMaterialData materialData, deferredLightData lightData ) {

		//deferredLightData lightData;
		materialData.diffuse = (1.0 - materialData.metallic) * materialData.baseColor;

		// Geometric AA
		vec3 ndFdx = dFdx( materialData.normal );
		vec3 ndFdy = dFdy( materialData.normal );
		
		materialData.geometricRoughness = pow( max( dot(ndFdx, ndFdx), dot(ndFdy, ndFdy) ), 0.333);
		materialData.roughness = max(materialData.roughness, materialData.geometricRoughness);
		
		materialData.specularReflectance = 	0.16 * 
											materialData.reflectance * materialData.reflectance * (1.0 - materialData.metallic) + 
											materialData.baseColor * materialData.metallic;
											
									
		materialData.clearCoatRoughness 	= max( mix(0.089, 0.6, materialData.clearCoatRoughness), materialData.geometricRoughness);
		

		materialData.vertex = normalize( cameraPosition - materialData.position ); // v
		
		vec3 refracted_vertex = -refract(materialData.vertex, materialData.normal, 1.0);
		
		materialData.refracted_NoV = clamp(dot(materialData.normal, refracted_vertex), 0.0, 1.0);
		
		
		float alpha = 1.0;
		
		// indirect lighting
		vec3 color = indirectLight( materialData );
		
		color *= environmentLuminance;

		
		color += directLight( materialData, lightData );
		return vec4(color, alpha);
	}