#include "accretion.h"
#include "room_types.h"
#include <string.h>

typedef struct {
    int x, y;
    int dx, dy;
} door_site_t;

#define MAX_SITES 512
#define MAX_ATTEMPTS 30

static int is_open(uint8_t terrain) {
    return terrain == T_FLOOR || terrain == T_CORRIDOR || terrain == T_DOOR;
}

/* Find door sites: FLOOR cells with a NOTHING neighbor in one of 4 cardinal
   directions. dx,dy is the outward direction (floor -> nothing). */
static int find_main_sites(grid_t g, door_site_t *out, int cap) {
    static const int dirs[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
    int n = 0;
    for (int y = 1; y < DROWS - 1 && n < cap; y++) {
        for (int x = 1; x < DCOLS - 1 && n < cap; x++) {
            if (g[y][x].terrain != T_FLOOR) continue;
            for (int d = 0; d < 4; d++) {
                int nx = x + dirs[d][0];
                int ny = y + dirs[d][1];
                if (!grid_in_bounds(nx, ny)) continue;
                if (g[ny][nx].terrain == T_NOTHING) {
                    out[n].x = x; out[n].y = y;
                    out[n].dx = dirs[d][0]; out[n].dy = dirs[d][1];
                    if (++n >= cap) return n;
                }
            }
        }
    }
    return n;
}

/* Find scratch sites: FLOOR cells with a NOTHING neighbor outward. */
static int find_scratch_sites(grid_t g, door_site_t *out, int cap) {
    return find_main_sites(g, out, cap);
}

/* Corridor path: door is path[0]; subsequent cells are corridor tiles; after
   the last path cell comes the entry floor where the scratch room blits. A
   straight corridor has a monotonic path; an L-bent one has one inflection. */
#define MAX_PATH 24

typedef struct {
    int x[MAX_PATH];
    int y[MAX_PATH];
    int len;
    int entry_x, entry_y;
} corridor_path_t;

/* Check whether placement is valid: all corridor path cells are NOTHING on
   main, entry is NOTHING, scratch floor lands on NOTHING with 1-cell buffer
   against existing rooms. */
static int fits_placement(grid_t scratch, grid_t main_g,
                          int tx, int ty, const corridor_path_t *p) {
    for (int i = 0; i < p->len; i++) {
        if (!grid_in_bounds(p->x[i], p->y[i])) return 0;
        if (main_g[p->y[i]][p->x[i]].terrain != T_NOTHING) return 0;
    }
    if (!grid_in_bounds(p->entry_x, p->entry_y)) return 0;
    if (main_g[p->entry_y][p->entry_x].terrain != T_NOTHING) return 0;

    for (int sy = 0; sy < DROWS; sy++) {
        for (int sx = 0; sx < DCOLS; sx++) {
            if (scratch[sy][sx].terrain != T_FLOOR) continue;
            int mx = sx + tx;
            int my = sy + ty;
            if (mx < 1 || my < 1 || mx >= DCOLS - 1 || my >= DROWS - 1) return 0;
            if (main_g[my][mx].terrain != T_NOTHING) return 0;

            for (int ny = -1; ny <= 1; ny++) {
                for (int nx = -1; nx <= 1; nx++) {
                    if (nx == 0 && ny == 0) continue;
                    int qx = mx + nx;
                    int qy = my + ny;
                    if (!grid_in_bounds(qx, qy)) continue;
                    /* neighbor is another translated scratch floor? ok */
                    int ssx = qx - tx, ssy = qy - ty;
                    if (ssx >= 0 && ssx < DCOLS && ssy >= 0 && ssy < DROWS
                        && scratch[ssy][ssx].terrain == T_FLOOR) continue;
                    /* neighbor is on the corridor path? ok (we're about to
                       carve it; NOTHING at check time). */
                    if (is_open(main_g[qy][qx].terrain)) return 0;
                }
            }
        }
    }
    return 1;
}

/* Keep only the largest 4-connected FLOOR region in scratch; zero others to
   NOTHING. Prevents disconnected multi-bump rooms (e.g., chunky with
   non-overlapping circles) from creating orphan rooms on main. */
static void scratch_keep_largest(grid_t scratch) {
    static int label[DROWS][DCOLS];
    memset(label, 0, sizeof(label));
    static int sx_stack[DCOLS * DROWS];
    static int sy_stack[DCOLS * DROWS];
    int best = 0, best_size = 0, next = 0;
    static const int dx4[4] = {1, -1, 0, 0};
    static const int dy4[4] = {0, 0, 1, -1};

    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++) {
            if (scratch[y][x].terrain != T_FLOOR || label[y][x]) continue;
            next++;
            int sp = 0;
            sx_stack[sp] = x; sy_stack[sp] = y; sp++;
            int size = 0;
            while (sp > 0) {
                sp--;
                int cx = sx_stack[sp], cy = sy_stack[sp];
                if (!grid_in_bounds(cx, cy)) continue;
                if (label[cy][cx] || scratch[cy][cx].terrain != T_FLOOR) continue;
                label[cy][cx] = next;
                size++;
                for (int i = 0; i < 4; i++) {
                    sx_stack[sp] = cx + dx4[i];
                    sy_stack[sp] = cy + dy4[i];
                    sp++;
                }
            }
            if (size > best_size) { best_size = size; best = next; }
        }
    }
    if (next <= 1) return;
    for (int y = 0; y < DROWS; y++)
        for (int x = 0; x < DCOLS; x++)
            if (scratch[y][x].terrain == T_FLOOR && label[y][x] != best) {
                scratch[y][x].terrain = T_NOTHING;
                scratch[y][x].flags = 0;
                scratch[y][x].room_id = 0;
            }
}

