package com.tiedup.remake.client;

import com.mojang.blaze3d.platform.InputConstants;
import com.tiedup.remake.client.gui.screens.AdjustmentScreen;
import com.tiedup.remake.client.gui.screens.UnifiedBondageScreen;
import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketForceSeatModifier;
import com.tiedup.remake.network.action.PacketStruggle;
import com.tiedup.remake.network.action.PacketTighten;
import com.tiedup.remake.network.bounty.PacketRequestBounties;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart;
import net.minecraft.ChatFormatting;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.jetbrains.annotations.Nullable;

/**
 *
 * Manages key mappings and sends packets to server when keys are pressed.
 *
 * Based on original KeyBindings from 1.12.2
 */
@Mod.EventBusSubscriber(
    modid = TiedUpMod.MOD_ID,
    bus = Mod.EventBusSubscriber.Bus.FORGE,
    value = Dist.CLIENT
)
public class ModKeybindings {

    /**
     * Key category for TiedUp keybindings
     */
    private static final String CATEGORY = "key.categories.tiedup";

    /**
     * Struggle keybinding - Press to struggle against binds
     * Default: R key
     */
    public static final KeyMapping STRUGGLE_KEY = new KeyMapping(
        "key.tiedup.struggle", // Translation key
        InputConstants.Type.KEYSYM,
        InputConstants.KEY_R, // Default key: R
        CATEGORY
    );

    /**
     * Adjustment screen keybinding - Open item adjustment screen
     * Default: K key
     */
    public static final KeyMapping ADJUSTMENT_KEY = new KeyMapping(
        "key.tiedup.adjustment_screen",
        InputConstants.Type.KEYSYM,
        InputConstants.KEY_K, // Default key: K
        CATEGORY
    );

    /**
     * Bondage inventory keybinding - Open bondage inventory screen
     * Default: J key
     */
    public static final KeyMapping INVENTORY_KEY = new KeyMapping(
        "key.tiedup.bondage_inventory",
        InputConstants.Type.KEYSYM,
        InputConstants.KEY_J, // Default key: J
        CATEGORY
    );

    /**
     * Slave management keybinding - Open slave management dashboard
     * Default: L key
     */
    public static final KeyMapping SLAVE_MANAGEMENT_KEY = new KeyMapping(
        "key.tiedup.slave_management",
        InputConstants.Type.KEYSYM,
        InputConstants.KEY_L, // Default key: L
        CATEGORY
    );

    /**
     * Bounty list keybinding - Open bounty list screen
     * Default: B key
     */
    public static final KeyMapping BOUNTY_KEY = new KeyMapping(
        "key.tiedup.bounties",
        InputConstants.Type.KEYSYM,
        InputConstants.KEY_B, // Default key: B
        CATEGORY
    );

    /**
     * Force seat keybinding - Hold to force captive on/off vehicles
     * Default: Left ALT key
     */
    public static final KeyMapping FORCE_SEAT_KEY = new KeyMapping(
        "key.tiedup.force_seat",
        InputConstants.Type.KEYSYM,
        InputConstants.KEY_LALT, // Default key: Left ALT
        CATEGORY
    );

    /**
     * Tighten bind keybinding - Tighten binds on looked-at target
     * Default: T key
     */
    public static final KeyMapping TIGHTEN_KEY = new KeyMapping(
        "key.tiedup.tighten",
        InputConstants.Type.KEYSYM,
        InputConstants.KEY_T, // Default key: T
        CATEGORY
    );

    /** Track last sent state to avoid spamming packets */
    private static boolean lastForceSeatState = false;

    /**
     * Check if Force Seat key is currently pressed.
     */
    public static boolean isForceSeatPressed() {
        return FORCE_SEAT_KEY.isDown();
    }

    /**
     * Register keybindings.
     * Called during mod initialization (MOD bus).
     *
     * @param event The registration event
     */
    public static void register(RegisterKeyMappingsEvent event) {
        event.register(STRUGGLE_KEY);
        event.register(ADJUSTMENT_KEY);
        event.register(INVENTORY_KEY);
        event.register(SLAVE_MANAGEMENT_KEY);
        event.register(BOUNTY_KEY);
        event.register(FORCE_SEAT_KEY);
        event.register(TIGHTEN_KEY);
        TiedUpMod.LOGGER.info("Registered {} keybindings", 7);
    }

    // ==================== STRUGGLE MINI-GAME (uses vanilla movement keys) ====================

    /**
     * Get the vanilla movement keybind for a given direction index.
     * Uses Minecraft's movement keys so AZERTY/QWERTY is already configured.
     * @param index 0=FORWARD, 1=LEFT, 2=BACK, 3=RIGHT
     * @return The keybind or null if invalid index
     */
    public static KeyMapping getStruggleDirectionKey(int index) {
        Minecraft mc = Minecraft.getInstance();
        if (mc.options == null) return null;
        return switch (index) {
            case 0 -> mc.options.keyUp; // Forward (W/Z)
            case 1 -> mc.options.keyLeft; // Strafe Left (A/Q)
            case 2 -> mc.options.keyDown; // Back (S)
            case 3 -> mc.options.keyRight; // Strafe Right (D)
            default -> null;
        };
    }

