#include "machines.h"
#include "blueprints.h"
#include <stddef.h>
#include <string.h>

static int is_pass(uint8_t t) {
    return terrain_is_passable(t);
}

/* Flood 4-connected from (sx,sy) through passable cells, but pretend
   (block_x,block_y) is blocked. Fills pocket[][] with 1 for reached cells,
   zero otherwise. Returns count. */
static int flood_pocket(grid_t g, int sx, int sy, int block_x, int block_y,
                        uint8_t pocket[DROWS][DCOLS]) {
    memset(pocket, 0, (size_t)DROWS * DCOLS);
    if (!grid_in_bounds(sx, sy)) return 0;
    if (!is_pass(g[sy][sx].terrain)) return 0;
    static int qx[DCOLS * DROWS];
    static int qy[DCOLS * DROWS];
    int head = 0, tail = 0;
    pocket[sy][sx] = 1;
    qx[tail] = sx; qy[tail] = sy; tail++;
    int count = 1;
    static const int d4[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
    while (head < tail) {
        int cx = qx[head], cy = qy[head]; head++;
        for (int i = 0; i < 4; i++) {
            int nx = cx + d4[i][0];
            int ny = cy + d4[i][1];
            if (!grid_in_bounds(nx, ny)) continue;
            if (pocket[ny][nx]) continue;
            if (nx == block_x && ny == block_y) continue;
            if (!is_pass(g[ny][nx].terrain)) continue;
            pocket[ny][nx] = 1;
            qx[tail] = nx; qy[tail] = ny; tail++;
            count++;
        }
    }
    return count;
}

/* Pick a random blueprint that fits the given depth and pocket size,
   weighted by frequency. Returns index into blueprint_catalog, or 0 if no
   blueprint matches. */
static int pick_blueprint(rng_t *rng, int depth, int pocket_size) {
    int total_weight = 0;
    for (int i = 1; i < NUM_BLUEPRINTS; i++) {
        const blueprint_t *bp = &blueprint_catalog[i];
        if (depth < bp->depth_min || depth > bp->depth_max) continue;
        if (pocket_size < bp->room_size_min || pocket_size > bp->room_size_max) continue;
        total_weight += bp->frequency;
    }
    if (total_weight <= 0) return 0;
    int roll = rng_range(rng, 0, total_weight - 1);
    for (int i = 1; i < NUM_BLUEPRINTS; i++) {
        const blueprint_t *bp = &blueprint_catalog[i];
        if (depth < bp->depth_min || depth > bp->depth_max) continue;
        if (pocket_size < bp->room_size_min || pocket_size > bp->room_size_max) continue;
        roll -= bp->frequency;
        if (roll < 0) return i;
    }
    return 0;
}

/* Collect interior cells into a flat list. Returns count. */
static int list_interior(uint8_t pocket[DROWS][DCOLS], int cells_x[], int cells_y[]) {
    int n = 0;
    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++) {
            if (pocket[y][x]) {
                cells_x[n] = x;
                cells_y[n] = y;
                n++;
            }
        }
    }
    return n;
}

/* Apply one feature. Returns the number of cells placed (counted against
   min_count for acceptance). */
static int apply_feature(grid_t g, rng_t *rng, const feature_t *f,
                         int origin_x, int origin_y,
                         int cells_x[], int cells_y[], int cell_count,
                         uint8_t blocked[DROWS][DCOLS]) {
    int placed = 0;

    if (f->flags & MF_EVERYWHERE) {
        for (int i = 0; i < cell_count; i++) {
            int x = cells_x[i], y = cells_y[i];
            if (f->terrain) g[y][x].terrain = (uint8_t)f->terrain;
            if (f->marker)  g[y][x].surface = (uint8_t)f->marker;
            placed++;
        }
        return placed;
    }

    if (f->flags & MF_BUILD_AT_ORIGIN) {
        if (grid_in_bounds(origin_x, origin_y)) {
            if (f->terrain) g[origin_y][origin_x].terrain = (uint8_t)f->terrain;
            if (f->marker)  g[origin_y][origin_x].surface = (uint8_t)f->marker;
            g[origin_y][origin_x].flags |= F_MACHINE_GATE;
            placed++;
        }
        return placed;
    }

    /* Random interior placements, avoiding cells already marked blocking. */
    int want = rng_range(rng, f->min_count, f->max_count);
    int attempts = 0;
    while (placed < want && attempts < cell_count * 4) {
        attempts++;
        int idx = rng_range(rng, 0, cell_count - 1);
        int x = cells_x[idx], y = cells_y[idx];
        if (blocked[y][x]) continue;
        if (f->terrain) g[y][x].terrain = (uint8_t)f->terrain;
        if (f->marker)  g[y][x].surface = (uint8_t)f->marker;
        if (f->flags & MF_TREAT_AS_BLOCKING) blocked[y][x] = 1;
        if (f->flags & MF_GENERATE_KEY)       g[y][x].flags |= F_MACHINE_KEY;
        placed++;
    }
    return placed;
}

