package com.tiedup.remake.blocks.entity;

import com.tiedup.remake.blocks.ModBlocks;
import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.cells.MarkerType;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.EntityKidnapperMerchant;
import com.tiedup.remake.entities.EntityMaid;
import com.tiedup.remake.entities.EntitySlaveTrader;
import com.tiedup.remake.entities.ModEntities;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.level.block.SlabBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;

/**
 * BlockEntity for marker blocks in the cell system.
 *
 * Phase: Kidnapper Revamp - Cell System
 *
 * Stores:
 * - Cell UUID this marker belongs to
 * - Marker type (WALL, ANCHOR, etc.)
 *
 * The marker serves as the spawn point for a cell and links to the CellRegistry.
 */
public class MarkerBlockEntity extends BlockEntity {

    @Nullable
    private UUID cellId;

    private MarkerType markerType = MarkerType.WALL;

    /** Flag to track if spawner has already spawned its kidnapper */
    private boolean hasSpawned = false;

    public MarkerBlockEntity(BlockPos pos, BlockState state) {
        super(ModBlockEntities.MARKER.get(), pos, state);
    }

    // ==================== CELL ID ====================

    /**
     * Get the cell UUID this marker belongs to.
     */
    @Nullable
    public UUID getCellId() {
        return cellId;
    }

    /**
     * Set the cell UUID this marker belongs to.
     */
    public void setCellId(@Nullable UUID cellId) {
        this.cellId = cellId;
        setChangedAndSync();
    }

    // ==================== MARKER TYPE ====================

    /**
     * Get the marker type.
     */
    public MarkerType getMarkerType() {
        return markerType;
    }

    /**
     * Set the marker type.
     */
    public void setMarkerType(MarkerType type) {
        this.markerType = type;
        setChangedAndSync();
    }