static void blit_room(grid_t scratch, grid_t main_g, int tx, int ty, uint8_t room_id) {
    for (int sy = 0; sy < DROWS; sy++) {
        for (int sx = 0; sx < DCOLS; sx++) {
            if (scratch[sy][sx].terrain != T_FLOOR) continue;
            int mx = sx + tx, my = sy + ty;
            if (!grid_in_bounds(mx, my)) continue;
            main_g[my][mx].terrain = T_FLOOR;
            main_g[my][mx].room_id = room_id;
            main_g[my][mx].flags |= F_IN_ROOM;
        }
    }
}

static void carve_path(grid_t g, const corridor_path_t *p, uint8_t room_id) {
    if (p->len == 0) return;
    g[p->y[0]][p->x[0]].terrain = T_DOOR;
    g[p->y[0]][p->x[0]].room_id = room_id;
    for (int i = 1; i < p->len; i++) {
        g[p->y[i]][p->x[i]].terrain = T_CORRIDOR;
        g[p->y[i]][p->x[i]].room_id = room_id;
    }
}

static void build_straight(corridor_path_t *p,
                           int door_x, int door_y, int len, int dx, int dy) {
    if (len > MAX_PATH) len = MAX_PATH;
    p->len = len;
    for (int i = 0; i < len; i++) {
        p->x[i] = door_x + i * dx;
        p->y[i] = door_y + i * dy;
    }
    p->entry_x = door_x + len * dx;
    p->entry_y = door_y + len * dy;
}

/* L-bent corridor: L1 cells in (dx1,dy1), then L2 cells in (dx2,dy2). dx2/dy2
   must be perpendicular to dx1/dy1. Total path cells = L1 + L2 (≤ MAX_PATH).
   Entry is one step past the last leg2 cell. */
static void build_lbent(corridor_path_t *p,
                        int door_x, int door_y,
                        int L1, int dx1, int dy1,
                        int L2, int dx2, int dy2) {
    int total = L1 + L2;
    if (total > MAX_PATH) total = MAX_PATH;
    p->len = total;
    int i = 0;
    for (int k = 0; k < L1 && i < total; k++, i++) {
        p->x[i] = door_x + k * dx1;
        p->y[i] = door_y + k * dy1;
    }
    int ex = door_x + (L1 - 1) * dx1;
    int ey = door_y + (L1 - 1) * dy1;
    for (int k = 1; k <= L2 && i < total; k++, i++) {
        p->x[i] = ex + k * dx2;
        p->y[i] = ey + k * dy2;
    }
    p->entry_x = ex + (L2 + 1) * dx2;
    p->entry_y = ey + (L2 + 1) * dy2;
}