int place_machines(grid_t g, rng_t *rng, int depth, event_sink_t *sink,
                   choke_map_t choke, choke_flag_t is_gate) {
    static uint8_t pocket[DROWS][DCOLS];
    static uint8_t blocked[DROWS][DCOLS];
    static int cells_x[DCOLS * DROWS];
    static int cells_y[DCOLS * DROWS];

    uint8_t next_machine_id = 1;

    /* Scan gate sites in ascending pocket size. */
    for (int target_size = 4; target_size <= DCOLS * DROWS; target_size++) {
        for (int gy = 0; gy < DROWS; gy++) {
            for (int gx = 0; gx < DCOLS; gx++) {
                if (!is_gate[gy][gx]) continue;
                if (choke[gy][gx] != target_size) continue;

                /* Find the passable neighbor inside the pocket. */
                static const int d4[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
                int px = -1, py = -1;
                for (int d = 0; d < 4; d++) {
                    int nx = gx + d4[d][0];
                    int ny = gy + d4[d][1];
                    if (!grid_in_bounds(nx, ny)) continue;
                    if (!is_pass(g[ny][nx].terrain)) continue;
                    /* The pocket side is the one where blocking the gate
                       would strand N cells. Try each neighbor and flood; the
                       smaller flood is the pocket. */
                    int n = flood_pocket(g, nx, ny, gx, gy, pocket);
                    if (n > 0 && n <= target_size + 2) {
                        px = nx; py = ny;
                        break;
                    }
                }
                if (px < 0) continue;

                int pocket_count = flood_pocket(g, px, py, gx, gy, pocket);
                if (pocket_count < 4) continue;

                int bp_idx = pick_blueprint(rng, depth, pocket_count);
                if (bp_idx == 0) continue;
                const blueprint_t *bp = &blueprint_catalog[bp_idx];

                event_emit(sink, EV_MACHINE_PROPOSED, NULL, 0);

                int cell_count = list_interior(pocket, cells_x, cells_y);
                memset(blocked, 0, sizeof(blocked));

                /* Apply features; track acceptance. */
                int all_required_met = 1;
                for (int i = 0; i < bp->feature_count; i++) {
                    const feature_t *f = &bp->features[i];
                    int placed = apply_feature(g, rng, f, gx, gy,
                                               cells_x, cells_y, cell_count,
                                               blocked);
                    if (placed < f->min_count) {
                        all_required_met = 0;
                        break;
                    }
                    event_emit(sink, EV_FEATURE_BUILT, NULL, 0);
                }

                if (!all_required_met) continue;

                /* Commit: tag every interior cell with F_IN_MACHINE + id. */
                for (int i = 0; i < cell_count; i++) {
                    int x = cells_x[i], y = cells_y[i];
                    g[y][x].flags |= F_IN_MACHINE | F_MACHINE_INTERIOR;
                    g[y][x].machine_id = next_machine_id;
                }
                /* The gate itself is part of the machine boundary. */
                g[gy][gx].flags |= F_IN_MACHINE | F_MACHINE_GATE;
                g[gy][gx].machine_id = next_machine_id;

                event_emit(sink, EV_MACHINE_ACCEPTED, NULL, 0);
                next_machine_id++;

                return 1; /* A.6c: one machine per seed */
            }
        }
    }
    return 0;
}
