package com.tiedup.remake.worldgen;

import com.tiedup.remake.blocks.ModBlocks;
import com.tiedup.remake.blocks.entity.TrappedChestBlockEntity;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.v2.V2Blocks;
import com.tiedup.remake.v2.blocks.PetCageBlock;
import com.tiedup.remake.v2.blocks.PetCagePartBlock;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.DoubleTag;
import net.minecraft.nbt.FloatTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext;
import net.minecraft.world.level.storage.loot.BuiltInLootTables;

/**
 * Structure piece for the hanging cage.
 *
 * Scans the cave geometry in postProcess() and places:
 * - Iron bars column from ceiling down to the top of the cage
 * - A Pet Cage (3x3x2 multi-block) suspended in the cave
 * - A Damsel entity inside the cage (25% shiny)
 */
public class HangingCagePiece extends StructurePiece {

    private final BlockPos candidatePos;
    private final Direction facing;
    private boolean placed = false;

    /** Minimum air gap between cave floor and bottom of cage. */
    private static final int MIN_GAP = 4;
    /** Height of the cage in blocks. */
    private static final int CAGE_HEIGHT = 2;
    /** Minimum cave height: gap + cage + at least 1 iron bar. */
    private static final int MIN_CAVE_HEIGHT = MIN_GAP + CAGE_HEIGHT + 1;
    /** Max scan distance up/down from candidate pos. */
    private static final int SCAN_RANGE = 64;

    /** X/Z offsets to try around chunk center. Cage is 3x3 so we space by 4 to avoid overlap. */
    private static final int[][] COLUMN_OFFSETS = {
        { 0, 0 },
        { 4, 0 },
        { -4, 0 },
        { 0, 4 },
        { 0, -4 },
        { 4, 4 },
        { -4, 4 },
        { 4, -4 },
        { -4, -4 },
        { 2, 2 },
        { -2, 2 },
        { 2, -2 },
        { -2, -2 },
    };

    public HangingCagePiece(BlockPos candidatePos, RandomSource random) {
        super(
            ModStructures.HANGING_CAGE_PIECE.get(),
            0,
            makeBoundingBox(candidatePos)
        );
        this.candidatePos = candidatePos;
        this.facing = Direction.Plane.HORIZONTAL.getRandomDirection(random);
    }

    public HangingCagePiece(CompoundTag tag) {
        super(ModStructures.HANGING_CAGE_PIECE.get(), tag);
        this.candidatePos = new BlockPos(
            tag.getInt("CandX"),
            tag.getInt("CandY"),
            tag.getInt("CandZ")
        );
        this.facing = Direction.from2DDataValue(tag.getInt("Facing"));
    }

    private static BoundingBox makeBoundingBox(BlockPos pos) {
        // Generous bounding box: ±7 covers 13-wide room (±6) + column offsets, vertical covers scan + room
        return new BoundingBox(
            pos.getX() - 7,
            pos.getY() - SCAN_RANGE,
            pos.getZ() - 7,
            pos.getX() + 7,
            pos.getY() + SCAN_RANGE,
            pos.getZ() + 7
        );
    }

    @Override
    protected void addAdditionalSaveData(
        StructurePieceSerializationContext context,
        CompoundTag tag
    ) {
        tag.putInt("CandX", candidatePos.getX());
        tag.putInt("CandY", candidatePos.getY());
        tag.putInt("CandZ", candidatePos.getZ());
        tag.putInt("Facing", facing.get2DDataValue());
    }

