package com.tiedup.remake.network.cell;

import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.MarkerType;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.network.base.AbstractClientPacket;
import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

/**
 * Packet to sync cell data from server to client.
 * This enables cell outline rendering on dedicated servers.
 *
 * Server -> Client
 *
 * Phase: Kidnapper Revamp - Cell System Network Sync
 */
public class PacketSyncCellData extends AbstractClientPacket {

    private final UUID cellId;
    private final BlockPos spawnPoint;
    private final Map<MarkerType, Set<BlockPos>> positions;
    private final List<BlockPos> pathWaypoints;
    private final String name;
    private final UUID ownerId;

    /**
     * Create a sync packet for a V2 cell.
     * Maps V2 feature lists back to MarkerType sets for wire format compatibility.
     */
    public PacketSyncCellData(CellDataV2 cell) {
        this.cellId = cell.getId();
        this.spawnPoint = cell.getCorePos();
        this.positions = new EnumMap<>(MarkerType.class);
        this.pathWaypoints = new ArrayList<>(cell.getPathWaypoints());
        this.name = cell.getName();
        this.ownerId = cell.getOwnerId();

        // Map V2 feature lists to MarkerType sets for serialization
        if (!cell.getWallBlocks().isEmpty()) {
            this.positions.put(
                MarkerType.WALL,
                new HashSet<>(cell.getWallBlocks())
            );
        }
        if (!cell.getAnchors().isEmpty()) {
            this.positions.put(
                MarkerType.ANCHOR,
                new HashSet<>(cell.getAnchors())
            );
        }
        if (!cell.getDoors().isEmpty()) {
            this.positions.put(MarkerType.DOOR, new HashSet<>(cell.getDoors()));
        }
        if (!cell.getBeds().isEmpty()) {
            this.positions.put(MarkerType.BED, new HashSet<>(cell.getBeds()));
        }
        BlockPos delivery = cell.getDeliveryPoint();
        if (delivery != null) {
            Set<BlockPos> deliverySet = new HashSet<>();
            deliverySet.add(delivery);
            this.positions.put(MarkerType.DELIVERY, deliverySet);
        }
    }

    /**
     * Private constructor for decoding.
     */
    private PacketSyncCellData(
        UUID cellId,
        BlockPos spawnPoint,
        Map<MarkerType, Set<BlockPos>> positions,
        List<BlockPos> pathWaypoints,
        String name,
        UUID ownerId
    ) {
        this.cellId = cellId;
        this.spawnPoint = spawnPoint;
        this.positions = positions;
        this.pathWaypoints = pathWaypoints;
        this.name = name;
        this.ownerId = ownerId;
    }

    public void encode(FriendlyByteBuf buf) {
        buf.writeUUID(this.cellId);
        buf.writeBlockPos(this.spawnPoint);

        // Write name (nullable)
        buf.writeBoolean(this.name != null);
        if (this.name != null) {
            buf.writeUtf(this.name, 64);
        }

        // Write owner (nullable)
        buf.writeBoolean(this.ownerId != null);
        if (this.ownerId != null) {
            buf.writeUUID(this.ownerId);
        }

        // Write positions by type
        // First, count non-empty types
        int typeCount = 0;
        for (MarkerType type : MarkerType.values()) {
            if (
                this.positions.containsKey(type) &&
                !this.positions.get(type).isEmpty()
            ) {
                typeCount++;
            }
        }
        buf.writeInt(typeCount);

        // Write each non-empty type
        for (MarkerType type : MarkerType.values()) {
            Set<BlockPos> typePositions = this.positions.get(type);
            if (typePositions != null && !typePositions.isEmpty()) {
                buf.writeUtf(type.getSerializedName(), 32);
                buf.writeInt(typePositions.size());
                for (BlockPos pos : typePositions) {
                    buf.writeBlockPos(pos);
                }
            }
        }

        // Write path waypoints
        buf.writeInt(this.pathWaypoints.size());
        for (BlockPos wp : this.pathWaypoints) {
            buf.writeBlockPos(wp);
        }
    }

