/*
 * Copyright (c) 2026 盐灯水母
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

package com.remains_cave.dungeon;

import com.mojang.datafixers.util.Either;
import com.remains_cave.block.*;
import com.remains_cave.dungeon.config.DungeonRoutineConfig;
import com.remains_cave.dungeon.config.RoutineStep;
import com.remains_cave.dungeon.room.RoomManager;
import com.remains_cave.dungeon.room.RoomState;
import com.remains_cave.dungeon.tomb.ChallengeType;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureEntityInfo;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;

import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;


public class DungeonGenerator {
    private static final int MAX_STRUCTURES = 100;

    private final ServerLevel level;
    private final RandomSource random;
    private final StructureTemplateManager templateManager;
    private final DungeonRoutineConfig config;
    private final BlockPos startPos;

    private record PlacedEntry(String name, BlockPos pos, Direction facing) {}
    private final List<PlacedEntry> placedEntries = new ArrayList<>();

    private record ProcessedExit(BlockPos pos, Direction facing, String stepId) {}
    private final Set<ProcessedExit> processedExits = new HashSet<>();

    private int structureCount = 0;

    private final Queue<Runnable> taskQueue = new ArrayDeque<>();
    private boolean scheduling = false;

    private Map<String, RoutineStep> stepMap;

    private static final Map<ResourceLocation, PlacementData> RAW_DATA_CACHE = new ConcurrentHashMap<>();

    private static final Field PALETTES_FIELD;
    private static final Field ENTITY_LIST_FIELD;
    private static final Field TEMPLATE_FIELD;

    private record TransformedData(PlacementData data, AABB worldBounds) {}

    private final String routineConfigName;
    private final BlockPos origin;
    public DungeonGenerator(ServerLevel level, BlockPos startPos, DungeonRoutineConfig config, String routineConfigName, long seed) {
        this.level = level;
        this.startPos = startPos;
        this.config = config;
        this.random = RandomSource.create(seed);
        this.templateManager = level.getStructureManager();
        this.routineConfigName = routineConfigName;
        this.origin = startPos;
    }

    static {
        try {
            PALETTES_FIELD = StructureTemplate.class.getDeclaredField("palettes");
            PALETTES_FIELD.setAccessible(true);
            ENTITY_LIST_FIELD = StructureTemplate.class.getDeclaredField("entityInfoList");
            ENTITY_LIST_FIELD.setAccessible(true);
            TEMPLATE_FIELD = SinglePoolElement.class.getDeclaredField("template");
            TEMPLATE_FIELD.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException("Failed to access required fields for reflection", e);
        }
    }



    public void generate() {
        processedExits.clear();
        structureCount = 0;
        taskQueue.clear();
        scheduling = false;

        config.validate();

        buildStepMap();

        SinglePoolElement startElement;
        if (config.getDungen_start_pool() != null) {
            startElement = pickRandomTemplate(ResourceLocation.tryParse(config.getDungen_start_pool()));
        } else if (config.getDungen_start_structure() != null) {
            startElement = createElementFromStructure(ResourceLocation.tryParse(config.getDungen_start_structure()));
        } else {
            startElement = null;
        }

        if (startElement == null) {
            return;
        }

        // 起始房间的 ID
        String startRoomId = "start_" + routineConfigName + "_" + origin.getX() + "_" + origin.getY() + "_" + origin.getZ();

        // 放置起始结构
        taskQueue.offer(() -> {
            placeElementAsync(startElement, startPos, Rotation.NONE, "start", startRoomId, false);
        });

        // 处理起始结构的出口门
        taskQueue.offer(() -> {
            processStartJigsaws(startElement);
        });

        scheduleNext();
    }
    private String generateRoomId() {
        return routineConfigName + "_" + origin.getX() + "_" + origin.getY() + "_" + origin.getZ();
    }
    private void buildStepMap() {
        stepMap = new HashMap<>();
        if (config.getRoutine() != null) {
            for (RoutineStep step : config.getRoutine()) {
                collectSteps(step);
            }
        }
    }

    private void collectSteps(RoutineStep step) {
        stepMap.put(step.getId(), step);
        if (step.getRoutine() != null) {
            for (RoutineStep child : step.getRoutine()) {
                collectSteps(child);
            }
        }
    }

    private void scheduleNext() {
        if (scheduling) return;
        if (taskQueue.isEmpty()) return;
        scheduling = true;
        level.getServer().execute(() -> {
            Runnable task = taskQueue.poll();
            if (task != null) {
                try {
                    task.run();
                } catch (Exception ignored) {
                }
            }
            scheduling = false;
            scheduleNext();
        });
    }

    private void processStartJigsaws(SinglePoolElement startElement) {
        List<StructureBlockInfo> startJigsaws = getShuffledJigsaws(startElement);
        for (RoutineStep step : config.getRoutine()) {
            for (StructureBlockInfo info : startJigsaws) {
                CompoundTag nbt = info.nbt();
                if (nbt == null) continue;
                String name = nbt.getString("name");
                if (step.getDoor().equals(name)) {
                    Direction facing = getFacingFromInfo(info);
                    BlockPos worldPos = startPos.offset(info.pos());
                    Direction worldFacing = applyRotationAndMirror(facing, Rotation.NONE);
                    taskQueue.offer(() -> generateStep(step, worldPos, worldFacing, null, null));
                    break;
                }
            }
        }
    }

    @Nullable
    private ResourceLocation getTemplateLocation(SinglePoolElement element) {
        try {
            Object fieldValue = TEMPLATE_FIELD.get(element);
            if (fieldValue instanceof Either) {
                Either<ResourceLocation, StructureTemplate> either = (Either<ResourceLocation, StructureTemplate>) fieldValue;
                return either.left().orElse(null);
            }
        } catch (IllegalAccessException ignored) {
        }
        return null;
    }

    private void generateStep(RoutineStep step, BlockPos sourceDoorPos, Direction sourceFacing,
                              String parentStepId, String parentStaircaseId) {
        ProcessedExit key = new ProcessedExit(sourceDoorPos, sourceFacing, step.getId());
        if (!processedExits.add(key)) {
            return;
        }

        SinglePoolElement element = null;
        if (step.getTo_pool() != null) {
            element = pickRandomTemplate(ResourceLocation.tryParse(step.getTo_pool()));
        } else if (step.getTo_structure() != null) {
            element = createElementFromStructure(ResourceLocation.tryParse(step.getTo_structure()));
        }
        if (element == null) {
            return;
        }

        JigsawInfo entryInfo = findJigsawInElement(element, step.getLink_door());
        if (entryInfo == null) {
            return;
        }

        Direction requiredEntryFacing = sourceFacing.getOpposite();
        BlockPos targetEntryPos = sourceDoorPos.relative(sourceFacing);

        PlacementResult placement = tryPlacement(element, entryInfo, targetEntryPos, requiredEntryFacing);
        if (placement == null) {
            return;
        }

        String roomId = UUID.randomUUID().toString();
        boolean isBossRoom = step.isBoss();

        RoomManager rm = RoomManager.get(level);
        RoomState roomState = rm.getOrCreateRoom(roomId);
        roomState.setStepId(step.getId());

        // 所有房间默认继承传入的 parentStaircaseId
        roomState.setParentStaircaseId(parentStaircaseId != null ? parentStaircaseId : "");

        // 如果是 staircase 或 staircase_alt_1，则覆盖为自身 roomId
        if (step.getId().equals("staircase") || step.getId().equals("staircase_alt_1")) {
            roomState.setParentStaircaseId(roomId);
        }  // event 房间的 parentStaircaseId 就是传入的值（已继承）


        placeElementAsync(element, placement.pos(), placement.rotation(), step.getId(), roomId, isBossRoom);
    }

    private void placeElementAsync(SinglePoolElement element, BlockPos worldPos, Rotation rotation,
                                   @Nullable String stepId, @Nullable String roomId, boolean isBossRoom) {
        structureCount++;
        if (structureCount > MAX_STRUCTURES) return;

        ResourceLocation templateLocation = getTemplateLocation(element);
        if (templateLocation == null) return;

        StructureTemplate template = templateManager.getOrCreate(templateLocation);

        PlacementData rawData;
        try {
            rawData = extractPlacementData(template, element, templateLocation);
        } catch (RuntimeException e) {
            return;
        }

        CompletableFuture.supplyAsync(() -> transformData(rawData, worldPos, rotation, roomId, isBossRoom))
                .thenAcceptAsync(transformed -> {
                    if (roomId != null) {
                        RoomManager roomManager = RoomManager.get(level);
                        RoomState roomState = roomManager.getOrCreateRoom(roomId);
                        roomState.expandProtectionArea(transformed.worldBounds());

                        // 同步 Emblem 初始状态
                        for (BlockPlacement placement : transformed.data().blocks()) {
                            if (placement.state().getBlock() instanceof EmblemBlock) {
                                boolean active = placement.state().getValue(EmblemBlock.ACTIVE);
                                if (active != roomState.isEmblemActive()) {
                                    roomState.setEmblemActive(active);
                                    roomManager.setDirty();
                                }
                                break; // 每个房间只有一个 Emblem
                            }
                        }

                        if (isBossRoom) {
                            roomManager.markBossRoom(roomId);
                        }
                        if ("tomb_room".equals(stepId)) {
                            roomManager.markTombRoom(roomId);
                        }
                        roomManager.setDirty();
                    }
                    BatchPlacer placer = new BatchPlacer(level, transformed.data());
                    scheduleBatchPlacement(placer, () -> processPlacedJigsaws(transformed.data(), stepId));
                }, level.getServer())
                .exceptionally(ex -> null);
    }

    private PlacementData extractPlacementData(StructureTemplate template, SinglePoolElement element, ResourceLocation templateLocation) {
        return RAW_DATA_CACHE.computeIfAbsent(templateLocation, key -> {
            try {
                List<?> palettes = (List<?>) PALETTES_FIELD.get(template);
                if (palettes.isEmpty()) {
                    throw new IllegalStateException("No palettes in template: " + key);
                }
                Object firstPalette = palettes.getFirst();
                List<StructureBlockInfo> blockInfoList = (List<StructureBlockInfo>) firstPalette.getClass().getMethod("blocks").invoke(firstPalette);
                List<BlockPlacement> blocks = new ArrayList<>(blockInfoList.size());
                for (StructureBlockInfo info : blockInfoList) {
                    if (info.state().getBlock() instanceof JigsawBlock) {
                        continue;
                    }
                    blocks.add(new BlockPlacement(info.pos(), info.state(), info.nbt() != null ? info.nbt().copy() : null));
                }

                List<StructureEntityInfo> entityInfoList = (List<StructureEntityInfo>) ENTITY_LIST_FIELD.get(template);
                List<EntityPlacement> entities = new ArrayList<>(entityInfoList.size());
                for (StructureEntityInfo info : entityInfoList) {
                    entities.add(new EntityPlacement(info.blockPos, info.nbt.copy()));
                }

                List<StructureBlockInfo> jigsawBlocks = getShuffledJigsaws(element);
                List<JigsawInfo> jigsaws = new ArrayList<>(jigsawBlocks.size());
                for (StructureBlockInfo info : jigsawBlocks) {
                    CompoundTag nbt = info.nbt();
                    if (nbt != null && nbt.contains("name", 8)) {
                        String name = nbt.getString("name");
                        Direction facing = getFacingFromInfo(info);
                        String finalState = nbt.getString("final_state");
                        if (finalState.isEmpty()) finalState = "minecraft:air";
                        jigsaws.add(new JigsawInfo(info.pos(), facing, name, finalState));
                    }
                }
                int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, minZ = Integer.MAX_VALUE;
                int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE, maxZ = Integer.MIN_VALUE;
                for (StructureBlockInfo info : blockInfoList) {
                    BlockPos pos = info.pos();
                    minX = Math.min(minX, pos.getX());
                    minY = Math.min(minY, pos.getY());
                    minZ = Math.min(minZ, pos.getZ());
                    maxX = Math.max(maxX, pos.getX());
                    maxY = Math.max(maxY, pos.getY());
                    maxZ = Math.max(maxZ, pos.getZ());
                }
                for (StructureEntityInfo info : entityInfoList) {
                    BlockPos pos = info.blockPos;
                    minX = Math.min(minX, pos.getX());
                    minY = Math.min(minY, pos.getY());
                    minZ = Math.min(minZ, pos.getZ());
                    maxX = Math.max(maxX, pos.getX());
                    maxY = Math.max(maxY, pos.getY());
                    maxZ = Math.max(maxZ, pos.getZ());
                }
                // 半开区间边界
                AABB localBounds = new AABB(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1);
                return new PlacementData(blocks, entities, jigsaws,localBounds);
            } catch (Exception e) {
                throw new RuntimeException("Failed to extract placement data for " + key, e);
            }
        });
    }

    private TransformedData transformData(PlacementData rawData, BlockPos worldPos, Rotation rotation,
                                          @Nullable String roomId, boolean isBossRoom) {
        List<BlockPlacement> transformedBlocks = new ArrayList<>(rawData.blocks().size());
        for (BlockPlacement bp : rawData.blocks()) {
            BlockPos transformedLocal = StructureTemplate.transform(bp.pos(), Mirror.NONE, rotation, BlockPos.ZERO);
            BlockPos worldBlockPos = worldPos.offset(transformedLocal);
            BlockState transformedState = bp.state().rotate(rotation);
            CompoundTag nbt = bp.nbt() != null ? bp.nbt().copy() : null;

            if (roomId != null && (transformedState.getBlock() instanceof SkeletonKingSummonBlock ||
                    transformedState.getBlock() instanceof SkeletonKingRemainsBlock ||
                    transformedState.getBlock() instanceof TombDoorCoreBlock)) {
                if (nbt == null) nbt = new CompoundTag();
                nbt.putString("RoomId", roomId);
            }
            if (transformedState.getBlock() instanceof EmblemBlock) {
                if (roomId != null) {
                    if (nbt == null) nbt = new CompoundTag();
                    nbt.putString("RoomId", roomId);
                }
            }
            if (transformedState.getBlock() instanceof TombDoorCoreBlock) {
                if (roomId != null) {
                    if (nbt == null) nbt = new CompoundTag();
                    nbt.putString("RoomId", roomId);
                    RoomManager.get(level).registerDoor(roomId, worldBlockPos);
                }
            }
            if (transformedState.getBlock() instanceof ChallengeCageBlock) {
                boolean elite = transformedState.getValue(ChallengeCageBlock.IS_ELITE);
                if (roomId != null) {
                    if (elite) {
                        RoomManager.get(level).registerEliteSpawner(roomId, worldBlockPos);
                    } else {
                        RoomManager.get(level).registerNormalSpawner(roomId, worldBlockPos);
                    }
                }
            }
            if (transformedState.getBlock() instanceof ChallengeAltarBlock) {
                ChallengeType type = transformedState.getValue(ChallengeAltarBlock.TYPE);
                if (roomId != null) {
                    if (nbt == null) nbt = new CompoundTag();
                    nbt.putString("RoomId", roomId);
                    RoomManager.get(level).registerAltar(roomId, worldBlockPos);
                }
            }
            if (transformedState.getBlock() instanceof TrialSpawnerBlock) {
                CompoundTag existingNbt = bp.nbt() != null ? bp.nbt().copy() : new CompoundTag();
                if (!existingNbt.contains("normal_config")) {
                    CompoundTag normalConfig = new CompoundTag();
                    ListTag lootTablesList = new ListTag();
                    CompoundTag lootEntry = new CompoundTag();
                    lootEntry.putString("data", "remains_cave:chests/trial");
                    lootEntry.putInt("weight", 1);
                    lootTablesList.add(lootEntry);
                    normalConfig.put("loot_tables_to_eject", lootTablesList);
                    existingNbt.put("normal_config", normalConfig);
                } else {
                    CompoundTag normalConfig = existingNbt.getCompound("normal_config");
                    ListTag lootTablesList = new ListTag();
                    CompoundTag lootEntry = new CompoundTag();
                    lootEntry.putString("data", "remains_cave:chests/trial");
                    lootEntry.putInt("weight", 1);
                    lootTablesList.add(lootEntry);
                    normalConfig.put("loot_tables_to_eject", lootTablesList);
                }
                nbt = existingNbt;
            }
            transformedBlocks.add(new BlockPlacement(worldBlockPos, transformedState, nbt));
        }

        List<EntityPlacement> transformedEntities = new ArrayList<>();
        for (EntityPlacement ep : rawData.entities()) {
            BlockPos transformedLocal = StructureTemplate.transform(ep.pos(), Mirror.NONE, rotation, BlockPos.ZERO);
            BlockPos worldEntityPos = worldPos.offset(transformedLocal);
            CompoundTag nbt = ep.nbt().copy();
            if (roomId != null && isBossRoom && isBossEntity(nbt)) {
                nbt.putString("RoomId", roomId);
            }
            transformedEntities.add(new EntityPlacement(worldEntityPos, nbt));
        }

        List<JigsawInfo> transformedJigsaws = new ArrayList<>();
        for (JigsawInfo ji : rawData.jigsaws()) {
            BlockPos transformedLocal = StructureTemplate.transform(ji.pos(), Mirror.NONE, rotation, BlockPos.ZERO);
            BlockPos worldJigsawPos = worldPos.offset(transformedLocal);
            Direction transformedFacing = applyRotationAndMirror(ji.facing(), rotation);

            ResourceLocation finalStateId = ResourceLocation.tryParse(ji.finalState());
            if (finalStateId == null) finalStateId = ResourceLocation.parse("minecraft:air");
            ResourceKey<Block> blockKey = ResourceKey.create(Registries.BLOCK, finalStateId);
            BlockState finalBlockState = level.registryAccess()
                    .lookupOrThrow(Registries.BLOCK)
                    .get(blockKey)
                    .map(holder -> holder.value().defaultBlockState())
                    .orElse(Blocks.AIR.defaultBlockState());

            transformedBlocks.add(new BlockPlacement(worldJigsawPos, finalBlockState, null));
            transformedJigsaws.add(new JigsawInfo(worldJigsawPos, transformedFacing, ji.name(), ji.finalState()));
        }
        AABB localBounds = rawData.localBounds();
        // 变换局部边界角点
        List<Vec3> corners = new ArrayList<>();
        for (double x : new double[]{localBounds.minX, localBounds.maxX - 1}) {
            for (double y : new double[]{localBounds.minY, localBounds.maxY - 1}) {
                for (double z : new double[]{localBounds.minZ, localBounds.maxZ - 1}) {
                    BlockPos transformedLocal = StructureTemplate.transform(new BlockPos((int)x, (int)y, (int)z), Mirror.NONE, rotation, BlockPos.ZERO);
                    Vec3 worldCorner = new Vec3(worldPos.getX() + transformedLocal.getX(),
                            worldPos.getY() + transformedLocal.getY(),
                            worldPos.getZ() + transformedLocal.getZ());
                    corners.add(worldCorner);
                }
            }
        }
        double minX = corners.stream().mapToDouble(Vec3::x).min().orElse(0);
        double minY = corners.stream().mapToDouble(Vec3::y).min().orElse(0);
        double minZ = corners.stream().mapToDouble(Vec3::z).min().orElse(0);
        double maxX = corners.stream().mapToDouble(Vec3::x).max().orElse(0);
        double maxY = corners.stream().mapToDouble(Vec3::y).max().orElse(0);
        double maxZ = corners.stream().mapToDouble(Vec3::z).max().orElse(0);
        AABB worldBounds = new AABB(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1);

        return new TransformedData(
                new PlacementData(transformedBlocks, transformedEntities, transformedJigsaws, rawData.localBounds()),
                worldBounds
        );
    }

    private boolean isBossEntity(CompoundTag nbt) {
        String id = nbt.getString("id");
        return id.equals("remains_cave:skeleton_king");
    }

    private void scheduleBatchPlacement(BatchPlacer placer, Runnable onComplete) {
        level.getServer().execute(() -> {
            boolean more = placer.placeNextBatch();
            if (more) {
                scheduleBatchPlacement(placer, onComplete);
            } else {
                if (onComplete != null) onComplete.run();
            }
        });
    }

    private void processPlacedJigsaws(PlacementData finalData, @Nullable String stepId) {
        if (stepId == null) {
            return;
        }
        RoutineStep currentStep = stepMap.get(stepId);
        if (currentStep == null) {
            return;
        }
        List<RoutineStep> subSteps = currentStep.getRoutine();
        if (subSteps == null || subSteps.isEmpty()) {
            return;
        }

        // 获取当前房间的 parentStaircaseId（通过任意一个 jigsaw 位置获取房间 ID）
        String parentStaircaseId;
        if (!finalData.jigsaws().isEmpty()) {
            BlockPos samplePos = finalData.jigsaws().getFirst().pos();
            RoomManager rm = RoomManager.get(level);
            String roomId = rm.getRoomIdAt(samplePos);
            if (roomId != null) {
                RoomState room = rm.getRooms().get(roomId);
                if (room != null) {
                    parentStaircaseId = room.getParentStaircaseId();
                    if (parentStaircaseId == null) parentStaircaseId = "";
                } else {
                    parentStaircaseId = "";
                }
            } else {
                parentStaircaseId = null;
            }
        } else {
            parentStaircaseId = null;
        }

        // 捕获 final 变量用于 lambda
        final String finalParentStaircaseId = parentStaircaseId;

        for (RoutineStep subStep : subSteps) {
            for (JigsawInfo jigsaw : finalData.jigsaws()) {
                if (subStep.getDoor().equals(jigsaw.name())) {
                    taskQueue.offer(() -> generateStep(subStep, jigsaw.pos(), jigsaw.facing(), stepId, finalParentStaircaseId));
                    break;
                }
            }
        }

        scheduleNext();
    }

    @Nullable
    private SinglePoolElement createElementFromStructure(ResourceLocation structureId) {
        if (structureId == null) return null;
        var processorRegistry = level.registryAccess().registryOrThrow(Registries.PROCESSOR_LIST);
        Holder<StructureProcessorList> emptyProcessorHolder = processorRegistry.getHolder(ResourceLocation.parse("minecraft:empty"))
                .orElseThrow(() -> new RuntimeException("Missing minecraft:empty processor list"));
        var projection = StructureTemplatePool.Projection.RIGID;
        StructurePoolElement element = StructurePoolElement.single(
                structureId.toString(),
                emptyProcessorHolder,
                LiquidSettings.APPLY_WATERLOGGING
        ).apply(projection);
        if (element instanceof SinglePoolElement single) return single;
        return null;
    }

    @Nullable
    private PlacementResult tryPlacement(SinglePoolElement element, JigsawInfo entry,
                                         BlockPos targetEntryPos, Direction requiredEntryFacing) {
        BlockPos entryLocalPos = entry.pos();
        Direction entryLocalFacing = entry.facing();
        List<PlacementResult> validPlacements = new ArrayList<>();
        for (Rotation rotation : Rotation.values()) {
            Direction rotatedFacing = rotation.rotate(entryLocalFacing);
            if (rotatedFacing != requiredEntryFacing) continue;
            BlockPos transformedPos = StructureTemplate.transform(entryLocalPos, Mirror.NONE, rotation, BlockPos.ZERO);
            BlockPos placePos = targetEntryPos.subtract(transformedPos);
            validPlacements.add(new PlacementResult(placePos, rotation, Mirror.NONE));
        }
        if (validPlacements.isEmpty()) return null;
        for (PlacementResult p : validPlacements) {
            if (p.rotation() == Rotation.CLOCKWISE_180) return p;
        }
        return validPlacements.getFirst();
    }

    @Nullable
    private SinglePoolElement pickRandomTemplate(ResourceLocation poolId) {
        if (poolId == null) return null;
        var registry = level.registryAccess().registryOrThrow(Registries.TEMPLATE_POOL);
        StructureTemplatePool pool = registry.get(poolId);
        if (pool == null) return null;
        StructurePoolElement element = pool.getRandomTemplate(random);
        if (element instanceof SinglePoolElement single) return single;
        return null;
    }

    @Nullable
    private JigsawInfo findJigsawInElement(SinglePoolElement element, String name) {
        List<StructureBlockInfo> jigsaws = getShuffledJigsaws(element);
        for (StructureBlockInfo info : jigsaws) {
            CompoundTag nbt = info.nbt();
            if (nbt != null && nbt.contains("name", 8)) {
                String nbtName = nbt.getString("name");
                if (name.equals(nbtName)) {
                    Direction facing = getFacingFromInfo(info);
                    String finalState = nbt.getString("final_state");
                    if (finalState.isEmpty()) finalState = "minecraft:air";
                    return new JigsawInfo(info.pos(), facing, nbtName, finalState);
                }
            }
        }
        return null;
    }

    private List<StructureBlockInfo> getShuffledJigsaws(SinglePoolElement element) {
        return element.getShuffledJigsawBlocks(templateManager, BlockPos.ZERO, Rotation.NONE, random);
    }

    private Direction getFacingFromInfo(StructureBlockInfo info) {
        CompoundTag nbt = info.nbt();
        if (nbt != null) {
            if (nbt.contains("facing", 8)) {
                Direction dir = Direction.byName(nbt.getString("facing"));
                if (dir != null) return dir;
            } else if (nbt.contains("facing", 3)) {
                return Direction.from3DDataValue(nbt.getInt("facing"));
            }
        }
        return JigsawBlock.getFrontFacing(info.state());
    }

    private Direction applyRotationAndMirror(Direction facing, Rotation rotation) {
        return rotation.rotate(facing);
    }

    private record PlacementResult(BlockPos pos, Rotation rotation, Mirror mirror) {}
}