package com.tiedup.remake.worldgen;

import com.mojang.serialization.Codec;
import com.tiedup.remake.blocks.ModBlocks;
import com.tiedup.remake.cells.MarkerType;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorType;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import org.jetbrains.annotations.Nullable;

/**
 * Structure processor that handles MarkerBlockEntity and CellCoreBlockEntity
 * when structures are placed.
 *
 * Phase: Kidnapper Revamp - Cell System
 *
 * When a structure containing cell markers or Cell Cores is placed:
 * 1. Remaps cellId UUIDs to fresh ones (prevents collisions between structures)
 * 2. Clears cellIds from structure markers (ENTRANCE, PATROL, LOOT, SPAWNER)
 * 3. Handles Cell Core blocks: remaps cellId, rotates spawnPoint/deliveryPoint offsets
 */
public class MarkerProcessor extends StructureProcessor {

    public static final Codec<MarkerProcessor> CODEC = Codec.unit(
        MarkerProcessor::new
    );

    /**
     * Track cell ID mappings during structure placement.
     * Old UUID -> New UUID.
     *
     * Made instance-based instead of static ThreadLocal to prevent memory leaks.
     * Each structure placement gets its own MarkerProcessor instance, so mappings
     * are naturally scoped to a single structure and garbage collected when done.
     */
    private final Map<UUID, UUID> cellIdMappings = new HashMap<>();

    public MarkerProcessor() {}

    @Nullable
    @Override
    public StructureTemplate.StructureBlockInfo processBlock(
        LevelReader level,
        BlockPos offset,
        BlockPos pos,
        StructureTemplate.StructureBlockInfo blockInfo,
        StructureTemplate.StructureBlockInfo relativeBlockInfo,
        StructurePlaceSettings settings
    ) {
        // Process Cell Core blocks (V2)
        if (relativeBlockInfo.state().is(ModBlocks.CELL_CORE.get())) {
            return processCellCore(relativeBlockInfo, settings);
        }

        // Process marker blocks (V1 + structure markers)
        if (!relativeBlockInfo.state().is(ModBlocks.MARKER.get())) {
            return relativeBlockInfo;
        }

        CompoundTag nbt = relativeBlockInfo.nbt();
        if (nbt == null) {
            return relativeBlockInfo;
        }

        // Get the marker type
        MarkerType markerType = MarkerType.WALL;
        if (nbt.contains("markerType")) {
            markerType = MarkerType.fromString(nbt.getString("markerType"));
        }

        // Structure markers don't need cell IDs - just clear any old one
        if (markerType.isStructureMarker()) {
            CompoundTag newNbt = nbt.copy();
            newNbt.remove("cellId");
            return new StructureTemplate.StructureBlockInfo(
                relativeBlockInfo.pos(),
                relativeBlockInfo.state(),
                newNbt
            );
        }

        // Cell markers need special handling
        // If this marker has a cellId, we need to map it to a new one
        if (nbt.contains("cellId")) {
            UUID oldCellId = nbt.getUUID("cellId");

            UUID newCellId;
            if (cellIdMappings.containsKey(oldCellId)) {
                // Use existing mapping
                newCellId = cellIdMappings.get(oldCellId);
            } else {
                // Create new cell ID (actual cell will be created when block entity loads)
                newCellId = UUID.randomUUID();
                cellIdMappings.put(oldCellId, newCellId);
            }

            // Update the NBT with new cell ID
            CompoundTag newNbt = nbt.copy();
            newNbt.putUUID("cellId", newCellId);

            // Rotate cell positions if present (important for structure rotation)
            if (newNbt.contains("cellPositions")) {
                CompoundTag cellPositions = newNbt.getCompound("cellPositions");
                CompoundTag rotatedPositions = new CompoundTag();

                for (String key : cellPositions.getAllKeys()) {
                    ListTag posList = cellPositions.getList(
                        key,
                        Tag.TAG_COMPOUND
                    );
                    ListTag rotatedList = new ListTag();

                    for (int i = 0; i < posList.size(); i++) {
                        BlockPos posOffset = NbtUtils.readBlockPos(
                            posList.getCompound(i)
                        );
                        BlockPos rotated = posOffset.rotate(
                            settings.getRotation()
                        );
                        rotatedList.add(NbtUtils.writeBlockPos(rotated));
                    }
                    rotatedPositions.put(key, rotatedList);
                }
                newNbt.put("cellPositions", rotatedPositions);

                com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                    "[MarkerProcessor] Rotated cellPositions for cell {}, rotation={}",
                    newCellId.toString().substring(0, 8),
                    settings.getRotation()
                );
            }

            return new StructureTemplate.StructureBlockInfo(
                relativeBlockInfo.pos(),
                relativeBlockInfo.state(),
                newNbt
            );
        }

        return relativeBlockInfo;
    }

    /**
     * Process a Cell Core block from a structure template.
     * Remaps the cellId UUID and rotates relative position offsets.
     */
    private StructureTemplate.StructureBlockInfo processCellCore(
        StructureTemplate.StructureBlockInfo blockInfo,
        StructurePlaceSettings settings
    ) {
        CompoundTag nbt = blockInfo.nbt();
        if (nbt == null || !nbt.contains("cellId")) {
            return blockInfo;
        }

        UUID oldCellId = nbt.getUUID("cellId");
        UUID newCellId;
        if (cellIdMappings.containsKey(oldCellId)) {
            newCellId = cellIdMappings.get(oldCellId);
        } else {
            newCellId = UUID.randomUUID();
            cellIdMappings.put(oldCellId, newCellId);
        }

        CompoundTag newNbt = nbt.copy();
        newNbt.putUUID("cellId", newCellId);

        // Remove old absolute position keys (from pre-offset saves)
        newNbt.remove("spawnPoint");
        newNbt.remove("deliveryPoint");

        // Rotate relative offset positions for structure rotation
        rotateOffset(newNbt, "spawnOffset", settings);
        rotateOffset(newNbt, "deliveryOffset", settings);

        // Rotate pathWaypoint offsets
        if (newNbt.contains("pathWaypointOffsets")) {
            ListTag list = newNbt.getList(
                "pathWaypointOffsets",
                Tag.TAG_COMPOUND
            );
            ListTag rotated = new ListTag();
            for (int i = 0; i < list.size(); i++) {
                BlockPos offset = NbtUtils.readBlockPos(list.getCompound(i));
                rotated.add(
                    NbtUtils.writeBlockPos(
                        offset.rotate(settings.getRotation())
                    )
                );
            }
            newNbt.put("pathWaypointOffsets", rotated);
        }

        com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
            "[MarkerProcessor] Remapped Cell Core cellId {} -> {} at {}, rotation={}",
            oldCellId.toString().substring(0, 8),
            newCellId.toString().substring(0, 8),
            blockInfo.pos().toShortString(),
            settings.getRotation()
        );

        return new StructureTemplate.StructureBlockInfo(
            blockInfo.pos(),
            blockInfo.state(),
            newNbt
        );
    }

    /**
     * Rotate a single BlockPos offset stored in NBT for structure rotation.
     */
    private static void rotateOffset(
        CompoundTag nbt,
        String key,
        StructurePlaceSettings settings
    ) {
        if (nbt.contains(key)) {
            BlockPos offset = NbtUtils.readBlockPos(nbt.getCompound(key));
            BlockPos rotated = offset.rotate(settings.getRotation());
            nbt.put(key, NbtUtils.writeBlockPos(rotated));
        }
    }

    @Override
    protected StructureProcessorType<?> getType() {
        return ModProcessors.MARKER_PROCESSOR.get();
    }
}
