layout (local_size_x = 8, local_size_y = 8, local_size_z = 8) in;
#if GI_VOXEL_RESOLUTION == 64
    const ivec3 workGroups = ivec3(8, 8, 8);
#elif GI_VOXEL_RESOLUTION == 128
    const ivec3 workGroups = ivec3(16, 16, 16);
#elif GI_VOXEL_RESOLUTION == 192
    const ivec3 workGroups = ivec3(24, 24, 24);
#elif GI_VOXEL_RESOLUTION == 256
    const ivec3 workGroups = ivec3(32, 32, 32);
#endif

//Common Variables//
uniform sampler3D gi_voxel_sampler;

#if GI_PARITY == 0
    uniform sampler3D gi_radiance_a_sampler;
    writeonly uniform image3D gi_radiance_b_img;
#else
    uniform sampler3D gi_radiance_b_sampler;
    writeonly uniform image3D gi_radiance_a_img;
#endif

#if GI_TEMPORAL_ACCUM == 1 && defined GI_IS_LAST_PASS
    writeonly uniform image3D gi_history_img;
#endif

//Includes//
#define GI_NEED_READ_SAMPLERS
#include "/lib/voxelization/giFloodfill.glsl"

// ================================================================
//  GLSL 1.30-compatible offset helpers (no const arrays)
// ================================================================

// Emission direction: returns one of 14 directions (6 face + 8 corner)
vec3 EmitDirection(int i) {
    if (i == 0) return vec3( 1, 0, 0);
    if (i == 1) return vec3(-1, 0, 0);
    if (i == 2) return vec3( 0, 1, 0);
    if (i == 3) return vec3( 0,-1, 0);
    if (i == 4) return vec3( 0, 0, 1);
    if (i == 5) return vec3( 0, 0,-1);
    if (i == 6) return vec3( 1, 1, 1);
    if (i == 7) return vec3( 1, 1,-1);
    if (i == 8) return vec3( 1,-1, 1);
    if (i == 9) return vec3( 1,-1,-1);
    if (i ==10) return vec3(-1, 1, 1);
    if (i ==11) return vec3(-1, 1,-1);
    if (i ==12) return vec3(-1,-1, 1);
    return vec3(-1,-1,-1);
}

// Neighbor offset: returns one of 32 directions (6 face + 12 edge + 8 corner
// + 6 extended face at distance 2, for a wider single-pass reach)
vec3 NeighborOffset(int i) {
    if (i < 6) {
        // Face (6)
        if (i == 0) return vec3( 1, 0, 0);
        if (i == 1) return vec3(-1, 0, 0);
        if (i == 2) return vec3( 0, 1, 0);
        if (i == 3) return vec3( 0,-1, 0);
        if (i == 4) return vec3( 0, 0, 1);
        return vec3( 0, 0,-1);
    }
    if (i < 18) {
        // Edge (12)
        int e = i - 6;
        if (e == 0) return vec3( 1, 1, 0);
        if (e == 1) return vec3( 1,-1, 0);
        if (e == 2) return vec3(-1, 1, 0);
        if (e == 3) return vec3(-1,-1, 0);
        if (e == 4) return vec3( 1, 0, 1);
        if (e == 5) return vec3( 1, 0,-1);
        if (e == 6) return vec3(-1, 0, 1);
        if (e == 7) return vec3(-1, 0,-1);
        if (e == 8) return vec3( 0, 1, 1);
        if (e == 9) return vec3( 0, 1,-1);
        if (e ==10) return vec3( 0,-1, 1);
        return vec3( 0,-1,-1);
    }
    if (i < 26) {
        // Corner (8)
        int c = i - 18;
        if (c == 0) return vec3( 1, 1, 1);
        if (c == 1) return vec3( 1, 1,-1);
        if (c == 2) return vec3( 1,-1, 1);
        if (c == 3) return vec3( 1,-1,-1);
        if (c == 4) return vec3(-1, 1, 1);
        if (c == 5) return vec3(-1, 1,-1);
        if (c == 6) return vec3(-1,-1, 1);
        return vec3(-1,-1,-1);
    }
    // Extended face (6) at distance 2 - wider single-pass reach so bounce
    // travels further per pass (needed to actually cross a room-sized gap
    // and reach walls/ceiling instead of stalling near the emitting surface)
    int f = i - 26;
    if (f == 0) return vec3( 2, 0, 0);
    if (f == 1) return vec3(-2, 0, 0);
    if (f == 2) return vec3( 0, 2, 0);
    if (f == 3) return vec3( 0,-2, 0);
    if (f == 4) return vec3( 0, 0, 2);
    return vec3( 0, 0,-2);
}

