#include "pipeline.h"

#include <string.h>

#include "../gen/grid.h"
#include "../gen/rng.h"
#include "../gen/events.h"
#include "../gen/accretion.h"
#include "../gen/loops.h"
#include "../gen/walls.h"
#include "../gen/stairs.h"

/* Map a 2D terrain byte to its 3D floor type. */
static uint8_t floor_from_terrain(uint8_t t, uint8_t liquid) {
    switch (t) {
        case T_FLOOR:       return FT_STONE;
        case T_CORRIDOR:    return FT_STONE;
        case T_DOOR:        return FT_DOOR_SILL;
        case T_STAIRS_UP:   return FT_STAIR_UP;
        case T_STAIRS_DOWN: return FT_STAIR_DOWN;
        case T_BRIDGE:      return FT_BRIDGE;
        case T_LIQUID:
            switch (liquid) {
                case L_WATER: case L_BRIMSTONE: return FT_WATER;
                case L_LAVA:                    return FT_LAVA;
                case L_CHASM:                   return FT_CHASM;
                default:                        return FT_WATER;
            }
        case T_WALL:
        case T_NOTHING:
        default:
            return FT_VOID;
    }
}

static uint8_t style_from_terrain(uint8_t t) {
    switch (t) {
        case T_FLOOR:    return S_ROOM;
        case T_CORRIDOR: return S_CORRIDOR;
        case T_DOOR:     return S_DOOR;
        default:         return S_VOID;
    }
}

/* For each standable cell, if the 4-neighbor isn't standable, we have a solid
   wall on that face. "Standable" in a 2D sense = any floor that isn't FT_VOID.
   Chasms (F_CHASM) are walkable-over-but-fall-through; we still put walls
   around them when the neighbor is solid, but they're a later-PR concern. */
static void compute_face_walls(dungeon_t *d, int lvl) {
    static const int dx[4] = { 0,  1,  0, -1 };
    static const int dy[4] = {-1,  0,  1,  0 };
    for (int y = 0; y < BL_DROWS; y++) {
        for (int x = 0; x < BL_DCOLS; x++) {
            cell3d_t *c = dungeon_cell(d, lvl, x, y);
            if (c->floor == FT_VOID) continue;
            for (int f = 0; f < 4; f++) {
                int nx = x + dx[f];
                int ny = y + dy[f];
                int solid;
                if (!b_in_bounds(nx, ny)) {
                    solid = 1;
                } else {
                    const cell3d_t *n = dungeon_cell_c(d, lvl, nx, ny);
                    solid = (n->floor == FT_VOID);
                }
                c->walls[f] = solid ? W_SOLID : W_NONE;
            }
        }
    }
}

/* Run the legacy 2D pipeline into a scratch grid_t. */
static void gen_level_2d(grid_t g, uint64_t seed, int depth) {
    rng_t rng;
    rng_seed(&rng, seed);
    grid_fill(g, T_NOTHING);

    event_sink_t sink = { .fn = NULL, .ctx = NULL, .step = 0 };
    (void)depth; /* lakes/machines deferred to later PRs */

    accrete_rooms(g, &rng, &sink);
    add_loops(g, &sink);
    finish_walls(g, &sink);
    place_stairs(g, &sink);
}

static void materialize_level(dungeon_t *d, int lvl, grid_t g) {
    b_level_info_t *info = &d->levels[lvl];
    for (int y = 0; y < BL_DROWS; y++) {
        for (int x = 0; x < BL_DCOLS; x++) {
            const cell_t *src = &g[y][x];
            cell3d_t *dst = dungeon_cell(d, lvl, x, y);

            dst->floor   = floor_from_terrain(src->terrain, src->liquid);
            dst->ceiling = C_STONE;
            dst->marker  = src->surface;
            dst->style   = style_from_terrain(src->terrain);
            dst->room_id = src->room_id;
            dst->machine_id = src->machine_id;
            dst->flags = 0;

            if (src->terrain == T_STAIRS_UP) {
                info->stairs_up_x = x; info->stairs_up_y = y;
            } else if (src->terrain == T_STAIRS_DOWN) {
                info->stairs_down_x = x; info->stairs_down_y = y;
            }
        }
    }
    compute_face_walls(d, lvl);
}

/* Count cells that are FT_STONE (and optionally S_ROOM) on both levels. */
static int count_shared_floor(const dungeon_t *d, int la, int lb, int require_room) {
    int n = 0;
    for (int y = 0; y < BL_DROWS; y++) {
        for (int x = 0; x < BL_DCOLS; x++) {
            const cell3d_t *ca = dungeon_cell_c(d, la, x, y);
            const cell3d_t *cb = dungeon_cell_c(d, lb, x, y);
            if (ca->floor != FT_STONE || cb->floor != FT_STONE) continue;
            if (require_room && (ca->style != S_ROOM || cb->style != S_ROOM)) continue;
            n++;
        }
    }
    return n;
}

