#include "dungeon_to_dict.h"

#include <cstring>

#include <godot_cpp/classes/mesh.hpp>
#include <godot_cpp/variant/array.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/packed_vector3_array.hpp>
#include <godot_cpp/variant/packed_vector2_array.hpp>
#include <godot_cpp/variant/vector2i.hpp>
#include <godot_cpp/variant/vector3i.hpp>

extern "C" {
#include "blobber/cell3d.h"
}

using namespace godot;

namespace {

/* Match src/gen/grid.h terrain_t / liquid_t values. Kept local so
   dungeon_to_dict stays independent of gen/grid.h at compile time. */
constexpr uint8_t T_NOTHING    = 0;
constexpr uint8_t T_FLOOR      = 1;
constexpr uint8_t T_WALL       = 2;
constexpr uint8_t T_DOOR       = 3;
constexpr uint8_t T_LIQUID     = 5;
constexpr uint8_t T_BRIDGE     = 6;
constexpr uint8_t T_STAIRS_UP  = 7;
constexpr uint8_t T_STAIRS_DOWN= 8;

constexpr uint8_t L_NONE  = 0;
constexpr uint8_t L_WATER = 1;
constexpr uint8_t L_LAVA  = 2;
constexpr uint8_t L_CHASM = 3;

/* Translate a cell3d_t's floor type into the brogue exporter's
   (terrain, liquid) pair. Walls are represented by FT_VOID here — the
   blobber grid surrounds rooms with solid voids rather than emitting a
   T_WALL-equivalent cell, so VOID is what gives us the wall geometry. */
void floor_to_2d(uint8_t b_floor, uint8_t &terrain, uint8_t &liquid) {
    terrain = T_NOTHING;
    liquid  = L_NONE;
    switch (b_floor) {
        case FT_VOID:       terrain = T_WALL;         break;
        case FT_STONE:      terrain = T_FLOOR;        break;
        case FT_WATER:      terrain = T_LIQUID; liquid = L_WATER; break;
        case FT_LAVA:       terrain = T_LIQUID; liquid = L_LAVA;  break;
        case FT_CHASM:      terrain = T_LIQUID; liquid = L_CHASM; break;
        case FT_STAIR_UP:   terrain = T_STAIRS_UP;    break;
        case FT_STAIR_DOWN: terrain = T_STAIRS_DOWN;  break;
        case FT_BRIDGE:     terrain = T_BRIDGE;       break;
        case FT_DOOR_SILL:  terrain = T_DOOR;         break;
        default:            terrain = T_NOTHING;      break;
    }
}

} // namespace

static Array pack_group_arrays(const mesh_group_t *g) {
    Array arrays;
    arrays.resize(Mesh::ARRAY_MAX);

    const size_t n = g->vert_count;
    PackedVector3Array verts;   verts.resize((int)n);
    PackedVector3Array normals; normals.resize((int)n);
    PackedVector2Array uvs;     uvs.resize((int)n);

    Vector3 *vp = verts.ptrw();
    Vector3 *np = normals.ptrw();
    Vector2 *tp = uvs.ptrw();
    for (size_t i = 0; i < n; i++) {
        const mesh_vertex_t *v = &g->verts[i];
        vp[i] = Vector3(v->px, v->py, v->pz);
        np[i] = Vector3(v->nx, v->ny, v->nz);
        tp[i] = Vector2(v->u,  v->v);
    }
    arrays[Mesh::ARRAY_VERTEX] = verts;
    arrays[Mesh::ARRAY_NORMAL] = normals;
    arrays[Mesh::ARRAY_TEX_UV] = uvs;
    return arrays;
}

Dictionary godot::dungeon_to_dictionary(const dungeon_t *d, const mesh_build_t *mb,
                                       int seed, int depth, int num_levels) {
    Dictionary out;
    out["seed"]       = seed;
    out["depth"]      = depth;
    out["num_levels"] = num_levels;
    out["cell_size"]  = mb->cell_size;
    out["dimensions"] = Vector3i(BL_DCOLS, num_levels, BL_DROWS);

    // Raw voxel cell data for future mutation. Order: z-major (level,y,x).
    const size_t n_cells = (size_t)num_levels * BL_DROWS * BL_DCOLS;
    PackedByteArray cells;
    cells.resize((int)(n_cells * sizeof(cell3d_t)));
    memcpy(cells.ptrw(), d->cells, n_cells * sizeof(cell3d_t));
    out["cells"] = cells;

    // Per-material mesh surfaces. Only materials with content are emitted.
    Array meshes;
    for (int m = 0; m < MAT_COUNT; m++) {
        const mesh_group_t *g = &mb->groups[m];
        if (g->vert_count == 0) continue;
        Dictionary surf;
        surf["material"]  = m;
        surf["tri_count"] = (int)(g->vert_count / 3);
        surf["arrays"]    = pack_group_arrays(g);
        meshes.push_back(surf);
    }
    out["meshes"] = meshes;

    // Per-level metadata.
    Array levels;
    for (int i = 0; i < d->n_levels; i++) {
        const b_level_info_t *info = &d->levels[i];
        Dictionary lvl;
        lvl["index"]       = i;
        lvl["stairs_up"]   = Vector2i(info->stairs_up_x,   info->stairs_up_y);
        lvl["stairs_down"] = Vector2i(info->stairs_down_x, info->stairs_down_y);
        levels.push_back(lvl);
    }
    out["levels"] = levels;

    // Plan reserved for PR 3+; keep the key present so consumers can rely on it.
    Dictionary plan;
    plan["stairs"]     = Array();
    plan["chasms"]     = Array();
    plan["waterfalls"] = Array();
    plan["footprints"] = Array();
    out["plan"] = plan;

    return out;
}

TypedArray<Dictionary> godot::dungeon_to_2d_slices(const dungeon_t *d) {
    TypedArray<Dictionary> slices;
    if (!d) return slices;

    const int w = BL_DCOLS;
    const int h = BL_DROWS;
    const int per_level = w * h;

    for (int lvl = 0; lvl < d->n_levels; lvl++) {
        PackedByteArray terrain; terrain.resize(per_level);
        PackedByteArray liquid;  liquid.resize(per_level);
        uint8_t *tp = terrain.ptrw();
        uint8_t *lp = liquid.ptrw();

        const cell3d_t *base = &d->cells[lvl * per_level];
        for (int i = 0; i < per_level; i++) {
            uint8_t t, l;
            floor_to_2d(base[i].floor, t, l);
            tp[i] = t;
            lp[i] = l;
        }

        Dictionary slice;
        slice["width"]   = w;
        slice["height"]  = h;
        slice["terrain"] = terrain;
        slice["liquid"]  = liquid;
        slices.push_back(slice);
    }

    return slices;
}