    @Override
    public void postProcess(
        WorldGenLevel level,
        net.minecraft.world.level.StructureManager structureManager,
        net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator,
        RandomSource random,
        BoundingBox chunkBB,
        ChunkPos chunkPos,
        BlockPos pivot
    ) {
        if (placed) return;

        // Try multiple columns around chunk center to maximize chance of finding a cave.
        // A single column has low odds of hitting a cave; 13 columns covers a wide area.
        for (int[] offset : COLUMN_OFFSETS) {
            int x = candidatePos.getX() + offset[0];
            int z = candidatePos.getZ() + offset[1];

            if (scanColumnAndPlace(level, random, x, z, chunkBB)) {
                placed = true;
                return; // Success
            }
        }

        // Fallback: carve a dungeon room and place the cage inside
        carveAndPlaceRoom(
            level,
            random,
            candidatePos.getX(),
            candidatePos.getZ(),
            chunkBB
        );
        placed = true;
    }

    /**
     * Scan a single column from Y=50 down to Y=-50, looking for caves tall enough.
     * @return true if a cage was placed
     */
    private boolean scanColumnAndPlace(
        WorldGenLevel level,
        RandomSource random,
        int x,
        int z,
        BoundingBox chunkBB
    ) {
        int scanTop = 50;
        int scanBottom = -50;

        int ceilingY = -1;
        boolean inAir = false;

        for (int y = scanTop; y >= scanBottom; y--) {
            BlockState state = level.getBlockState(new BlockPos(x, y, z));
            boolean solid = !state.isAir() && !state.liquid();

            if (!inAir && !solid) {
                // Transition solid -> air: ceiling is at y+1
                ceilingY = y + 1;
                inAir = true;
            } else if (inAir && solid) {
                // Transition air -> solid: floor is at y
                int floorY = y;
                inAir = false;

                int caveHeight = ceilingY - floorY - 1;
                if (caveHeight >= MIN_CAVE_HEIGHT) {
                    if (
                        tryPlaceCage(
                            level,
                            random,
                            x,
                            z,
                            floorY,
                            ceilingY,
                            chunkBB
                        )
                    ) {
                        return true;
                    }
                }

                ceilingY = -1;
            }
        }
        return false;
    }

    /**
     * Attempt to place the cage in a cave with the given floor and ceiling Y.
     * @return true if placement succeeded
     */
    private boolean tryPlaceCage(
        WorldGenLevel level,
        RandomSource random,
        int x,
        int z,
        int floorY,
        int ceilingY,
        BoundingBox chunkBB
    ) {
        int cageMasterY = floorY + 1 + MIN_GAP;
        int cageTopY = cageMasterY + CAGE_HEIGHT;
        int numBars = ceilingY - cageTopY;
        if (numBars < 1) return false;

        BlockPos masterPos = new BlockPos(x, cageMasterY, z);

        // Validate 3x3x2 footprint is all air
        if (!level.getBlockState(masterPos).isAir()) return false;

        BlockPos[] partPositions = PetCageBlock.getPartPositions(
            masterPos,
            facing
        );
        for (BlockPos partPos : partPositions) {
            if (!level.getBlockState(partPos).isAir()) return false;
        }

        // Place iron bars from ceiling down to top of cage
        for (int y = cageTopY; y < ceilingY; y++) {
            safeSetBlock(
                level,
                new BlockPos(x, y, z),
                Blocks.IRON_BARS.defaultBlockState(),
                chunkBB
            );
        }

        // Place Pet Cage master block
        BlockState masterState = V2Blocks.PET_CAGE.get()
            .defaultBlockState()
            .setValue(PetCageBlock.FACING, facing);
        safeSetBlock(level, masterPos, masterState, chunkBB);

        // Place Pet Cage part blocks
        BlockState partState = V2Blocks.PET_CAGE_PART.get()
            .defaultBlockState()
            .setValue(PetCagePartBlock.FACING, facing);
        for (BlockPos partPos : partPositions) {
            safeSetBlock(level, partPos, partState, chunkBB);
        }

        if (chunkBB.isInside(masterPos)) {
            scheduleDamselSpawn(level, masterPos, random);
        }

        TiedUpMod.LOGGER.info(
            "[HangingCage] Placed cage in cave at {}",
            masterPos.toShortString()
        );
        return true;
    }

    // RoomLayout and RoomTheme extracted to separate files in this package.

    // ── Fallback room generation ─────────────────────────────────────