int accrete_rooms(grid_t g, rng_t *rng, event_sink_t *sink) {
    /* First room at center. Cavern is only attempted here (too big for the
       accretion buffer check to fit against existing rooms). */
    grid_fill(g, T_NOTHING);
    uint8_t room_id = 1;
    room_type_t t0;
    if (rng_percent(rng, 25)) {
        t0 = ROOM_CAVERN;
    } else {
        /* pick any non-cavern type */
        t0 = (room_type_t)rng_range(rng, 0, ROOM_COUNT - 2);
        if (t0 == ROOM_CAVERN) t0 = ROOM_SMALL_RECT;
    }
    int tries = 0;
    while (!room_carve(g, t0, rng, DCOLS/2, DROWS/2, room_id) && tries++ < 8) {
        t0 = (room_type_t)rng_range(rng, 0, ROOM_COUNT - 2);
        if (t0 == ROOM_CAVERN) t0 = ROOM_SMALL_RECT;
    }
    scratch_keep_largest(g);
    event_emit(sink, EV_ROOM_PLACED, NULL, 0);

    grid_t scratch;
    door_site_t main_sites[MAX_SITES];
    door_site_t scratch_sites[MAX_SITES];

    for (room_id = 2; room_id <= MAX_ROOMS; room_id++) {
        int placed = 0;
        for (int attempt = 0; attempt < MAX_ATTEMPTS && !placed; attempt++) {
            /* Exclude cavern (too big to attach reliably). */
            room_type_t rt = (room_type_t)rng_range(rng, 0, ROOM_COUNT - 2);
            if (rt == ROOM_CAVERN) rt = ROOM_SMALL_RECT;
            event_emit(sink, EV_ROOM_PROPOSED, NULL, 0);

            grid_fill(scratch, T_NOTHING);
            if (!room_carve(scratch, rt, rng, DCOLS/2, DROWS/2, room_id)) continue;
            scratch_keep_largest(scratch);

            int n_scratch = find_scratch_sites(scratch, scratch_sites, MAX_SITES);
            int n_main = find_main_sites(g, main_sites, MAX_SITES);
            if (n_scratch == 0 || n_main == 0) continue;

            /* Pick main site, then find scratch site facing opposite. */
            door_site_t ms = main_sites[rng_range(rng, 0, n_main - 1)];
            int opp_idx[MAX_SITES];
            int n_opp = 0;
            for (int i = 0; i < n_scratch; i++) {
                if (scratch_sites[i].dx == -ms.dx && scratch_sites[i].dy == -ms.dy)
                    opp_idx[n_opp++] = i;
            }
            if (n_opp == 0) continue;
            door_site_t ss = scratch_sites[opp_idx[rng_range(rng, 0, n_opp - 1)]];

            int horiz = (ms.dy == 0);
            int L = horiz ? rng_range(rng, 2, 10) : rng_range(rng, 2, 6);

            int door_x = ms.x + ms.dx;
            int door_y = ms.y + ms.dy;

            corridor_path_t path;
            int want_bend = L >= 4 && rng_percent(rng, 30);
            if (want_bend) {
                int L1 = rng_range(rng, 2, L - 2);
                int L2 = L - L1;
                int sign = rng_percent(rng, 50) ? 1 : -1;
                int dx2 = -ms.dy * sign, dy2 = ms.dx * sign;
                build_lbent(&path, door_x, door_y,
                            L1, ms.dx, ms.dy, L2, dx2, dy2);
            } else {
                build_straight(&path, door_x, door_y, L, ms.dx, ms.dy);
            }

            /* Scratch room's entry floor must land at path.entry. Its door-
               facing scratch cell ss points OUT of the scratch room in its
               outward dir; we want scratch to be placed so ss lands at entry,
               and the scratch room extends AWAY from the corridor's final
               direction. For straight corridors that direction is ms.dir; for
               bent it's the leg2 direction. We recover it from path endpoints. */
            int final_dx = ms.dx, final_dy = ms.dy;
            if (want_bend) {
                int last = path.len - 1;
                final_dx = path.entry_x - path.x[last];
                final_dy = path.entry_y - path.y[last];
                /* Scratch site must face opposite to this final direction. */
                if (ss.dx != -final_dx || ss.dy != -final_dy) continue;
            }
            (void)final_dx; (void)final_dy;

            int tx = path.entry_x - ss.x;
            int ty = path.entry_y - ss.y;

            if (!fits_placement(scratch, g, tx, ty, &path)) continue;

            event_emit(sink, EV_DOOR_SITE_CHOSEN, NULL, 0);
            carve_path(g, &path, room_id);
            event_emit(sink, EV_CORRIDOR_CARVED, NULL, 0);
            blit_room(scratch, g, tx, ty, room_id);
            event_emit(sink, EV_ROOM_PLACED, NULL, 0);
            placed = 1;
        }
        if (!placed) event_emit(sink, EV_ROOM_REJECTED, NULL, 0);
    }
    return (int)(room_id - 1);
}