    /**
     * Check if a keycode matches any vanilla movement keybind.
     * @param keyCode The GLFW key code
     * @return The direction index (0-3) or -1 if not a movement key
     */
    public static int getStruggleDirectionFromKeyCode(int keyCode) {
        Minecraft mc = Minecraft.getInstance();
        if (mc.options == null) return -1;
        if (mc.options.keyUp.matches(keyCode, 0)) return 0;
        if (mc.options.keyLeft.matches(keyCode, 0)) return 1;
        if (mc.options.keyDown.matches(keyCode, 0)) return 2;
        if (mc.options.keyRight.matches(keyCode, 0)) return 3;
        return -1;
    }

    /**
     * Get the display name of a vanilla movement key.
     * Shows the actual bound key (W for QWERTY, Z for AZERTY, etc.)
     * @param index 0=FORWARD, 1=LEFT, 2=BACK, 3=RIGHT
     * @return The key's display name
     */
    public static String getStruggleDirectionKeyName(int index) {
        KeyMapping key = getStruggleDirectionKey(index);
        if (key == null) return "?";
        return key.getTranslatedKeyMessage().getString().toUpperCase();
    }

    /**
     * Handle key presses on client tick.
     * Called every client tick (FORGE bus).
     *
     * @param event The tick event
     */
    @SubscribeEvent
    public static void onClientTick(TickEvent.ClientTickEvent event) {
        // Only run at end of tick
        if (event.phase != TickEvent.Phase.END) {
            return;
        }

        Minecraft mc = Minecraft.getInstance();
        if (mc.player == null || mc.level == null) {
            return;
        }

        // Sync Force Seat keybind state to server (only send on change)
        boolean currentForceSeatState = isForceSeatPressed();
        if (currentForceSeatState != lastForceSeatState) {
            lastForceSeatState = currentForceSeatState;
            ModNetwork.sendToServer(
                new PacketForceSeatModifier(currentForceSeatState)
            );
        }

        // Check struggle key - Flow based on bind/accessories
        while (STRUGGLE_KEY.consumeClick()) {
            handleStruggleKey();
        }

        // Check adjustment screen key
        while (ADJUSTMENT_KEY.consumeClick()) {
            // Only open if not already in a screen and player has adjustable items
            if (mc.screen == null && AdjustmentScreen.canOpen()) {
                mc.setScreen(new AdjustmentScreen());
                TiedUpMod.LOGGER.debug(
                    "[CLIENT] Adjustment key pressed - opening screen"
                );
            }
        }

        // Check bondage inventory key - opens UnifiedBondageScreen in SELF or MASTER mode
        while (INVENTORY_KEY.consumeClick()) {
            if (mc.screen == null) {
                LivingEntity masterTarget = findOwnedCollarTarget(mc.player);
                if (masterTarget != null) {
                    mc.setScreen(new UnifiedBondageScreen(masterTarget));
                } else {
                    mc.setScreen(new UnifiedBondageScreen());
                }
            }
        }

        // SLAVE_MANAGEMENT_KEY: now handled by [J] with master mode detection (see above)
        while (SLAVE_MANAGEMENT_KEY.consumeClick()) {
            // consumed but no-op — kept registered to avoid key conflict during transition
        }

        // Check bounty list key
        while (BOUNTY_KEY.consumeClick()) {
            // Request bounty list from server (server will open the screen)
            if (mc.screen == null) {
                ModNetwork.sendToServer(new PacketRequestBounties());
                TiedUpMod.LOGGER.debug(
                    "[CLIENT] Bounty key pressed - requesting bounty list"
                );
            }
        }

        // Check tighten key
        while (TIGHTEN_KEY.consumeClick()) {
            // Send tighten packet to server (server finds target)
            if (mc.screen == null) {
                ModNetwork.sendToServer(new PacketTighten());
                TiedUpMod.LOGGER.debug(
                    "[CLIENT] Tighten key pressed - sending tighten request"
                );
            }
        }
    }