    /**
     * Fallback: carve a 13x13x12 dungeon room and place the cage inside.
     * Randomly selects a theme (Oubliette/Inferno) and layout (Square/Octagonal).
     */
    private void carveAndPlaceRoom(
        WorldGenLevel level,
        RandomSource random,
        int centerX,
        int centerZ,
        BoundingBox chunkBB
    ) {
        RoomTheme theme = RoomTheme.values()[random.nextInt(
            RoomTheme.values().length
        )];
        RoomLayout layout = RoomLayout.values()[random.nextInt(
            RoomLayout.values().length
        )];

        int ROOM = 13;
        int HEIGHT = 12;
        int baseX = centerX - 6;
        int baseZ = centerZ - 6;
        int floorY = candidatePos.getY() - 5;

        for (int rx = 0; rx < ROOM; rx++) {
            for (int rz = 0; rz < ROOM; rz++) {
                if (!layout.isInShape(rx, rz)) continue;

                for (int ry = 0; ry < HEIGHT; ry++) {
                    BlockPos pos = new BlockPos(
                        baseX + rx,
                        floorY + ry,
                        baseZ + rz
                    );
                    boolean isWall = layout.isWall(rx, rz);
                    boolean isFloor = ry == 0;
                    boolean isCeiling = ry == 11;

                    if (isFloor) {
                        if (isWall) {
                            safeSetBlock(
                                level,
                                pos,
                                theme.wallShellBlock(),
                                chunkBB
                            );
                        } else {
                            boolean isEdge = layout.isWallAdjacent(rx, rz);
                            safeSetBlock(
                                level,
                                pos,
                                theme.floorBlock(random, rx, rz, isEdge),
                                chunkBB
                            );
                        }
                    } else if (isCeiling) {
                        safeSetBlock(
                            level,
                            pos,
                            theme.ceilingBlock(random),
                            chunkBB
                        );
                    } else if (isWall) {
                        safeSetBlock(
                            level,
                            pos,
                            theme.wallBlock(random, ry),
                            chunkBB
                        );
                    } else {
                        safeSetBlock(
                            level,
                            pos,
                            Blocks.AIR.defaultBlockState(),
                            chunkBB
                        );
                    }
                }
            }
        }

        theme.placeDecorations(
            level,
            random,
            baseX,
            baseZ,
            floorY,
            layout,
            chunkBB
        );

        RoomTheme.placeSharedPillars(
            level,
            random,
            baseX,
            baseZ,
            floorY,
            layout,
            theme,
            chunkBB
        );
        RoomTheme.placeSharedFloorScatter(
            level,
            random,
            baseX,
            baseZ,
            floorY,
            layout,
            theme,
            chunkBB
        );
        RoomTheme.placeSharedCeilingDecor(
            level,
            random,
            baseX,
            baseZ,
            floorY,
            layout,
            chunkBB
        );
        RoomTheme.placeSharedWallLighting(
            level,
            random,
            baseX,
            baseZ,
            floorY,
            layout,
            chunkBB
        );
        RoomTheme.placeSharedWallBands(
            level,
            baseX,
            baseZ,
            floorY,
            layout,
            theme,
            chunkBB
        );

        placeVanillaChest(level, random, baseX, baseZ, floorY, layout, chunkBB);
        placeTrappedChest(level, random, baseX, baseZ, floorY, layout, chunkBB);

        for (int ry = 7; ry <= 10; ry++) {
            safeSetBlock(
                level,
                new BlockPos(centerX, floorY + ry, centerZ),
                Blocks.IRON_BARS.defaultBlockState(),
                chunkBB
            );
        }

        BlockPos masterPos = new BlockPos(centerX, floorY + 5, centerZ);

        BlockState masterState = V2Blocks.PET_CAGE.get()
            .defaultBlockState()
            .setValue(PetCageBlock.FACING, facing);
        safeSetBlock(level, masterPos, masterState, chunkBB);

        BlockState partState = V2Blocks.PET_CAGE_PART.get()
            .defaultBlockState()
            .setValue(PetCagePartBlock.FACING, facing);
        for (BlockPos partPos : PetCageBlock.getPartPositions(
            masterPos,
            facing
        )) {
            safeSetBlock(level, partPos, partState, chunkBB);
        }

        if (chunkBB.isInside(masterPos)) {
            scheduleDamselSpawn(level, masterPos, random);
        }

        TiedUpMod.LOGGER.info(
            "[HangingCage] Placed cage in carved room (theme={}, layout={}) at {}",
            theme.name(),
            layout.name(),
            masterPos.toShortString()
        );
    }

