package com.tiedup.remake.network.sync;

import com.mojang.logging.LogUtils;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.util.ValidationHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import java.util.*;
import java.util.function.Function;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

/**
 * Centralized synchronization manager for TiedUp mod.
 *
 * This manager handles all synchronization between server and client:
 * - Provides unified methods for syncing inventory, state, and enslavement
 * - Server-side: Sends packets to tracking clients
 * - Client-side: Delegates to ClientSyncHandler for queue management
 *
 * IMPORTANT: This class is loaded on both server and client.
 * All client-specific code (Minecraft.getInstance(), queue processing)
 * has been moved to ClientSyncHandler to avoid NoClassDefFoundError
 * on dedicated servers.
 *
 * @see ClientSyncHandler for client-side queue handling
 */
public class SyncManager {

    private static final Logger LOGGER = LogUtils.getLogger();

    // ==================== Server-side sync methods ====================

    /**
     * Generic sync method that handles the common pattern:
     * 1. Check if server-side
     * 2. Create packet from player
     * 3. Send to all tracking and self
     *
     *
     * @param player The player to sync (must be ServerPlayer)
     * @param packetFactory Function to create packet from player
     * @param <T> The packet type
     */
    private static <T> void sendSync(
        @Nullable Player player,
        Function<Player, T> packetFactory
    ) {
        ValidationHelper.asServerPlayer(player).ifPresent(serverPlayer -> {
            T packet = packetFactory.apply(player);
            if (packet != null) {
                ModNetwork.sendToAllTrackingAndSelf(packet, serverPlayer);
            }
        });
    }

    /**
     * Sync a player's bondage inventory to all tracking clients and themselves.
     * This is the main method to call when equipment changes.
     *
     * <p><b>Epic 5A:</b> Delegates to {@link V2EquipmentHelper#sync} which sends
     * {@code PacketSyncV2Equipment}.
     *
     * @param player The player whose inventory changed (must be ServerPlayer)
     */
    public static void syncInventory(Player player) {
        // V2: Use unified V2 equipment sync
        ValidationHelper.asServerPlayer(player).ifPresent(serverPlayer -> {
            V2EquipmentHelper.sync(serverPlayer);
        });
    }

    /**
     * Sync a player's bind state flags to all tracking clients and themselves.
     *
     * @param player The player whose state changed (must be ServerPlayer)
     */
    public static void syncBindState(Player player) {
        sendSync(player, PacketSyncBindState::fromPlayer);
    }

    /**
     * Sync a player's enslavement state to all tracking clients and themselves.
     *
     * @param player The player whose enslavement state changed (must be ServerPlayer)
     */
    public static void syncEnslavement(Player player) {
        sendSync(player, PacketSyncEnslavement::fromPlayer);
    }

    /**
     * Sync a player's struggle animation state to all tracking clients and themselves.
     * Used when struggle animation starts or stops.
     *
     * @param player The player whose struggle state changed (must be ServerPlayer)
     */
    public static void syncStruggleState(Player player) {
        sendSync(player, PacketSyncStruggleState::fromPlayer);
    }

    /**
     * Sync a player's clothes configuration to all tracking clients and themselves.
     * Used when clothes are equipped, unequipped, or their settings are changed.
     *
     * @param player The player whose clothes state changed (must be ServerPlayer)
     */
    public static void syncClothesConfig(Player player) {
        ValidationHelper.asServerPlayer(player).ifPresent(serverPlayer -> {
            ItemStack clothes = V2EquipmentHelper.getInRegion(
                player,
                BodyRegionV2.TORSO
            );
            PacketSyncClothesConfig packet = new PacketSyncClothesConfig(
                player.getUUID(),
                clothes
            );
            ModNetwork.sendToAllTrackingAndSelf(packet, serverPlayer);
        });
    }

    /**
     * Sync all data (inventory + state + enslavement + struggle + clothes) for a player.
     *
     * @param player The player to sync
     */
    public static void syncAll(Player player) {
        syncInventory(player);
        syncBindState(player);
        syncEnslavement(player);
        syncStruggleState(player);
        syncClothesConfig(player);
    }

