#include "VoxelMesher.h"
#include <cstring>
#include <algorithm>

namespace voxel {

// ── Build binary masks per axis ─────────────────────────────────
// For each axis, solid[u][v] is a 32-bit mask where bit i=1 means
// voxel at position i along that axis is solid.
void VoxelMesher::buildAxisMasks(const Chunk& chunk, AxisMasks masks[3]) {
    std::memset(masks, 0, sizeof(AxisMasks) * 3);

    for (int z = 0; z < CHUNK_SIZE; z++) {
        for (int y = 0; y < CHUNK_SIZE; y++) {
            for (int x = 0; x < CHUNK_SIZE; x++) {
                const VoxelData& v = chunk.at(x, y, z);
                // Phase 5: smooth voxels are still "solid" for face culling
                // (they block neighbor faces) but don't emit their own quads.
                // So they must be in the solid mask.
                if (!v.isEmpty()) {
                    // X-axis: march along X, indexed by [Y][Z]
                    masks[0].solid[y][z] |= (1u << x);
                    // Y-axis: march along Y, indexed by [X][Z]
                    masks[1].solid[x][z] |= (1u << y);
                    // Z-axis: march along Z, indexed by [X][Y]
                    masks[2].solid[x][y] |= (1u << z);
                }
            }
        }
    }
}

// Helper: get voxel, considering neighbor chunks for boundary faces
static VoxelData getVoxelSafe(const Chunk& chunk, const VoxelWorld& world, int x, int y, int z) {
    if (chunk.isInBounds(x, y, z)) {
        return chunk.at(x, y, z);
    }
    // Cross-chunk lookup
    int wx = chunk.pos.x * CHUNK_SIZE + x;
    int wy = chunk.pos.y * CHUNK_SIZE + y;
    int wz = chunk.pos.z * CHUNK_SIZE + z;
    return world.getVoxel(wx, wy, wz);
}

// ── Greedy Merge ────────────────────────────────────────────────
// For a given face direction, merge visible faces of the same material
// into maximal rectangular quads.
void VoxelMesher::greedyMerge(
    const Chunk& chunk,
    const VoxelWorld& world,
    uint8_t face,
    const uint32_t faceMasks[CHUNK_SIZE][CHUNK_SIZE],
    std::vector<PackedQuad>& outQuads
) {
    // Determine axis mapping based on face
    // face 0,1 = X axis -> iterate over Y,Z slices
    // face 2,3 = Y axis -> iterate over X,Z slices
    // face 4,5 = Z axis -> iterate over X,Y slices

    // For each slice along the face normal axis
    for (int depth = 0; depth < CHUNK_SIZE; depth++) {
        // Build a 2D grid of material IDs for this slice
        uint8_t matGrid[CHUNK_SIZE][CHUNK_SIZE];
        bool visited[CHUNK_SIZE][CHUNK_SIZE];
        std::memset(matGrid, 0, sizeof(matGrid));
        std::memset(visited, 0, sizeof(visited));

        int faceCount = 0;
        for (int v = 0; v < CHUNK_SIZE; v++) {
            for (int u = 0; u < CHUNK_SIZE; u++) {
                // Check if this face is visible at this depth
                bool faceVisible = false;

                int x, y, z;
                switch (face) {
                case FACE_POS_X: x = depth; y = u; z = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break;
                case FACE_NEG_X: x = depth; y = u; z = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break;
                case FACE_POS_Y: y = depth; x = u; z = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break;
                case FACE_NEG_Y: y = depth; x = u; z = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break;
                case FACE_POS_Z: z = depth; x = u; y = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break;
                case FACE_NEG_Z: z = depth; x = u; y = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break;
                }

                if (faceVisible) {
                    // Phase 5: skip smooth voxels (they have Surface Nets mesh)
                    const VoxelData& vd = chunk.at(x, y, z);
                    if (vd.isSmooth()) continue;
                    matGrid[v][u] = vd.getMaterialID();
                    faceCount++;
                }
            }
        }

        if (faceCount == 0) continue;

        // Greedy merge: scan row by row, merge same-material quads
        for (int v = 0; v < CHUNK_SIZE; v++) {
            for (int u = 0; u < CHUNK_SIZE; u++) {
                if (visited[v][u] || matGrid[v][u] == 0) continue;

                uint8_t mat = matGrid[v][u];

                // Expand width (along u)
                int w = 1;
                while (u + w < CHUNK_SIZE && !visited[v][u + w] && matGrid[v][u + w] == mat) {
                    w++;
                }

                // Expand height (along v)
                int h = 1;
                bool canExpand = true;
                while (v + h < CHUNK_SIZE && canExpand) {
                    for (int du = 0; du < w; du++) {
                        if (visited[v + h][u + du] || matGrid[v + h][u + du] != mat) {
                            canExpand = false;
                            break;
                        }
                    }
                    if (canExpand) h++;
                }

                // Mark as visited
                for (int dv = 0; dv < h; dv++) {
                    for (int du = 0; du < w; du++) {
                        visited[v + dv][u + du] = true;
                    }
                }

                // Compute the actual position in chunk-local coords
                uint8_t px, py, pz;
                switch (face) {
                case FACE_POS_X: px = (uint8_t)depth; py = (uint8_t)u; pz = (uint8_t)v; break;
                case FACE_NEG_X: px = (uint8_t)depth; py = (uint8_t)u; pz = (uint8_t)v; break;
                case FACE_POS_Y: px = (uint8_t)u; py = (uint8_t)depth; pz = (uint8_t)v; break;
                case FACE_NEG_Y: px = (uint8_t)u; py = (uint8_t)depth; pz = (uint8_t)v; break;
                case FACE_POS_Z: px = (uint8_t)u; py = (uint8_t)v; pz = (uint8_t)depth; break;
                case FACE_NEG_Z: px = (uint8_t)u; py = (uint8_t)v; pz = (uint8_t)depth; break;
                default: px = py = pz = 0; break;
                }

                // Width/height in the quad's local UV space
                uint8_t qw, qh;
                switch (face) {
                case FACE_POS_X: case FACE_NEG_X: qw = (uint8_t)w; qh = (uint8_t)h; break;
                case FACE_POS_Y: case FACE_NEG_Y: qw = (uint8_t)w; qh = (uint8_t)h; break;
                case FACE_POS_Z: case FACE_NEG_Z: qw = (uint8_t)w; qh = (uint8_t)h; break;
                default: qw = qh = 1; break;
                }

                outQuads.push_back(PackedQuad::create(
                    px, py, pz, qw, qh, face, mat, 0, 0
                ));
            }
        }
    }
}

uint32_t VoxelMesher::meshChunk(Chunk& chunk, const VoxelWorld& world) {
    chunk.quads.clear();

    // Step 1: Build binary solid masks per axis
    AxisMasks axisMasks[3];
    buildAxisMasks(chunk, axisMasks);

    // Step 2: For each face direction, compute visible face masks
    // then do greedy merge
    for (uint8_t face = 0; face < FACE_COUNT; face++) {
        int axis = face / 2;       // 0=X, 1=Y, 2=Z
        bool positive = (face % 2 == 0);

        // Compute visible face masks
        // A face is visible if the voxel is solid and the neighbor in the face direction is air
        uint32_t faceMasks[CHUNK_SIZE][CHUNK_SIZE];
        std::memset(faceMasks, 0, sizeof(faceMasks));

        for (int v = 0; v < CHUNK_SIZE; v++) {
            for (int u = 0; u < CHUNK_SIZE; u++) {
                uint32_t solid = axisMasks[axis].solid[u][v];
                if (solid == 0) continue;

                uint32_t visible;
                if (positive) {
                    // +dir: face visible if solid here and NOT solid at pos+1
                    // Shift right: neighbor is at bit+1
                    uint32_t neighbor = (solid >> 1);
                    // The highest bit has no neighbor in this chunk - check boundary
                    visible = solid & ~neighbor;
                    // Bit 31 (chunk boundary): need to check neighbor chunk
                    // For now, always show boundary faces
                    if (solid & (1u << (CHUNK_SIZE - 1))) {
                        // Check if neighbor chunk's voxel is empty
                        int nx, ny, nz;
                        switch (axis) {
                        case 0: nx = CHUNK_SIZE; ny = u; nz = v; break; // X+
                        case 1: nx = u; ny = CHUNK_SIZE; nz = v; break; // Y+
                        case 2: nx = u; ny = v; nz = CHUNK_SIZE; break; // Z+
                        default: nx = ny = nz = 0;
                        }
                        if (!getVoxelSafe(chunk, world, nx, ny, nz).isEmpty()) {
                            visible &= ~(1u << (CHUNK_SIZE - 1)); // hide boundary face
                        }
                    }
                } else {
                    // -dir: face visible if solid here and NOT solid at pos-1
                    uint32_t neighbor = (solid << 1);
                    visible = solid & ~neighbor;
                    // Bit 0 (chunk boundary)
                    if (solid & 1u) {
                        int nx, ny, nz;
                        switch (axis) {
                        case 0: nx = -1; ny = u; nz = v; break;
                        case 1: nx = u; ny = -1; nz = v; break;
                        case 2: nx = u; ny = v; nz = -1; break;
                        default: nx = ny = nz = 0;
                        }
                        if (!getVoxelSafe(chunk, world, nx, ny, nz).isEmpty()) {
                            visible &= ~1u; // hide boundary face
                        }
                    }
                }

                faceMasks[u][v] = visible;
            }
        }

        uint32_t beforeCount = (uint32_t)chunk.quads.size();
        greedyMerge(chunk, world, face, faceMasks, chunk.quads);
        chunk.faceOffsets[face] = beforeCount;
        chunk.faceCounts[face] = (uint32_t)chunk.quads.size() - beforeCount;
    }

    chunk.quadCount = (uint32_t)chunk.quads.size();
    chunk.dirty = false;
    return chunk.quadCount;
}

uint8_t VoxelMesher::calcAO(const VoxelWorld& world, const ChunkPos& cpos,
                             int x, int y, int z, uint8_t face) {
    // Simplified AO: count occluding neighbors around the vertex
    // Returns packed 4x2-bit AO for the 4 corners
    // TODO: implement proper per-corner AO
    return 0;
}

// ══════════════════════════════════════════════════════════════════
// ── Smooth meshing (Phase 5) ────────────────────────────────────
// ══════════════════════════════════════════════════════════════════
// The CPU SmoothMesher has been removed. Smooth meshing is now handled
// exclusively by the GPU compute shaders (voxelSmoothCentroidCS.hlsl
// + voxelSmoothCS.hlsl) which include crease-angle correction for
// correct normals at sharp edges (e.g. vertical walls).

} // namespace voxel