    /**
     *
     * Flow:
     * 1. If bind equipped: Send PacketStruggle to server (struggle against bind)
     * 2. If no bind: Check for locked accessories
     *    - If locked accessories exist: Open StruggleChoiceScreen
     *    - If no locked accessories: Show "Nothing to struggle" message
     */
    private static void handleStruggleKey() {
        Minecraft mc = Minecraft.getInstance();
        Player player = mc.player;
        if (player == null || mc.screen != null) {
            return;
        }

        // V2 path: check if player has V2 equipment to struggle against
        if (
            com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.hasAnyEquipment(
                player
            )
        ) {
            handleV2Struggle(player);
            return;
        }

        PlayerBindState state = PlayerBindState.getInstance(player);
        if (state == null) {
            return;
        }

        // Check if player has bind equipped
        if (state.isTiedUp()) {
            // Has bind - struggle against it
            if (SettingsAccessor.isStruggleMiniGameEnabled()) {
                // New: Start struggle mini-game
                ModNetwork.sendToServer(
                    new PacketV2StruggleStart(BodyRegionV2.ARMS)
                );
                TiedUpMod.LOGGER.debug(
                    "[CLIENT] Struggle key pressed - starting V2 struggle mini-game"
                );
            } else {
                // Legacy: Probability-based struggle
                ModNetwork.sendToServer(new PacketStruggle());
                TiedUpMod.LOGGER.debug(
                    "[CLIENT] Struggle key pressed - legacy struggle against bind"
                );
            }
            return;
        }

        // No bind - check for locked accessories
        boolean hasLockedAccessories = hasAnyLockedAccessory(player);

        if (hasLockedAccessories) {
            // Open UnifiedBondageScreen in self mode
            mc.setScreen(new UnifiedBondageScreen());
            TiedUpMod.LOGGER.debug(
                "[CLIENT] Struggle key pressed - opening unified bondage screen"
            );
        } else {
            // No locked accessories - show message
            player.displayClientMessage(
                Component.translatable("tiedup.struggle.nothing").withStyle(
                    ChatFormatting.GRAY
                ),
                true
            );
            TiedUpMod.LOGGER.debug(
                "[CLIENT] Struggle key pressed - nothing to struggle"
            );
        }
    }

    /**
     * Handle struggle key for V2 equipment.
     * Auto-targets the highest posePriority item.
     */
    private static void handleV2Struggle(Player player) {
        java.util.Map<com.tiedup.remake.v2.BodyRegionV2, ItemStack> equipped =
            com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getAllEquipped(
                player
            );

        if (equipped.isEmpty()) return;

        // Auto-target: find highest posePriority item
        com.tiedup.remake.v2.BodyRegionV2 bestRegion = null;
        int bestPriority = Integer.MIN_VALUE;

        for (java.util.Map.Entry<
            com.tiedup.remake.v2.BodyRegionV2,
            ItemStack
        > entry : equipped.entrySet()) {
            ItemStack stack = entry.getValue();
            if (
                stack.getItem() instanceof
                    com.tiedup.remake.v2.bondage.IV2BondageItem item
            ) {
                if (item.getPosePriority(stack) > bestPriority) {
                    bestPriority = item.getPosePriority(stack);
                    bestRegion = entry.getKey();
                }
            }
        }

        if (bestRegion != null) {
            ModNetwork.sendToServer(
                new com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart(
                    bestRegion
                )
            );
            TiedUpMod.LOGGER.debug(
                "[CLIENT] V2 Struggle key pressed - targeting region {}",
                bestRegion.name()
            );
        }
    }

    /**
     * Check the crosshair entity: if it is a LivingEntity wearing a collar owned by the player,
     * return it as the MASTER mode target. Returns null if no valid target.
     */
    @Nullable
    private static LivingEntity findOwnedCollarTarget(Player player) {
        if (player == null) return null;
        Minecraft mc = Minecraft.getInstance();
        net.minecraft.world.entity.Entity crosshair = mc.crosshairPickEntity;
        if (crosshair instanceof LivingEntity living) {
            return checkCollarOwnership(living, player) ? living : null;
        }
        return null;
    }

    /**
     * Returns true if the given entity has a collar in the NECK region that lists the player as an owner.
     */
    private static boolean checkCollarOwnership(
        LivingEntity target,
        Player player
    ) {
        ItemStack collarStack =
            com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getInRegion(
                target,
                BodyRegionV2.NECK
            );
        if (!collarStack.isEmpty() && CollarHelper.isCollar(collarStack)) {
            return CollarHelper.isOwner(collarStack, player);
        }
        return false;
    }

    /**
     * Check if player has any locked accessories.
     */
    private static boolean hasAnyLockedAccessory(Player player) {
        BodyRegionV2[] accessoryRegions = {
            BodyRegionV2.MOUTH,
            BodyRegionV2.EYES,
            BodyRegionV2.EARS,
            BodyRegionV2.NECK,
            BodyRegionV2.TORSO,
            BodyRegionV2.HANDS,
        };

        for (BodyRegionV2 region : accessoryRegions) {
            ItemStack stack = V2EquipmentHelper.getInRegion(player, region);
            if (
                !stack.isEmpty() &&
                stack.getItem() instanceof ILockable lockable
            ) {
                if (lockable.isLocked(stack)) {
                    return true;
                }
            }
        }

        return false;
    }
}