// Distance for neighbor index: face=1, edge=1.414, corner=1.732, extended face=2.0
float NeighborDistance(int i) {
    if (i < 6) return 1.0;
    if (i < 18) return 1.414;
    if (i < 26) return 1.732;
    return 2.0;
}

//Program//
void main() {
    ivec3 pos = ivec3(gl_GlobalInvocationID);

    #if GI_PARITY == 0
        vec4 selfRadiance = GiLoadRadiance(gi_radiance_a_sampler, pos);
    #else
        vec4 selfRadiance = GiLoadRadiance(gi_radiance_b_sampler, pos);
    #endif

    vec4 albedoOpacity = GiLoadAlbedoOpacity(pos);
    bool isSolid = albedoOpacity.a > 0.5;

    vec3 radianceNew = selfRadiance.rgb;

    // ================================================================
    //  STEP 1: SOLID VOXELS EMIT INTO OPEN SPACE
    // ================================================================
    if (isSolid && GetLuminance(selfRadiance.rgb) > 0.001) {
        vec3 voxelNormal = GiEstimateVoxelNormal(pos);

        for (int d = 0; d < 14; d++) {
            vec3 dir = normalize(EmitDirection(d));

            float normalDot = max(0.0, dot(dir, voxelNormal));
            float emitWeight = mix(0.3, 1.0, normalDot);

            // Ray march to find open space extent
            float openSpace = 0.0;
            const int MAX_MARCH = 10;

            for (int step = 1; step <= MAX_MARCH; step++) {
                ivec3 checkPos = pos + ivec3(round(dir * float(step)));
                if (!GiCheckInsideVolume(vec3(checkPos))) break;
                vec4 checkAlbedo = GiLoadAlbedoOpacity(checkPos);
                if (checkAlbedo.a > 0.5) break;
                openSpace += 1.0;
            }

            if (openSpace < 0.5) continue;

            // Open-space boost: wider open space around the emitter = more
            // of that light is allowed to travel further (this is the main
            // knob that makes bounce reach walls/ceiling across a room
            // instead of staying a tight blob near the source).
            // ("kotak highlight di sudut block" fix): diturunkan dari 0.18 ->
            // 0.10. Boost ini menguatkan cahaya di SEMUA 14 arah emisi secara
            // seragam, jadi voxel terang memancarkan halo kubus yang menempel
            // erat di siluet block (paling terang di pojok atas/bawah/kanan/kiri,
            // baca sebagai highlight tebal). Amplifikasi yang lebih lemah tetap
            // membawa bounce ke dinding ruangan lewat beberapa lintasan
            // GI_BOUNCE_PASSES, tapi tanpa mengecat "kotak" di sekitar sumber.
            float spaceBoost = 1.0 + openSpace * 0.10;

            for (int step = 1; step <= int(openSpace); step++) {
                ivec3 airPos = pos + ivec3(round(dir * float(step)));
                if (!GiCheckInsideVolume(vec3(airPos))) break;

                vec4 airAlbedo = GiLoadAlbedoOpacity(airPos);
                if (airAlbedo.a > 0.5) break;

                #if GI_PARITY == 0
                    vec4 airRad = GiLoadRadiance(gi_radiance_a_sampler, airPos);
                #else
                    vec4 airRad = GiLoadRadiance(gi_radiance_b_sampler, airPos);
                #endif

                float distFade = 1.0 / (1.0 + float(step) * 0.55);
                distFade *= spaceBoost;

                vec3 emittedLight = selfRadiance.rgb * emitWeight * distFade * 0.35;
                vec3 airNew = airRad.rgb + emittedLight;

                #if GI_PARITY == 0
                    imageStore(gi_radiance_b_img, airPos, vec4(airNew, 0.0));
                #else
                    imageStore(gi_radiance_a_img, airPos, vec4(airNew, 0.0));
                #endif
            }
        }
    }

    // ================================================================
    //  STEP 2: AIR VOXELS GATHER FROM EMITTING SURFACES
    // ================================================================
    if (!isSolid) {
        vec3 gatheredSum = vec3(0.0);
        float gatheredWeight = 0.0;

        for (int i = 0; i < 32; i++) {
            vec3 off = NeighborOffset(i);
            ivec3 np = pos + ivec3(off);
            if (!GiCheckInsideVolume(vec3(np))) continue;

            vec4 neighborAlbedo = GiLoadAlbedoOpacity(np);
            if (neighborAlbedo.a < 0.5) continue;

            #if GI_PARITY == 0
                vec3 neighborRad = GiLoadRadiance(gi_radiance_a_sampler, np).rgb;
            #else
                vec3 neighborRad = GiLoadRadiance(gi_radiance_b_sampler, np).rgb;
            #endif

            if (GetLuminance(neighborRad) < 0.001) continue;

            float dist = NeighborDistance(i);
            float w = 1.0 / (1.0 + dist * 0.5);

            gatheredSum += neighborRad * w;
            gatheredWeight += w;
        }

        // ("bounce nempel di permukaan, tidak nyebar ke udara" fix): ALSO
        // gather from AIR neighbours (face-only, i < 6). Before this, only
        // SOLID neighbours were gathered, so radiance only ever sat in the
        // air cells directly beside a bright surface and never travelled
        // through the middle of the room - it read as bounce "sticking" to
        // walls/ceiling with the room core staying dark. Face-only air
        // gathering is a safe diffusion (1 voxel per pass, and it skips solid
        // cells so it can never cross a wall) that fills the whole room
        // volume evenly over the GI_BOUNCE_PASSES iterations.
        for (int i = 0; i < 6; i++) {
            vec3 off = NeighborOffset(i);
            ivec3 np = pos + ivec3(off);
            if (!GiCheckInsideVolume(vec3(np))) continue;

            vec4 na = GiLoadAlbedoOpacity(np);
            if (na.a >= 0.5) continue; // solid blocks diffusion

            #if GI_PARITY == 0
                vec3 airDiff = GiLoadRadiance(gi_radiance_a_sampler, np).rgb;
            #else
                vec3 airDiff = GiLoadRadiance(gi_radiance_b_sampler, np).rgb;
            #endif

            if (GetLuminance(airDiff) < 0.001) continue;

            float w = 1.0 / (1.0 + 1.0 * 0.5);
            gatheredSum += airDiff * w;
            gatheredWeight += w;
        }

        if (gatheredWeight > 0.001) {
            vec3 volumeLight = gatheredSum / gatheredWeight;
            radianceNew = mix(radianceNew, volumeLight, 0.55);
        }
    }

    // ================================================================
    //  STEP 3: SOLID VOXELS READ VOLUME RADIANCE
    // ================================================================
    if (isSolid) {
        vec3 volumeSum = vec3(0.0);
        float volumeWeight = 0.0;
        vec3 voxelNormal = GiEstimateVoxelNormal(pos);

        for (int i = 0; i < 32; i++) {
            vec3 off = NeighborOffset(i);
            ivec3 np = pos + ivec3(off);
            if (!GiCheckInsideVolume(vec3(np))) continue;

            vec4 neighborAlbedo = GiLoadAlbedoOpacity(np);
            if (neighborAlbedo.a > 0.5) continue;

            #if GI_PARITY == 0
                vec3 airRad = GiLoadRadiance(gi_radiance_a_sampler, np).rgb;
            #else
                vec3 airRad = GiLoadRadiance(gi_radiance_b_sampler, np).rgb;
            #endif

            if (GetLuminance(airRad) < 0.001) continue;

            float dist = NeighborDistance(i);
            float w = 1.0 / (1.0 + dist * 0.5);

            vec3 toAir = normalize(vec3(np) - vec3(pos));
            float normalBias = max(0.0, dot(voxelNormal, toAir));

            volumeSum += airRad * w * normalBias;
            volumeWeight += w * normalBias;
        }

        if (volumeWeight > 0.001) {
            vec3 indirectLight = volumeSum / volumeWeight;
            indirectLight *= albedoOpacity.rgb;
            radianceNew += indirectLight * 0.55;
        }
    }

    // ================================================================
    //  STEP 4: ENERGY DECAY & CLAMP
    // ================================================================
    float decay = float(GI_DECAY);
    radianceNew *= decay;
    radianceNew = min(radianceNew, vec3(4.5));

    // Write result
    #if GI_PARITY == 0
        imageStore(gi_radiance_b_img, pos, vec4(radianceNew, 0.0));
    #else
        imageStore(gi_radiance_a_img, pos, vec4(radianceNew, 0.0));
    #endif

    #if GI_TEMPORAL_ACCUM == 1 && defined GI_IS_LAST_PASS
        imageStore(gi_history_img, pos, vec4(radianceNew, 0.0));
    #endif
}