    /**
     * Sync all online players' data TO a specific player.
     * Called when a player joins to ensure they see everyone's state.
     *
     * <p><b>Epic 5A:</b> Equipment sync uses V2 {@link V2EquipmentHelper#syncTo}.
     *
     * @param target The player who just joined and needs to receive all data
     */
    public static void syncAllPlayersTo(ServerPlayer target) {
        if (target.level().isClientSide) return;

        var server = target.getServer();
        if (server == null) return;

        int syncedCount = 0;
        for (ServerPlayer otherPlayer : server.getPlayerList().getPlayers()) {
            if (otherPlayer == target) continue; // Skip self

            // V2: Send other player's V2 equipment to target
            V2EquipmentHelper.syncTo(otherPlayer, target);

            // Send other player's state to target
            PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(
                otherPlayer
            );
            if (statePacket != null) {
                ModNetwork.sendToPlayer(statePacket, target);
            }

            // Send other player's enslavement state to target
            PacketSyncEnslavement enslavementPacket =
                PacketSyncEnslavement.fromPlayer(otherPlayer);
            if (enslavementPacket != null) {
                ModNetwork.sendToPlayer(enslavementPacket, target);
            }

            // Send other player's struggle state to target
            PacketSyncStruggleState strugglePacket =
                PacketSyncStruggleState.fromPlayer(otherPlayer);
            if (strugglePacket != null) {
                ModNetwork.sendToPlayer(strugglePacket, target);
            }

            // Send other player's clothes config to target
            ItemStack clothes = V2EquipmentHelper.getInRegion(
                otherPlayer,
                BodyRegionV2.TORSO
            );
            if (!clothes.isEmpty()) {
                PacketSyncClothesConfig clothesPacket =
                    new PacketSyncClothesConfig(otherPlayer.getUUID(), clothes);
                ModNetwork.sendToPlayer(clothesPacket, target);
            }

            syncedCount++;
        }

        if (syncedCount > 0) {
            LOGGER.debug(
                "[SyncManager] Synced {} players' data to {}",
                syncedCount,
                target.getName().getString()
            );
        }
    }

    // ==================== Client-side queue delegation ====================
    // These methods delegate to ClientSyncHandler on client side.
    // They are safe to call from packet handlers because they check the side.

    /**
     * Queue a bind state sync for later processing.
     * Called from PacketSyncBindState.handle() when target player is null.
     *
     * @param targetUUID The UUID of the player to sync
     * @param stateFlags The state flags to apply
     */
    @OnlyIn(Dist.CLIENT)
    public static void queueBindStateSync(UUID targetUUID, byte stateFlags) {
        ClientSyncHandler.queueBindStateSync(targetUUID, stateFlags);
    }

    /**
     * Queue an enslavement sync for later processing.
     * Called from PacketSyncEnslavement.handle() when target player is null.
     *
     * @param targetUUID The UUID of the player to sync
     * @param masterUUID The master's UUID (null if free)
     * @param transportUUID The transport entity UUID (null if no transport)
     * @param isEnslaved Whether the player is enslaved
     */
    @OnlyIn(Dist.CLIENT)
    public static void queueEnslavementSync(
        UUID targetUUID,
        UUID masterUUID,
        UUID transportUUID,
        boolean isEnslaved
    ) {
        ClientSyncHandler.queueEnslavementSync(
            targetUUID,
            masterUUID,
            transportUUID,
            isEnslaved
        );
    }

    /**
     * Clear all pending syncs for a player (e.g., when they disconnect).
     *
     * @param playerUUID The player's UUID
     */
    @OnlyIn(Dist.CLIENT)
    public static void clearPendingForPlayer(UUID playerUUID) {
        ClientSyncHandler.clearPendingForPlayer(playerUUID);
    }

    /**
     * Clear the entire pending queue (e.g., when disconnecting from server).
     */
    @OnlyIn(Dist.CLIENT)
    public static void clearAllPending() {
        ClientSyncHandler.clearAllPending();
    }
}
