package com.xfestudio.mydimension.network.builder;

import com.xfestudio.mydimension.builder.PendingBuildData;
import com.xfestudio.mydimension.builder.RealmwrightData;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.network.NetworkEvent;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;

/** Immutable server preview. At present this primarily carries material-waiting cells. */
public record BuilderPreviewPacket(ResourceLocation dimension, List<Cell> cells,
                                   @Nullable BlockPos first, @Nullable BlockPos second,
                                   @Nullable UUID activeJobId, boolean blueprintPreview,
                                   boolean cancelable, int revision) {
    private static final int MAX_CELLS = 65_536;

    public BuilderPreviewPacket {
        cells = List.copyOf(cells);
        first = first == null ? null : first.immutable();
        second = second == null ? null : second.immutable();
    }

    public static BuilderPreviewPacket from(ServerPlayer player, ItemStack scepter) {
        PendingBuildData.Task task = PendingBuildData.get(player.getServer()).get(player.getUUID());
        if (task == null || !task.scepterId().equals(RealmwrightData.id(scepter))) {
            return new BuilderPreviewPacket(player.level().dimension().location(), List.of(),
                    null, null, null, false, false, revision(player));
        }
        List<Cell> cells = task.missing().stream().limit(MAX_CELLS)
                .map(entry -> new Cell(entry.pos(), entry.state(), Kind.MISSING, true))
                .toList();
        return new BuilderPreviewPacket(task.dimension().location(), cells, null, null,
                task.transactionId(), false, true, revision(player));
    }

    public static void encode(BuilderPreviewPacket packet, FriendlyByteBuf buffer) {
        buffer.writeResourceLocation(packet.dimension);
        buffer.writeVarInt(packet.cells.size());
        for (Cell cell : packet.cells) {
            buffer.writeBlockPos(cell.pos);
            buffer.writeVarInt(Block.getId(cell.state));
            buffer.writeEnum(cell.kind);
            buffer.writeBoolean(cell.ghost);
        }
        writeOptionalPos(buffer, packet.first);
        writeOptionalPos(buffer, packet.second);
        writeOptionalUuid(buffer, packet.activeJobId);
        buffer.writeBoolean(packet.blueprintPreview);
        buffer.writeBoolean(packet.cancelable);
        buffer.writeVarInt(packet.revision);
    }

    public static BuilderPreviewPacket decode(FriendlyByteBuf buffer) {
        ResourceLocation dimension = buffer.readResourceLocation();
        int count = boundedCount(buffer.readVarInt(), MAX_CELLS, "builder preview cells");
        List<Cell> cells = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            BlockPos pos = buffer.readBlockPos();
            BlockState state = Block.stateById(buffer.readVarInt());
            cells.add(new Cell(pos, state, buffer.readEnum(Kind.class), buffer.readBoolean()));
        }
        return new BuilderPreviewPacket(dimension, cells, readOptionalPos(buffer), readOptionalPos(buffer),
                readOptionalUuid(buffer), buffer.readBoolean(), buffer.readBoolean(), buffer.readVarInt());
    }

    public static void handle(BuilderPreviewPacket packet,
                              Supplier<NetworkEvent.Context> contextSupplier) {
        NetworkEvent.Context context = contextSupplier.get();
        context.enqueueWork(() -> BuilderClientPacketHooks.receiver().preview(packet));
        context.setPacketHandled(true);
    }

    private static int revision(ServerPlayer player) {
        return (int) (player.serverLevel().getGameTime() & Integer.MAX_VALUE);
    }

    private static int boundedCount(int value, int maximum, String label) {
        if (value < 0 || value > maximum) {
            throw new IllegalArgumentException("Invalid " + label + " count: " + value);
        }
        return value;
    }

    private static void writeOptionalPos(FriendlyByteBuf buffer, @Nullable BlockPos value) {
        buffer.writeBoolean(value != null);
        if (value != null) buffer.writeBlockPos(value);
    }

    @Nullable
    private static BlockPos readOptionalPos(FriendlyByteBuf buffer) {
        return buffer.readBoolean() ? buffer.readBlockPos() : null;
    }

    private static void writeOptionalUuid(FriendlyByteBuf buffer, @Nullable UUID value) {
        buffer.writeBoolean(value != null);
        if (value != null) buffer.writeUUID(value);
    }

    @Nullable
    private static UUID readOptionalUuid(FriendlyByteBuf buffer) {
        return buffer.readBoolean() ? buffer.readUUID() : null;
    }

    public record Cell(BlockPos pos, BlockState state, Kind kind, boolean ghost) {
        public Cell {
            pos = pos.immutable();
        }
    }

    public enum Kind {
        BUILD,
        DEMOLISH,
        MISSING,
        INVALID,
        BLUEPRINT
    }
}