    /**
     * Place a vanilla dungeon chest in the 3rd inner corner.
     * Uses BuiltInLootTables.SIMPLE_DUNGEON for standard dungeon loot.
     */
    private void placeVanillaChest(
        WorldGenLevel level,
        RandomSource random,
        int baseX,
        int baseZ,
        int floorY,
        RoomLayout layout,
        BoundingBox chunkBB
    ) {
        int[][] corners = layout.innerCorners();
        if (corners.length < 3) return;
        int[] c = corners[2];
        if (!layout.isInShape(c[0], c[1]) || layout.isWall(c[0], c[1])) return;

        BlockPos chestPos = new BlockPos(
            baseX + c[0],
            floorY + 1,
            baseZ + c[1]
        );
        if (!chunkBB.isInside(chestPos)) return;

        // Face chest toward room center
        Direction chestFacing;
        if (c[0] < 6) chestFacing = Direction.EAST;
        else if (c[0] > 6) chestFacing = Direction.WEST;
        else if (c[1] < 6) chestFacing = Direction.SOUTH;
        else chestFacing = Direction.NORTH;

        BlockState chestState = Blocks.CHEST.defaultBlockState().setValue(
            ChestBlock.FACING,
            chestFacing
        );
        level.setBlock(chestPos, chestState, 2);

        BlockEntity be = level.getBlockEntity(chestPos);
        if (be instanceof RandomizableContainerBlockEntity container) {
            container.setLootTable(
                BuiltInLootTables.SIMPLE_DUNGEON,
                random.nextLong()
            );
        }
    }

    /**
     * Place a TiedUp trapped chest in the 4th inner corner.
     * Filled with random bondage items (bind, gag, blindfold).
     */
    private void placeTrappedChest(
        WorldGenLevel level,
        RandomSource random,
        int baseX,
        int baseZ,
        int floorY,
        RoomLayout layout,
        BoundingBox chunkBB
    ) {
        int[][] corners = layout.innerCorners();
        if (corners.length < 4) return;
        int[] c = corners[3];
        if (!layout.isInShape(c[0], c[1]) || layout.isWall(c[0], c[1])) return;

        BlockPos chestPos = new BlockPos(
            baseX + c[0],
            floorY + 1,
            baseZ + c[1]
        );
        if (!chunkBB.isInside(chestPos)) return;

        // Face chest toward room center
        Direction chestFacing;
        if (c[0] < 6) chestFacing = Direction.EAST;
        else if (c[0] > 6) chestFacing = Direction.WEST;
        else if (c[1] < 6) chestFacing = Direction.SOUTH;
        else chestFacing = Direction.NORTH;

        BlockState chestState = ModBlocks.TRAPPED_CHEST.get()
            .defaultBlockState()
            .setValue(ChestBlock.FACING, chestFacing);
        level.setBlock(chestPos, chestState, 2);

        BlockEntity be = level.getBlockEntity(chestPos);
        if (be instanceof TrappedChestBlockEntity trappedChest) {
            // Random bind
            BindVariant[] bindVariants = BindVariant.values();
            BindVariant chosenBind = bindVariants[random.nextInt(
                bindVariants.length
            )];
            ItemStack bindStack = new ItemStack(ModItems.getBind(chosenBind));
            trappedChest.setBind(bindStack);

            // Random gag (50% chance)
            if (random.nextFloat() < 0.50f) {
                GagVariant[] gagVariants = GagVariant.values();
                GagVariant chosenGag = gagVariants[random.nextInt(
                    gagVariants.length
                )];
                ItemStack gagStack = new ItemStack(ModItems.getGag(chosenGag));
                trappedChest.setGag(gagStack);
            }

            // Random blindfold (30% chance)
            if (random.nextFloat() < 0.30f) {
                BlindfoldVariant[] bfVariants = BlindfoldVariant.values();
                BlindfoldVariant chosenBf = bfVariants[random.nextInt(
                    bfVariants.length
                )];
                ItemStack bfStack = new ItemStack(
                    ModItems.getBlindfold(chosenBf)
                );
                trappedChest.setBlindfold(bfStack);
            }
        }
    }