    public static PacketSyncCellData decode(FriendlyByteBuf buf) {
        UUID cellId = buf.readUUID();
        BlockPos spawnPoint = buf.readBlockPos();

        // Read name
        String name = buf.readBoolean() ? buf.readUtf(64) : null;

        // Read owner
        UUID ownerId = buf.readBoolean() ? buf.readUUID() : null;

        // Read positions
        Map<MarkerType, Set<BlockPos>> positions = new EnumMap<>(
            MarkerType.class
        );
        int typeCount = buf.readInt();

        for (int i = 0; i < typeCount; i++) {
            String typeName = buf.readUtf(32);
            MarkerType type = MarkerType.fromString(typeName);

            int posCount = buf.readInt();
            Set<BlockPos> typePositions = new HashSet<>();
            for (int j = 0; j < posCount; j++) {
                typePositions.add(buf.readBlockPos());
            }
            positions.put(type, typePositions);
        }

        // Read path waypoints
        int waypointCount = buf.readInt();
        List<BlockPos> pathWaypoints = new ArrayList<>(waypointCount);
        for (int i = 0; i < waypointCount; i++) {
            pathWaypoints.add(buf.readBlockPos());
        }

        return new PacketSyncCellData(
            cellId,
            spawnPoint,
            positions,
            pathWaypoints,
            name,
            ownerId
        );
    }

    @Override
    @OnlyIn(Dist.CLIENT)
    protected void handleClientImpl() {
        ClientHandler.handle(this);
    }

    @OnlyIn(Dist.CLIENT)
    private static class ClientHandler {

        private static void handle(PacketSyncCellData pkt) {
            TiedUpMod.LOGGER.debug(
                "[PacketSyncCellData] Received cell sync: {} at {} with {} position types, {} waypoints",
                pkt.cellId.toString().substring(0, 8),
                pkt.spawnPoint.toShortString(),
                pkt.positions.size(),
                pkt.pathWaypoints.size()
            );

            // Reconstruct a CellDataV2 from the wire data
            CellDataV2 cell = new CellDataV2(pkt.cellId, pkt.spawnPoint);
            cell.setName(pkt.name);
            cell.setOwnerId(pkt.ownerId);

            // Populate geometry from MarkerType positions
            for (BlockPos pos : pkt.positions.getOrDefault(
                MarkerType.WALL,
                Collections.emptySet()
            )) {
                cell.addWallBlock(pos);
            }
            for (BlockPos pos : pkt.positions.getOrDefault(
                MarkerType.ANCHOR,
                Collections.emptySet()
            )) {
                cell.addAnchor(pos);
            }
            for (BlockPos pos : pkt.positions.getOrDefault(
                MarkerType.DOOR,
                Collections.emptySet()
            )) {
                cell.addDoor(pos);
            }
            for (BlockPos pos : pkt.positions.getOrDefault(
                MarkerType.BED,
                Collections.emptySet()
            )) {
                cell.addBed(pos);
            }
            Set<BlockPos> delivery = pkt.positions.getOrDefault(
                MarkerType.DELIVERY,
                Collections.emptySet()
            );
            if (!delivery.isEmpty()) {
                cell.setDeliveryPoint(delivery.iterator().next());
            }

            // Add path waypoints
            cell.setPathWaypoints(pkt.pathWaypoints);

            // Update the client-side cache
            com.tiedup.remake.client.events.CellHighlightHandler.updateCachedCell(
                cell
            );
        }
    }

    // Getters for testing/debugging
    public UUID getCellId() {
        return cellId;
    }

    public BlockPos getSpawnPoint() {
        return spawnPoint;
    }

    public Map<MarkerType, Set<BlockPos>> getPositions() {
        return Collections.unmodifiableMap(positions);
    }
}
