package com.tiedup.remake.cells;

import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;

/**
 * BFS flood-fill algorithm for detecting enclosed rooms around a Cell Core.
 *
 * Scans outward from air neighbors of the Core block, treating solid blocks
 * (including the Core itself) as walls. Picks the smallest successful fill
 * as the cell interior (most likely the room, not the hallway).
 */
public final class FloodFillAlgorithm {

    static final int MAX_VOLUME = 1200;
    static final int MIN_VOLUME = 2;
    static final int MAX_X = 12;
    static final int MAX_Y = 8;
    static final int MAX_Z = 12;

    private FloodFillAlgorithm() {}

    /**
     * Try flood-fill from each air neighbor of the Core position.
     * Pick the smallest successful fill (= most likely the cell, not the hallway).
     * If none succeed, return a failure result.
     */
    public static FloodFillResult tryFill(Level level, BlockPos corePos) {
        Set<BlockPos> bestInterior = null;
        Direction bestDirection = null;

        for (Direction dir : Direction.values()) {
            BlockPos neighbor = corePos.relative(dir);
            BlockState neighborState = level.getBlockState(neighbor);

            if (!isPassable(neighborState)) {
                continue;
            }

            Set<BlockPos> interior = bfs(level, neighbor, corePos);
            if (interior == null) {
                // Overflow or out of bounds — this direction opens to the outside
                continue;
            }

            if (interior.size() < MIN_VOLUME) {
                continue;
            }

            if (bestInterior == null || interior.size() < bestInterior.size()) {
                bestInterior = interior;
                bestDirection = dir;
            }
        }

        if (bestInterior == null) {
            // No direction produced a valid fill — check why
            // Try again to determine the most helpful error message
            boolean anyAir = false;
            boolean tooLarge = false;
            boolean tooSmall = false;
            boolean outOfBounds = false;

            for (Direction dir : Direction.values()) {
                BlockPos neighbor = corePos.relative(dir);
                BlockState neighborState = level.getBlockState(neighbor);
                if (!isPassable(neighborState)) continue;
                anyAir = true;

                Set<BlockPos> result = bfsDiagnostic(level, neighbor, corePos);
                if (result == null) {
                    // Overflowed — could be not enclosed or too large
                    tooLarge = true;
                } else if (result.size() < MIN_VOLUME) {
                    tooSmall = true;
                } else {
                    outOfBounds = true;
                }
            }

            if (!anyAir) {
                return FloodFillResult.failure(
                    "msg.tiedup.cell_core.not_enclosed"
                );
            } else if (tooLarge) {
                // Could be open to outside or genuinely too large
                return FloodFillResult.failure(
                    "msg.tiedup.cell_core.not_enclosed"
                );
            } else if (outOfBounds) {
                return FloodFillResult.failure(
                    "msg.tiedup.cell_core.out_of_bounds"
                );
            } else if (tooSmall) {
                return FloodFillResult.failure(
                    "msg.tiedup.cell_core.too_small"
                );
            } else {
                return FloodFillResult.failure(
                    "msg.tiedup.cell_core.not_enclosed"
                );
            }
        }

        // Build walls set
        Set<BlockPos> walls = findWalls(level, bestInterior, corePos);

        // Detect features
        List<BlockPos> beds = new ArrayList<>();
        List<BlockPos> petBeds = new ArrayList<>();
        List<BlockPos> anchors = new ArrayList<>();
        List<BlockPos> doors = new ArrayList<>();
        List<BlockPos> linkedRedstone = new ArrayList<>();
        detectFeatures(
            level,
            bestInterior,
            walls,
            beds,
            petBeds,
            anchors,
            doors,
            linkedRedstone
        );

        return FloodFillResult.success(
            bestInterior,
            walls,
            bestDirection,
            beds,
            petBeds,
            anchors,
            doors,
            linkedRedstone
        );
    }

    /**
     * BFS from start position, treating corePos and solid blocks as walls.
     *
     * @return The set of interior (passable) positions, or null if the fill
     *         overflowed MAX_VOLUME or exceeded MAX bounds.
     */
    private static Set<BlockPos> bfs(
        Level level,
        BlockPos start,
        BlockPos corePos
    ) {
        Set<BlockPos> visited = new HashSet<>();
        Queue<BlockPos> queue = new ArrayDeque<>();

        visited.add(start);
        queue.add(start);

        int minX = start.getX(),
            maxX = start.getX();
        int minY = start.getY(),
            maxY = start.getY();
        int minZ = start.getZ(),
            maxZ = start.getZ();

        while (!queue.isEmpty()) {
            BlockPos current = queue.poll();

            for (Direction dir : Direction.values()) {
                BlockPos next = current.relative(dir);

                if (next.equals(corePos)) {
                    // Core is always treated as wall
                    continue;
                }

                if (visited.contains(next)) {
                    continue;
                }

                // Treat unloaded chunks as walls to avoid synchronous chunk loading
                if (!level.isLoaded(next)) {
                    continue;
                }

                BlockState state = level.getBlockState(next);
                if (!isPassable(state)) {
                    // Solid block = wall, don't expand
                    continue;
                }

                visited.add(next);

                // Check volume
                if (visited.size() > MAX_VOLUME) {
                    return null; // Too large or not enclosed
                }

                // Update bounds
                minX = Math.min(minX, next.getX());
                maxX = Math.max(maxX, next.getX());
                minY = Math.min(minY, next.getY());
                maxY = Math.max(maxY, next.getY());
                minZ = Math.min(minZ, next.getZ());
                maxZ = Math.max(maxZ, next.getZ());

                // Check dimensional bounds
                if (
                    (maxX - minX + 1) > MAX_X ||
                    (maxY - minY + 1) > MAX_Y ||
                    (maxZ - minZ + 1) > MAX_Z
                ) {
                    return null; // Exceeds max dimensions
                }

                queue.add(next);
            }
        }

        return visited;
    }