    /**
     * Handle marker destruction.
     * - If this is the spawn point marker: delete the entire cell
     * - If this is a linked position (WALL, ANCHOR, etc.): only remove this position
     *
     * @return true if something was deleted
     */
    public boolean deleteCell() {
        if (cellId == null || !(level instanceof ServerLevel serverLevel)) {
            return false;
        }

        CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
        CellDataV2 cell = registry.getCell(cellId);

        if (cell == null) {
            // Cell doesn't exist - just clear our reference
            this.cellId = null;
            setChangedAndSync();
            return false;
        }

        boolean result;

        // Check if this marker is the spawn point (primary marker) or a secondary position
        if (
            cell.getCorePos().equals(worldPosition) ||
            (cell.getSpawnPoint() != null &&
                cell.getSpawnPoint().equals(worldPosition))
        ) {
            // This is the core/spawn point - remove the entire cell
            registry.removeCell(cellId);
            com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                "[MarkerBlockEntity] Spawn point destroyed - removed entire cell {}",
                cellId.toString().substring(0, 8)
            );
            result = true;
        } else {
            // Secondary position — V2 geometry is managed by flood-fill, just clear reference
            com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                "[MarkerBlockEntity] Secondary marker {} cleared from cell {}",
                worldPosition.toShortString(),
                cellId.toString().substring(0, 8)
            );
            result = true;
        }

        this.cellId = null;
        setChangedAndSync();

        return result;
    }

    // ==================== LIFECYCLE ====================

    @Override
    public void onLoad() {
        super.onLoad();

        if (!(level instanceof ServerLevel serverLevel)) {
            return;
        }

        // V1→V2 RETROCOMPAT: Cell markers loaded from old .nbt templates.
        // Only the primary marker (spawn point, with cachedCellPositions) creates a Cell Core.
        // Secondary markers (WALL/BED/DOOR/etc. without cachedCellPositions) are simply removed —
        // the Core's flood-fill will reconstruct the cell geometry.
        if (markerType.isCellMarker()) {
            if (cachedCellPositions != null) {
                // Primary marker (spawn point) → convert to Cell Core with data transfer
                convertToCellCore(serverLevel);
            } else {
                // Secondary marker (WALL/BED/DOOR/etc.) → just remove, flood-fill will handle
                com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                    "[MarkerBlockEntity] Removing secondary cell marker {} at {}",
                    markerType.getSerializedName(),
                    worldPosition.toShortString()
                );
                serverLevel.removeBlock(worldPosition, false);
            }
            return;
        }

        // Register LOOT markers in CampData for fast chest lookup
        if (markerType == MarkerType.LOOT) {
            CampOwnership ownership = CampOwnership.get(serverLevel);
            CampOwnership.CampData nearestCamp = ownership.findNearestAliveCamp(
                worldPosition,
                50
            );
            if (nearestCamp != null) {
                // LOOT markers are above chests - register the position below
                BlockPos chestPos = worldPosition.below();
                if (
                    serverLevel.getBlockEntity(chestPos) instanceof
                        ChestBlockEntity
                ) {
                    nearestCamp.addLootChestPosition(chestPos);
                    ownership.setDirty();
                    com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                        "[MarkerBlockEntity] Registered LOOT chest at {} for camp {}",
                        chestPos.toShortString(),
                        nearestCamp.getCampId().toString().substring(0, 8)
                    );
                }
            }
        }

        // Debug: Log all spawn-related marker checks
        if (
            markerType == MarkerType.SPAWNER ||
            markerType == MarkerType.TRADER_SPAWN ||
            markerType == MarkerType.MAID_SPAWN ||
            markerType == MarkerType.MERCHANT_SPAWN
        ) {
            com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                "[MarkerBlockEntity] onLoad() SPAWN marker at {}: type={}, hasSpawned={}",
                worldPosition.toShortString(),
                markerType.getSerializedName(),
                hasSpawned
            );
        }

        // Handle SPAWNER markers - spawn a kidnapper on first load
        if (markerType == MarkerType.SPAWNER && !hasSpawned) {
            com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                "[MarkerBlockEntity] onLoad() Triggering spawnKidnapper at {}",
                worldPosition.toShortString()
            );
            spawnKidnapper(serverLevel);
        }

        // Handle TRADER_SPAWN markers - spawn a SlaveTrader on first load
        if (markerType == MarkerType.TRADER_SPAWN && !hasSpawned) {
            com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                "[MarkerBlockEntity] onLoad() Triggering spawnTrader at {}",
                worldPosition.toShortString()
            );
            spawnTrader(serverLevel);
        }

        // Handle MAID_SPAWN markers - spawn a Maid on first load
        if (markerType == MarkerType.MAID_SPAWN && !hasSpawned) {
            com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                "[MarkerBlockEntity] onLoad() Triggering spawnMaid at {}",
                worldPosition.toShortString()
            );
            spawnMaid(serverLevel);
        }

        // Handle MERCHANT_SPAWN markers - spawn a Merchant on first load
        if (markerType == MarkerType.MERCHANT_SPAWN && !hasSpawned) {
            com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                "[MarkerBlockEntity] onLoad() Triggering spawnMerchant at {}",
                worldPosition.toShortString()
            );
            spawnMerchant(serverLevel);
        }
    }

    /**
     * Convert this cell marker to a V2 Cell Core block.
     * Used for retrocompat: old .nbt templates that still have BlockMarker cell markers.
     *
     * Steps:
     * 1. Find solid block below this position (max 3 down) for Core placement
     * 2. Place BlockCellCore at that position
     * 3. Configure Core: cellId, spawnPoint, deliveryPoint from cached positions (if available)
     * 4. Core's onLoad() triggers flood-fill and V2 cell creation
     * 5. Replace this marker with air
     *
     * Works for both primary markers (with cachedCellPositions) and secondary cell markers.
     * When cachedCellPositions is null, delivery point won't be set — maid AI handles null fallback.
     */
    private void convertToCellCore(ServerLevel serverLevel) {
        // Find solid block below for Core placement
        // Accepts full cubes (isSolidRender) and slabs (upper or lower half)
        BlockPos corePos = null;
        for (int yOff = 0; yOff <= 3; yOff++) {
            BlockPos candidate = worldPosition.below(yOff);
            BlockState candidateState = serverLevel.getBlockState(candidate);
            if (
                candidateState.isSolidRender(serverLevel, candidate) ||
                candidateState.getBlock() instanceof SlabBlock
            ) {
                corePos = candidate;
                break;
            }
        }

        if (corePos == null) {
            // Fallback: place Core at marker position itself
            corePos = worldPosition;
        }

        // Skip if a Cell Core already exists at this position (prevents duplicates)
        if (
            serverLevel.getBlockState(corePos).getBlock() instanceof
                com.tiedup.remake.blocks.BlockCellCore
        ) {
            com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                "[MarkerBlockEntity] Skipping Core placement at {} — already exists (marker at {})",
                corePos.toShortString(),
                worldPosition.toShortString()
            );
            if (!corePos.equals(worldPosition)) {
                serverLevel.removeBlock(worldPosition, false);
            }
            return;
        }

        // Place Cell Core block
        serverLevel.setBlock(
            corePos,
            ModBlocks.CELL_CORE.get().defaultBlockState(),
            3
        );

        // Configure the Core block entity
        if (
            serverLevel.getBlockEntity(corePos) instanceof
                CellCoreBlockEntity coreBE
        ) {
            // Ensure a cellId exists — new camp markers won't have one
            UUID coreId = this.cellId != null ? this.cellId : UUID.randomUUID();
            coreBE.setCellId(coreId);
            coreBE.setSpawnPoint(worldPosition); // Marker position becomes spawn point

            // Extract delivery point from cached positions
            if (
                cachedCellPositions != null &&
                cachedCellPositions.contains(
                    MarkerType.DELIVERY.getSerializedName()
                )
            ) {
                net.minecraft.nbt.ListTag deliveryList =
                    cachedCellPositions.getList(
                        MarkerType.DELIVERY.getSerializedName(),
                        net.minecraft.nbt.Tag.TAG_COMPOUND
                    );
                if (deliveryList.size() > 0) {
                    BlockPos deliveryOffset =
                        net.minecraft.nbt.NbtUtils.readBlockPos(
                            deliveryList.getCompound(0)
                        );
                    BlockPos deliveryWorld = worldPosition.offset(
                        deliveryOffset
                    );
                    coreBE.setDeliveryPoint(deliveryWorld);
                }
            }

            // Extract pathWaypoints from cached positions
            if (
                cachedCellPositions != null &&
                cachedCellPositions.contains("pathWaypoints")
            ) {
                net.minecraft.nbt.ListTag waypointsList =
                    cachedCellPositions.getList(
                        "pathWaypoints",
                        net.minecraft.nbt.Tag.TAG_COMPOUND
                    );
                java.util.ArrayList<BlockPos> waypoints =
                    new java.util.ArrayList<>();
                for (int i = 0; i < waypointsList.size(); i++) {
                    BlockPos offset = net.minecraft.nbt.NbtUtils.readBlockPos(
                        waypointsList.getCompound(i)
                    );
                    waypoints.add(worldPosition.offset(offset));
                }
                coreBE.setPendingPathWaypoints(waypoints);
            }

            com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                "[MarkerBlockEntity] Converted spawn marker to Cell Core at {} (marker was at {}, cellId={})",
                corePos.toShortString(),
                worldPosition.toShortString(),
                cellId != null ? cellId.toString().substring(0, 8) : "null"
            );
        }

        // Replace the marker block with air (unless Core was placed at our position)
        if (!corePos.equals(worldPosition)) {
            serverLevel.removeBlock(worldPosition, false);
        }
    }

    /**
     * Spawn a kidnapper at this spawner marker position.
     * Called once when the marker is first loaded (structure placement).
     */
    private void spawnKidnapper(ServerLevel serverLevel) {
        EntityKidnapper kidnapper = ModEntities.KIDNAPPER.get().create(
            serverLevel
        );
        if (kidnapper != null) {
            // Spawn above the marker block
            double x = worldPosition.getX() + 0.5;
            double y = worldPosition.getY() + 1.0;
            double z = worldPosition.getZ() + 0.5;

            kidnapper.moveTo(
                x,
                y,
                z,
                serverLevel.random.nextFloat() * 360F,
                0.0F
            );
            kidnapper.finalizeSpawn(
                serverLevel,
                serverLevel.getCurrentDifficultyAt(worldPosition),
                MobSpawnType.STRUCTURE,
                null,
                null
            );

            // Pre-link to structure camp ID so dispersal goal works immediately
            // Uses same ID generation as trader spawn for consistency
            UUID structureCampId = generateStructureCampId(worldPosition);
            kidnapper.setAssociatedStructure(structureCampId);

            serverLevel.addFreshEntity(kidnapper);

            // Mark as spawned so we don't spawn again on chunk reload
            hasSpawned = true;
            setChangedAndSync();

            com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                "[MarkerBlockEntity] Spawned kidnapper at {} for camp {}",
                worldPosition.toShortString(),
                structureCampId.toString().substring(0, 8)
            );
        }
    }

    /**
     * Spawn a SlaveTrader at this marker position.
     * Creates a camp entry in CampOwnership.
     * Links nearby kidnappers and spawns new ones.
     */
    private void spawnTrader(ServerLevel serverLevel) {
        EntitySlaveTrader trader = ModEntities.SLAVE_TRADER.get().create(
            serverLevel
        );
        if (trader != null) {
            double x = worldPosition.getX() + 0.5;
            double y = worldPosition.getY() + 1.0;
            double z = worldPosition.getZ() + 0.5;

            trader.moveTo(x, y, z, serverLevel.random.nextFloat() * 360F, 0.0F);
            trader.finalizeSpawn(
                serverLevel,
                serverLevel.getCurrentDifficultyAt(worldPosition),
                MobSpawnType.STRUCTURE,
                null,
                null
            );

            // Create camp entry using structure-based camp ID for consistency
            // This ensures cells created by the same structure will link to this camp
            UUID campId = generateStructureCampId(worldPosition);
            trader.setCampUUID(campId);
            trader.setSpawnPos(worldPosition);

            serverLevel.addFreshEntity(trader);

            // Register camp ownership
            CampOwnership ownership = CampOwnership.get(serverLevel);
            ownership.registerCamp(
                campId,
                trader.getUUID(),
                null,
                worldPosition
            );

            // Link nearby existing kidnappers to this camp (100 block radius)
            linkNearbyKidnappers(serverLevel, campId, worldPosition);

            // Link nearby cells to this camp (they were created with their own cellId as ownerId)
            linkNearbyCellsToCamp(serverLevel, campId, worldPosition);

            // Spawn ~10 new kidnappers in an annulus around the camp (40-100 block radius)
            spawnCampKidnappers(serverLevel, campId, worldPosition);

            hasSpawned = true;
            setChangedAndSync();

            com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                "[MarkerBlockEntity] Spawned SlaveTrader at {} for camp {}",
                worldPosition.toShortString(),
                campId.toString().substring(0, 8)
            );
        }
    }

    /**
     * Link existing kidnappers within 100 blocks to the camp.
     */
    private void linkNearbyKidnappers(
        ServerLevel serverLevel,
        UUID campId,
        BlockPos center
    ) {
        CampOwnership ownership = CampOwnership.get(serverLevel);

        // Search for kidnappers within 100 blocks
        AABB searchBox = new AABB(center).inflate(100, 50, 100);
        List<EntityKidnapper> nearbyKidnappers = serverLevel.getEntitiesOfClass(
            EntityKidnapper.class,
            searchBox,
            kidnapper -> !(kidnapper instanceof EntityMaid) // Don't link maids
        );

        int linked = 0;
        for (EntityKidnapper kidnapper : nearbyKidnappers) {
            UUID existingCamp = kidnapper.getAssociatedStructure();
            if (existingCamp == null) {
                // No camp yet - link to this trader's camp
                kidnapper.setAssociatedStructure(campId);
                ownership.linkKidnapperToCamp(campId, kidnapper.getUUID());
                linked++;
            } else if (
                !existingCamp.equals(campId) &&
                !ownership.isCampAlive(existingCamp)
            ) {
                // Kidnapper has an orphaned camp ID (structure grid boundary split —
                // generateStructureCampId can give different IDs for markers in the
                // same structure if they straddle a 128-block grid boundary).
                // Re-link to the real camp since the orphan was never registered.
                kidnapper.setAssociatedStructure(campId);
                ownership.linkKidnapperToCamp(campId, kidnapper.getUUID());
                linked++;
                com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                    "[MarkerBlockEntity] Re-linked kidnapper {} from orphan camp {} to {}",
                    kidnapper.getNpcName(),
                    existingCamp.toString().substring(0, 8),
                    campId.toString().substring(0, 8)
                );
            }
        }

        com.tiedup.remake.core.TiedUpMod.LOGGER.info(
            "[MarkerBlockEntity] Linked {} existing kidnappers to camp {}",
            linked,
            campId.toString().substring(0, 8)
        );
    }

    /**
     * Link nearby cells to this camp.
     * Cells created by structure placement may have:
     * - Their own cellId as ownerId (old behavior)
     * - A structure-based camp ID (new behavior)
     * This method updates them to use the canonical campId from the trader.
     */
    private void linkNearbyCellsToCamp(
        ServerLevel serverLevel,
        UUID campId,
        BlockPos center
    ) {
        CellRegistryV2 registry = CellRegistryV2.get(serverLevel);

        // Find all cells within 50 blocks (typical structure size)
        // Reduced from 100 to avoid linking cells from other structures
        List<CellDataV2> nearbyCells = registry.findCellsNear(center, 50);

        // Also calculate what the structure-based camp ID would be for this position
        UUID expectedStructureCampId = generateStructureCampId(center);

        com.tiedup.remake.core.TiedUpMod.LOGGER.info(
            "[MarkerBlockEntity] linkNearbyCellsToCamp: found {} cells near {}, expectedStructureCampId={}, targetCampId={}",
            nearbyCells.size(),
            center.toShortString(),
            expectedStructureCampId.toString().substring(0, 8),
            campId.toString().substring(0, 8)
        );

        int linked = 0;
        for (CellDataV2 cell : nearbyCells) {
            // Only link cells that are camp-owned
            if (cell.isCampOwned()) {
                UUID currentOwnerId = cell.getOwnerId();

                com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                    "[MarkerBlockEntity] Checking cell {} at {} (currentOwnerId={}, expectedStructureCampId={}, targetCampId={})",
                    cell.getId().toString().substring(0, 8),
                    cell.getCorePos().toShortString(),
                    currentOwnerId != null
                        ? currentOwnerId.toString().substring(0, 8)
                        : "null",
                    expectedStructureCampId.toString().substring(0, 8),
                    campId.toString().substring(0, 8)
                );

                // Link if:
                // 1. ownerId equals cellId (old structure default) - need to update
                // 2. ownerId equals structure-based camp ID but different from target - need to update
                // 3. ownerId is already correct (campId) - update index only
                boolean needsLink = false;
                boolean updateIndexOnly = false;

                if (currentOwnerId != null) {
                    if (currentOwnerId.equals(campId)) {
                        // ownerId is already correct - just ensure index is updated
                        updateIndexOnly = true;
                        com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                            "[MarkerBlockEntity] Cell {} already has correct ownerId, updating index only",
                            cell.getId().toString().substring(0, 8)
                        );
                    } else if (currentOwnerId.equals(cell.getId())) {
                        // Old behavior: cell has its own ID as owner
                        needsLink = true;
                        com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                            "[MarkerBlockEntity] Cell {} needs link (reason: ownerId equals cellId)",
                            cell.getId().toString().substring(0, 8)
                        );
                    } else if (currentOwnerId.equals(expectedStructureCampId)) {
                        // New behavior: cell has structure-based ID, update to canonical camp ID
                        needsLink = true;
                        com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                            "[MarkerBlockEntity] Cell {} needs link (reason: ownerId equals expectedStructureCampId)",
                            cell.getId().toString().substring(0, 8)
                        );
                    } else {
                        // Check if cell's camp is orphaned (grid boundary split —
                        // different markers in the same structure got different camp IDs)
                        CampOwnership ownership = CampOwnership.get(
                            serverLevel
                        );
                        if (!ownership.isCampAlive(currentOwnerId)) {
                            needsLink = true;
                            com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                                "[MarkerBlockEntity] Cell {} needs link (reason: orphan camp {} not registered)",
                                cell.getId().toString().substring(0, 8),
                                currentOwnerId.toString().substring(0, 8)
                            );
                        }
                    }
                }

                if (needsLink) {
                    // Update ownerId and index
                    UUID oldOwnerId = currentOwnerId;
                    cell.setOwnerId(campId);
                    registry.updateCampIndex(cell, oldOwnerId);
                    linked++;

                    com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                        "[MarkerBlockEntity] Linked cell {} to camp {} (changed ownerId)",
                        cell.getId().toString().substring(0, 8),
                        campId.toString().substring(0, 8)
                    );
                } else if (updateIndexOnly) {
                    // ownerId is correct, just update index
                    registry.updateCampIndex(cell, null);
                    linked++;

                    com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                        "[MarkerBlockEntity] Linked cell {} to camp {} (index update only)",
                        cell.getId().toString().substring(0, 8),
                        campId.toString().substring(0, 8)
                    );
                }
            }
        }

        com.tiedup.remake.core.TiedUpMod.LOGGER.info(
            "[MarkerBlockEntity] Linked {} cells to camp {}",
            linked,
            campId.toString().substring(0, 8)
        );
    }

    /**
     * Spawn ~10 kidnappers in an annulus around the camp center (40-100 blocks).
     */
    private void spawnCampKidnappers(
        ServerLevel serverLevel,
        UUID campId,
        BlockPos center
    ) {
        CampOwnership ownership = CampOwnership.get(serverLevel);
        int spawned = 0;
        int targetCount = 10;

        // Try to spawn 10 kidnappers
        for (
            int attempt = 0;
            attempt < 50 && spawned < targetCount;
            attempt++
        ) {
            // Random angle
            double angle = serverLevel.random.nextDouble() * Math.PI * 2;

            // Random distance in the 40-100 block annulus
            double distance = 40 + serverLevel.random.nextDouble() * 60;

            int targetX = center.getX() + (int) (Math.cos(angle) * distance);
            int targetZ = center.getZ() + (int) (Math.sin(angle) * distance);

            // Find valid spawn position
            BlockPos spawnPos = findValidSpawnPosition(
                serverLevel,
                targetX,
                targetZ,
                center.getY()
            );
            if (spawnPos == null) {
                continue;
            }

            // Spawn kidnapper
            EntityKidnapper kidnapper = ModEntities.KIDNAPPER.get().create(
                serverLevel
            );
            if (kidnapper != null) {
                kidnapper.moveTo(
                    spawnPos.getX() + 0.5,
                    spawnPos.getY(),
                    spawnPos.getZ() + 0.5,
                    serverLevel.random.nextFloat() * 360F,
                    0.0F
                );
                kidnapper.finalizeSpawn(
                    serverLevel,
                    serverLevel.getCurrentDifficultyAt(spawnPos),
                    MobSpawnType.STRUCTURE,
                    null,
                    null
                );

                // Link to camp
                kidnapper.setAssociatedStructure(campId);
                serverLevel.addFreshEntity(kidnapper);
                ownership.linkKidnapperToCamp(campId, kidnapper.getUUID());
                spawned++;
            }
        }

        com.tiedup.remake.core.TiedUpMod.LOGGER.info(
            "[MarkerBlockEntity] Spawned {} new kidnappers for camp {}",
            spawned,
            campId.toString().substring(0, 8)
        );
    }

    /**
     * Find a valid spawn position at the given XZ coordinates.
     * Checks for solid ground with air above.
     */
    @Nullable
    private BlockPos findValidSpawnPosition(
        ServerLevel level,
        int x,
        int z,
        int baseY
    ) {
        // Search within ±20 blocks of base Y
        for (int yOffset = 0; yOffset <= 20; yOffset++) {
            // Check above first
            BlockPos above = new BlockPos(x, baseY + yOffset, z);
            if (isValidSpawnPosition(level, above)) {
                return above;
            }

            // Then check below
            if (yOffset > 0) {
                BlockPos below = new BlockPos(x, baseY - yOffset, z);
                if (isValidSpawnPosition(level, below)) {
                    return below;
                }
            }
        }
        return null;
    }

    /**
     * Check if a position is valid for spawning.
     * Requires solid ground below and 2 blocks of air.
     */
    private boolean isValidSpawnPosition(ServerLevel level, BlockPos pos) {
        // Ground must be solid
        if (!level.getBlockState(pos.below()).isSolid()) {
            return false;
        }
        // Feet and head must be non-solid (air or passable)
        if (
            level.getBlockState(pos).isSolid() ||
            level.getBlockState(pos.above()).isSolid()
        ) {
            return false;
        }
        // Not in water
        if (
            !level.getFluidState(pos).isEmpty() ||
            !level.getFluidState(pos.above()).isEmpty()
        ) {
            return false;
        }
        return true;
    }

    /**
     * Spawn a Maid at this marker position.
     * Links to the nearest SlaveTrader.
     */
    private void spawnMaid(ServerLevel serverLevel) {
        EntityMaid maid = ModEntities.MAID.get().create(serverLevel);
        if (maid != null) {
            double x = worldPosition.getX() + 0.5;
            double y = worldPosition.getY() + 1.0;
            double z = worldPosition.getZ() + 0.5;

            maid.moveTo(x, y, z, serverLevel.random.nextFloat() * 360F, 0.0F);
            maid.finalizeSpawn(
                serverLevel,
                serverLevel.getCurrentDifficultyAt(worldPosition),
                MobSpawnType.STRUCTURE,
                null,
                null
            );

            serverLevel.addFreshEntity(maid);

            // Find nearest trader and link
            CampOwnership ownership = CampOwnership.get(serverLevel);
            CampOwnership.CampData nearestCamp = ownership.findNearestAliveCamp(
                worldPosition,
                50
            );
            if (nearestCamp != null) {
                maid.setMasterTraderUUID(nearestCamp.getTraderUUID());

                // Update camp with maid UUID
                nearestCamp.setMaidUUID(maid.getUUID());

                // Update trader with maid UUID
                var traderEntity = serverLevel.getEntity(
                    nearestCamp.getTraderUUID()
                );
                if (traderEntity instanceof EntitySlaveTrader trader) {
                    trader.setMaidUUID(maid.getUUID());
                }

                com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                    "[MarkerBlockEntity] Spawned Maid at {} linked to trader {}",
                    worldPosition.toShortString(),
                    nearestCamp.getTraderUUID().toString().substring(0, 8)
                );
            } else {
                com.tiedup.remake.core.TiedUpMod.LOGGER.warn(
                    "[MarkerBlockEntity] Spawned Maid at {} but no trader found nearby",
                    worldPosition.toShortString()
                );
            }

            hasSpawned = true;
            setChangedAndSync();
        }
    }

    /**
     * Spawn a Merchant at this marker position.
     * Links to the nearest camp/trader.
     */
    private void spawnMerchant(ServerLevel serverLevel) {
        EntityKidnapperMerchant merchant =
            ModEntities.KIDNAPPER_MERCHANT.get().create(serverLevel);
        if (merchant != null) {
            double x = worldPosition.getX() + 0.5;
            double y = worldPosition.getY() + 1.0;
            double z = worldPosition.getZ() + 0.5;

            merchant.moveTo(
                x,
                y,
                z,
                serverLevel.random.nextFloat() * 360F,
                0.0F
            );
            merchant.finalizeSpawn(
                serverLevel,
                serverLevel.getCurrentDifficultyAt(worldPosition),
                MobSpawnType.STRUCTURE,
                null,
                null
            );

            serverLevel.addFreshEntity(merchant);

            // Find nearest camp and link
            CampOwnership ownership = CampOwnership.get(serverLevel);
            CampOwnership.CampData nearestCamp = ownership.findNearestAliveCamp(
                worldPosition,
                100
            );
            if (nearestCamp != null) {
                merchant.setAssociatedStructure(nearestCamp.getCampId());

                com.tiedup.remake.core.TiedUpMod.LOGGER.info(
                    "[MarkerBlockEntity] Spawned Merchant at {} linked to camp {}",
                    worldPosition.toShortString(),
                    nearestCamp.getCampId().toString().substring(0, 8)
                );
            } else {
                com.tiedup.remake.core.TiedUpMod.LOGGER.warn(
                    "[MarkerBlockEntity] Spawned Merchant at {} but no camp found nearby",
                    worldPosition.toShortString()
                );
            }

            hasSpawned = true;
            setChangedAndSync();
        }
    }

    // ==================== SPAWN RESET ====================

    /**
     * Reset the hasSpawned flag to false.
     * Used by admin commands to prepare markers for structure saving.
     */
    public void resetHasSpawned() {
        this.hasSpawned = false;
        setChangedAndSync();
    }

    /**
     * Check if this marker has already spawned its NPC.
     */
    public boolean hasSpawned() {
        return hasSpawned;
    }

    /**
     * Check if this marker is a spawn type (SPAWNER, TRADER_SPAWN, MAID_SPAWN, MERCHANT_SPAWN).
     */
    public boolean isSpawnMarker() {
        return (
            markerType == MarkerType.SPAWNER ||
            markerType == MarkerType.TRADER_SPAWN ||
            markerType == MarkerType.MAID_SPAWN ||
            markerType == MarkerType.MERCHANT_SPAWN
        );
    }

    // ==================== STRUCTURE CAMP ID ====================

    /**
     * Generate a deterministic camp ID based on structure position.
     * Uses a grid-based approach: rounds position to nearest 32 blocks,
     * ensuring all markers in the same structure get the same camp ID.
     *
     * @param pos The position of the marker
     * @return A deterministic UUID for this structure
     */
    private UUID generateStructureCampId(BlockPos pos) {
        // Round to nearest 128 blocks (structure grid)
        // Increased from 32 to 128 to ensure entire structure (40-80 blocks) falls in same grid cell
        int gridX = (pos.getX() / 128) * 128;
        int gridZ = (pos.getZ() / 128) * 128;

        // Create a deterministic UUID from the grid position
        // Using a simple hash-based approach
        long mostSigBits = ((long) gridX << 32) | (gridZ & 0xFFFFFFFFL);
        long leastSigBits = 0x8000000000000000L | (gridX ^ gridZ); // Version 4 UUID-like

        return new UUID(mostSigBits, leastSigBits);
    }

    // ==================== NBT SERIALIZATION ====================

    /** Cached cell positions from NBT (for structure placement) */
    private CompoundTag cachedCellPositions = null;

    @Override
    public void load(CompoundTag tag) {
        super.load(tag);

        if (tag.contains("cellId")) {
            this.cellId = tag.getUUID("cellId");
        } else {
            this.cellId = null;
        }

        if (tag.contains("markerType")) {
            this.markerType = MarkerType.fromString(
                tag.getString("markerType")
            );
        }

        if (tag.contains("hasSpawned")) {
            this.hasSpawned = tag.getBoolean("hasSpawned");
        }

        // Debug logging to understand structure loading
        com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
            "[MarkerBlockEntity] load() at {}: markerType={}, hasSpawned={}, cellId={}",
            worldPosition != null ? worldPosition.toShortString() : "unknown",
            markerType.getSerializedName(),
            hasSpawned,
            cellId != null ? cellId.toString().substring(0, 8) : "null"
        );

        // Cache cell positions from structure NBT (will be applied in onLoad)
        if (tag.contains("cellPositions")) {
            this.cachedCellPositions = tag.getCompound("cellPositions");
            com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                "[MarkerBlockEntity] load(): Found cellPositions in NBT for cell {}: {}",
                cellId != null ? cellId.toString().substring(0, 8) : "null",
                cachedCellPositions.getAllKeys()
            );
        }
    }

    @Override
    protected void saveAdditional(CompoundTag tag) {
        super.saveAdditional(tag);

        if (cellId != null) {
            tag.putUUID("cellId", cellId);

            // If this is the spawn point marker, save all cell positions
            // This allows structures to preserve cell data
            if (level instanceof ServerLevel serverLevel) {
                CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
                CellDataV2 cell = registry.getCell(cellId);
                if (
                    cell != null &&
                    (cell.getCorePos().equals(worldPosition) ||
                        (cell.getSpawnPoint() != null &&
                            cell.getSpawnPoint().equals(worldPosition)))
                ) {
                    // Save positions relative to spawn point for structure portability
                    CompoundTag positionsTag = saveCellPositionsRelative(cell);
                    if (!positionsTag.isEmpty()) {
                        tag.put("cellPositions", positionsTag);
                        com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                            "[MarkerBlockEntity] Saved cellPositions for cell {} at {}: {}",
                            cellId.toString().substring(0, 8),
                            worldPosition.toShortString(),
                            positionsTag.getAllKeys()
                        );
                    }
                } else if (cell == null) {
                    com.tiedup.remake.core.TiedUpMod.LOGGER.debug(
                        "[MarkerBlockEntity] saveAdditional: cell {} not found in registry",
                        cellId.toString().substring(0, 8)
                    );
                }
            }
        }
        tag.putString("markerType", markerType.getSerializedName());

        if (hasSpawned) {
            tag.putBoolean("hasSpawned", true);
        }
    }

    /**
     * Save cell positions as relative offsets from spawn point.
     * This makes the structure portable to any location.
     */
    private CompoundTag saveCellPositionsRelative(CellDataV2 cell) {
        CompoundTag positionsTag = new CompoundTag();

        // Save V2 typed positions as relative offsets
        savePositionList(
            positionsTag,
            MarkerType.DELIVERY.getSerializedName(),
            cell.getDeliveryPoint() != null
                ? List.of(cell.getDeliveryPoint())
                : List.of()
        );
        savePositionList(
            positionsTag,
            MarkerType.BED.getSerializedName(),
            cell.getBeds()
        );
        savePositionList(
            positionsTag,
            MarkerType.ANCHOR.getSerializedName(),
            cell.getAnchors()
        );
        savePositionList(
            positionsTag,
            MarkerType.DOOR.getSerializedName(),
            cell.getDoors()
        );
        savePositionList(
            positionsTag,
            "pathWaypoints",
            cell.getPathWaypoints()
        );

        return positionsTag;
    }

    /** Save a list of positions as relative offsets from this marker's worldPosition. */
    private void savePositionList(
        CompoundTag tag,
        String key,
        java.util.Collection<BlockPos> positions
    ) {
        if (positions.isEmpty()) return;
        net.minecraft.nbt.ListTag list = new net.minecraft.nbt.ListTag();
        for (BlockPos pos : positions) {
            list.add(
                net.minecraft.nbt.NbtUtils.writeBlockPos(
                    pos.subtract(worldPosition)
                )
            );
        }
        tag.put(key, list);
    }

    // ==================== NETWORK SYNC ====================

    protected void setChangedAndSync() {
        if (level != null) {
            setChanged();
            level.sendBlockUpdated(
                worldPosition,
                getBlockState(),
                getBlockState(),
                3
            );
        }
    }

    @Override
    public CompoundTag getUpdateTag() {
        CompoundTag tag = super.getUpdateTag();
        if (cellId != null) {
            tag.putUUID("cellId", cellId);
        }
        tag.putString("markerType", markerType.getSerializedName());
        return tag;
    }

    @Nullable
    @Override
    public Packet<ClientGamePacketListener> getUpdatePacket() {
        return ClientboundBlockEntityDataPacket.create(this);
    }

    @Override
    public void handleUpdateTag(CompoundTag tag) {
        if (tag.contains("cellId")) {
            this.cellId = tag.getUUID("cellId");
        }
        if (tag.contains("markerType")) {
            this.markerType = MarkerType.fromString(
                tag.getString("markerType")
            );
        }
    }
}
