package com.xfestudio.mydimension.network;

import com.xfestudio.mydimension.MyDimension;
import com.xfestudio.mydimension.builder.BuilderNetworkBridge;
import com.xfestudio.mydimension.builder.anchor.AnchorBindings;
import com.xfestudio.mydimension.network.blueprint.BlueprintNetworkRegistration;
import com.xfestudio.mydimension.network.builder.BuilderNetworkRegistration;
import com.xfestudio.mydimension.network.builder.BuilderOpenMenuPacket;
import com.xfestudio.mydimension.network.builder.BuilderPreviewPacket;
import com.xfestudio.mydimension.network.builder.BuilderSnapshotPacket;
import com.xfestudio.mydimension.network.builder.BuilderAvailabilityPacket;
import com.xfestudio.mydimension.config.BuilderConfig;
import com.xfestudio.mydimension.registry.ModItems;
import com.xfestudio.mydimension.world.MindTeamAccess;
import com.xfestudio.mydimension.world.PrivateMindFeature;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel;

import java.util.List;
import java.util.UUID;

public class ModNetwork {
    // Builder settings snapshots and commands gained the replacement-policy bit/action. Older v5
    // peers would otherwise read a different packet shape and must not join this protocol.
    private static final String PROTOCOL_VERSION = "6";

    public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel(
            new ResourceLocation(MyDimension.MOD_ID, "main"),
            () -> PROTOCOL_VERSION,
            PROTOCOL_VERSION::equals,
            PROTOCOL_VERSION::equals
    );

    public static void register() {
        int id = 0;
        CHANNEL.registerMessage(
                id++,
                SetRiftActionPacket.class,
                SetRiftActionPacket::encode,
                SetRiftActionPacket::decode,
                SetRiftActionPacket::handle
        );
        CHANNEL.registerMessage(
                id++,
                TeamMindCommandPacket.class,
                TeamMindCommandPacket::encode,
                TeamMindCommandPacket::decode,
                TeamMindCommandPacket::handle
        );
        CHANNEL.registerMessage(
                id++,
                TeamMindDataPacket.class,
                TeamMindDataPacket::encode,
                TeamMindDataPacket::decode,
                TeamMindDataPacket::handle
        );
        CHANNEL.registerMessage(
                id++,
                MindAccessPromptPacket.class,
                MindAccessPromptPacket::encode,
                MindAccessPromptPacket::decode,
                MindAccessPromptPacket::handle
        );
        id = BuilderNetworkRegistration.register(CHANNEL, id);
        BlueprintNetworkRegistration.register(CHANNEL, id);
        BuilderNetworkBridge.install(ModNetwork::sendBuilderOpenMenu, ModNetwork::sendBuilderState);
    }

    public static void sendTeamMindData(ServerPlayer player) {
        if (!PrivateMindFeature.isEnabled()) {
            CHANNEL.send(PacketDistributor.PLAYER.with(() -> player),
                    new TeamMindDataPacket(List.of(), List.of(), List.of()));
            return;
        }

        TeamMindDataPacket packet = TeamMindDataPacket.from(
                MindTeamAccess.entrancesFor(player),
                MindTeamAccess.candidatesFor(player),
                MindTeamAccess.guestsFor(player)
        );
        CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), packet);
    }

    public static void sendMindAccessPrompt(ServerPlayer owner, MindTeamAccess.PendingRequest request) {
        CHANNEL.send(PacketDistributor.PLAYER.with(() -> owner),
                new MindAccessPromptPacket(request.guestId(), request.guestName()));
    }

    public static void sendBuilderAvailability(ServerPlayer player) {
        CHANNEL.send(PacketDistributor.PLAYER.with(() -> player),
                new BuilderAvailabilityPacket(BuilderConfig.isEnabled()));
    }

    /** Opens only after the common-side caller has proved the player is holding the scepter. */
    public static void sendBuilderOpenMenu(ServerPlayer player) {
        if (!player.getMainHandItem().is(ModItems.REALMWRIGHT_SCEPTER.get())) return;
        CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), new BuilderOpenMenuPacket());
        sendBuilderState(player);
    }

    /** Sends a matched UI snapshot and missing-material preview for the main-hand scepter. */
    public static void sendBuilderState(ServerPlayer player) {
        if (!player.getMainHandItem().is(ModItems.REALMWRIGHT_SCEPTER.get())) return;
        sendBuilderSnapshot(player);
        CHANNEL.send(PacketDistributor.PLAYER.with(() -> player),
                BuilderPreviewPacket.from(player, player.getMainHandItem()));
    }

    /** Lightweight metadata refresh used by the open screen's periodic poll. */
    public static void sendBuilderSnapshot(ServerPlayer player) {
        if (!player.getMainHandItem().is(ModItems.REALMWRIGHT_SCEPTER.get())) return;
        CHANNEL.send(PacketDistributor.PLAYER.with(() -> player),
                BuilderSnapshotPacket.from(player, player.getMainHandItem()));
    }

    /**
     * Removes a destroyed anchor from every online player's currently held scepter and
     * invalidates its lightweight client metadata.
     *
     * <p>Other scepters are cleaned lazily when they are next held and synchronized.  The item
     * sync here is important because clearing only SavedData would leave the client-side main-hand
     * whitelist stale and the supply screen would still list a disconnected endpoint.</p>
     */
    public static void sendBuilderAnchorInvalidation(MinecraftServer server, UUID anchorId) {
        for (ServerPlayer player : server.getPlayerList().getPlayers()) {
            var scepter = player.getMainHandItem();
            if (!scepter.is(ModItems.REALMWRIGHT_SCEPTER.get())
                    || !AnchorBindings.unbind(scepter, anchorId)) continue;

            player.getInventory().setChanged();
            player.containerMenu.broadcastChanges();
            if (player.inventoryMenu != player.containerMenu) {
                player.inventoryMenu.broadcastChanges();
            }
            sendBuilderSnapshot(player);
        }
    }
}