    static void safeSetBlock(
        WorldGenLevel level,
        BlockPos pos,
        BlockState state,
        BoundingBox chunkBB
    ) {
        if (chunkBB.isInside(pos)) {
            level.setBlock(pos, state, 2);
        }
    }

    /**
     * Write damsel entity NBT directly into the ProtoChunk, avoiding entity
     * construction on the worker thread (which deadlocks due to PlayerAnimator).
     * The entity will be created naturally when the chunk is promoted to a LevelChunk.
     */
    private void scheduleDamselSpawn(
        WorldGenLevel level,
        BlockPos masterPos,
        RandomSource random
    ) {
        boolean shiny = random.nextFloat() < 0.25f;
        String entityId = shiny
            ? TiedUpMod.MOD_ID + ":damsel_shiny"
            : TiedUpMod.MOD_ID + ":damsel";

        CompoundTag entityTag = new CompoundTag();
        entityTag.putString("id", entityId);

        // Position: +1Y to be inside cage (above the thin 2px cage floor)
        ListTag posList = new ListTag();
        posList.add(DoubleTag.valueOf(masterPos.getX() + 0.5));
        posList.add(DoubleTag.valueOf(masterPos.getY() + 1.0));
        posList.add(DoubleTag.valueOf(masterPos.getZ() + 0.5));
        entityTag.put("Pos", posList);

        // Motion (stationary)
        ListTag motionList = new ListTag();
        motionList.add(DoubleTag.valueOf(0.0));
        motionList.add(DoubleTag.valueOf(0.0));
        motionList.add(DoubleTag.valueOf(0.0));
        entityTag.put("Motion", motionList);

        // Rotation (random yaw)
        ListTag rotList = new ListTag();
        rotList.add(FloatTag.valueOf(random.nextFloat() * 360F));
        rotList.add(FloatTag.valueOf(0.0F));
        entityTag.put("Rotation", rotList);

        // Persistence + prevent fall death
        entityTag.putBoolean("PersistenceRequired", true);
        entityTag.putBoolean("OnGround", true);
        entityTag.putFloat("FallDistance", 0.0F);
        entityTag.putFloat("AbsorptionAmount", 20.0F);
        entityTag.putUUID("UUID", java.util.UUID.randomUUID());

        // Random bind item — the damsel spawns already restrained
        BindVariant[] variants = BindVariant.values();
        BindVariant chosenBind = variants[random.nextInt(variants.length)];
        ItemStack bindStack = new ItemStack(ModItems.getBind(chosenBind));
        bindStack.getOrCreateTag().putString("bindMode", "full");
        entityTag.put("Bind", bindStack.save(new CompoundTag()));

        // Add directly to chunk's pending entity list
        ChunkAccess chunk = level.getChunk(masterPos);
        if (chunk instanceof ProtoChunk protoChunk) {
            protoChunk.addEntity(entityTag);
            TiedUpMod.LOGGER.info(
                "[HangingCage] Scheduled {} damsel with {} at {}",
                shiny ? "shiny" : "regular",
                chosenBind.getRegistryName(),
                masterPos.toShortString()
            );
        }
    }
}