static int pick_nth_shared(const dungeon_t *d, int la, int lb, int require_room,
                           int target, int *ox, int *oy) {
    int n = 0;
    for (int y = 0; y < BL_DROWS; y++) {
        for (int x = 0; x < BL_DCOLS; x++) {
            const cell3d_t *ca = dungeon_cell_c(d, la, x, y);
            const cell3d_t *cb = dungeon_cell_c(d, lb, x, y);
            if (ca->floor != FT_STONE || cb->floor != FT_STONE) continue;
            if (require_room && (ca->style != S_ROOM || cb->style != S_ROOM)) continue;
            if (n == target) { *ox = x; *oy = y; return 1; }
            n++;
        }
    }
    return 0;
}

/* If (x,y) on level lvl currently holds expected_floor, reset it to FT_STONE.
   Used to undo the per-level 2D stair placement before dropping an aligned
   stair pair at a new XY. */
static void clear_stair_cell(dungeon_t *d, int lvl, int x, int y, uint8_t expected_floor) {
    if (x < 0 || y < 0 || !b_in_bounds(x, y)) return;
    cell3d_t *c = dungeon_cell(d, lvl, x, y);
    if (c->floor == expected_floor) c->floor = FT_STONE;
}

/* Replace each level's per-level 2D stair placement with aligned stair pairs
   so level L's down-stair and level L+1's up-stair share the same XY. The
   bottom level's down-stair is removed (no level below). The top level keeps
   its existing up-stair as the party's entry point. Does nothing when
   n_levels < 2. */
static void link_stairs(dungeon_t *d, uint64_t seed) {
    if (d->n_levels < 2) return;

    rng_t rng;
    rng_seed(&rng, seed ^ 0xB106B3D5B4B83A07ULL);

    for (int lvl = 0; lvl < d->n_levels - 1; lvl++) {
        clear_stair_cell(d, lvl,     d->levels[lvl].stairs_down_x,
                                     d->levels[lvl].stairs_down_y, FT_STAIR_DOWN);
        clear_stair_cell(d, lvl + 1, d->levels[lvl + 1].stairs_up_x,
                                     d->levels[lvl + 1].stairs_up_y, FT_STAIR_UP);

        int sx = -1, sy = -1;
        int n_rooms = count_shared_floor(d, lvl, lvl + 1, 1);
        if (n_rooms > 0) {
            int pick = (int)(rng_u32(&rng) % (uint32_t)n_rooms);
            pick_nth_shared(d, lvl, lvl + 1, 1, pick, &sx, &sy);
        } else {
            int n_any = count_shared_floor(d, lvl, lvl + 1, 0);
            if (n_any > 0) {
                int pick = (int)(rng_u32(&rng) % (uint32_t)n_any);
                pick_nth_shared(d, lvl, lvl + 1, 0, pick, &sx, &sy);
            }
        }
        if (sx < 0) {
            /* No shared standable cell — leave this boundary unlinked. */
            d->levels[lvl].stairs_down_x = -1;
            d->levels[lvl].stairs_down_y = -1;
            d->levels[lvl + 1].stairs_up_x = -1;
            d->levels[lvl + 1].stairs_up_y = -1;
            continue;
        }

        dungeon_cell(d, lvl,     sx, sy)->floor = FT_STAIR_DOWN;
        dungeon_cell(d, lvl + 1, sx, sy)->floor = FT_STAIR_UP;
        d->levels[lvl].stairs_down_x = sx;
        d->levels[lvl].stairs_down_y = sy;
        d->levels[lvl + 1].stairs_up_x = sx;
        d->levels[lvl + 1].stairs_up_y = sy;
    }

    int last = d->n_levels - 1;
    clear_stair_cell(d, last, d->levels[last].stairs_down_x,
                              d->levels[last].stairs_down_y, FT_STAIR_DOWN);
    d->levels[last].stairs_down_x = -1;
    d->levels[last].stairs_down_y = -1;
}

dungeon_t *blobber_generate(uint64_t seed, int n_levels, int depth) {
    if (n_levels < 1) n_levels = 1;
    dungeon_t *d = dungeon_create(n_levels);
    if (!d) return NULL;

    grid_t g;
    for (int lvl = 0; lvl < n_levels; lvl++) {
        uint64_t level_seed = seed + (uint64_t)lvl * 0x9E3779B97F4A7C15ULL;
        gen_level_2d(g, level_seed, depth);
        materialize_level(d, lvl, g);
    }
    link_stairs(d, seed);
    return d;
}