    /**
     * Diagnostic BFS: same as bfs() but returns the set even on bounds overflow
     * (returns null only on volume overflow). Used to determine error messages.
     */
    private static Set<BlockPos> bfsDiagnostic(
        Level level,
        BlockPos start,
        BlockPos corePos
    ) {
        Set<BlockPos> visited = new HashSet<>();
        Queue<BlockPos> queue = new ArrayDeque<>();

        visited.add(start);
        queue.add(start);

        while (!queue.isEmpty()) {
            BlockPos current = queue.poll();

            for (Direction dir : Direction.values()) {
                BlockPos next = current.relative(dir);

                if (next.equals(corePos) || visited.contains(next)) {
                    continue;
                }

                // Treat unloaded chunks as walls to avoid synchronous chunk loading
                if (!level.isLoaded(next)) {
                    continue;
                }

                BlockState state = level.getBlockState(next);
                if (!isPassable(state)) {
                    continue;
                }

                visited.add(next);

                if (visited.size() > MAX_VOLUME) {
                    return null;
                }

                queue.add(next);
            }
        }

        return visited;
    }

    /**
     * Find all solid blocks adjacent to the interior set (the walls of the cell).
     * The Core block itself is always included as a wall.
     */
    private static Set<BlockPos> findWalls(
        Level level,
        Set<BlockPos> interior,
        BlockPos corePos
    ) {
        Set<BlockPos> walls = new HashSet<>();
        walls.add(corePos);

        for (BlockPos pos : interior) {
            for (Direction dir : Direction.values()) {
                BlockPos neighbor = pos.relative(dir);
                if (!interior.contains(neighbor) && !neighbor.equals(corePos)) {
                    // This is a solid boundary block
                    walls.add(neighbor);
                }
            }
        }

        return walls;
    }

    /**
     * Scan interior and wall blocks to detect notable features.
     */
    private static void detectFeatures(
        Level level,
        Set<BlockPos> interior,
        Set<BlockPos> walls,
        List<BlockPos> beds,
        List<BlockPos> petBeds,
        List<BlockPos> anchors,
        List<BlockPos> doors,
        List<BlockPos> linkedRedstone
    ) {
        // Scan interior for beds and pet beds
        for (BlockPos pos : interior) {
            BlockState state = level.getBlockState(pos);
            Block block = state.getBlock();

            if (block instanceof BedBlock) {
                // Only count the HEAD part to avoid double-counting (beds are 2 blocks)
                if (
                    state.getValue(BedBlock.PART) ==
                    net.minecraft.world.level.block.state.properties.BedPart.HEAD
                ) {
                    beds.add(pos.immutable());
                }
            }

            // Check for mod's pet bed block
            if (block instanceof com.tiedup.remake.v2.blocks.PetBedBlock) {
                petBeds.add(pos.immutable());
            }
        }

        // Scan walls for doors, redstone components, and anchors
        for (BlockPos pos : walls) {
            BlockState state = level.getBlockState(pos);
            Block block = state.getBlock();

            // Doors, trapdoors, fence gates
            if (block instanceof DoorBlock) {
                // Only count the lower half to avoid double-counting
                if (
                    state.getValue(DoorBlock.HALF) ==
                    net.minecraft.world.level.block.state.properties.DoubleBlockHalf.LOWER
                ) {
                    doors.add(pos.immutable());
                }
            } else if (
                block instanceof TrapDoorBlock ||
                block instanceof FenceGateBlock
            ) {
                doors.add(pos.immutable());
            }

            // Chain blocks as anchors
            if (block instanceof ChainBlock) {
                anchors.add(pos.immutable());
            }

            // Buttons and levers as linked redstone
            if (block instanceof ButtonBlock || block instanceof LeverBlock) {
                linkedRedstone.add(pos.immutable());
            }
        }

        // Also check for buttons/levers on the interior side adjacent to walls
        for (BlockPos pos : interior) {
            BlockState state = level.getBlockState(pos);
            Block block = state.getBlock();

            if (block instanceof ButtonBlock || block instanceof LeverBlock) {
                linkedRedstone.add(pos.immutable());
            }
        }
    }

    /**
     * Determine if a block state is passable for flood-fill purposes.
     *
     * Air and non-solid blocks (torches, carpets, flowers, signs, etc.) are passable.
     * Closed doors block the fill (treated as walls). Open doors let fill through.
     * Glass, bars, fences are solid → treated as wall.
     */
    private static boolean isPassable(BlockState state) {
        if (state.isAir()) {
            return true;
        }

        Block block = state.getBlock();

        // Doors are always treated as walls for flood-fill (detected as features separately).
        // This prevents the fill from leaking through open doors.
        if (
            block instanceof DoorBlock ||
            block instanceof TrapDoorBlock ||
            block instanceof FenceGateBlock
        ) {
            return false;
        }

        // Beds are interior furniture, not walls.
        // BedBlock.isSolid() returns true in 1.20.1 which would misclassify them as walls,
        // preventing detectFeatures() from finding them (it only scans interior for beds).
        if (block instanceof BedBlock) {
            return true;
        }

        // Non-solid decorative blocks are passable
        // This covers torches, carpets, flowers, signs, pressure plates, etc.
        return !state.isSolid();
    }
}
