package com.tiedup.remake.client.events;

import com.mojang.blaze3d.vertex.PoseStack;
import com.tiedup.remake.blocks.BlockMarker;
import com.tiedup.remake.blocks.entity.MarkerBlockEntity;
import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.cells.MarkerType;
import com.tiedup.remake.client.renderer.CellOutlineRenderer;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemAdminWand;
import java.util.UUID;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderLevelStageEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

/**
 * Event handler for rendering cell outlines when holding an admin wand.
 *
 * Phase: Kidnapper Revamp - Cell System
 *
 * Renders colored outlines around cell positions when:
 * - Player is holding an Admin Wand
 * - A cell is currently selected in the wand
 *
 * The outlines help builders visualize which blocks are part of the cell.
 *
 * Network sync: On dedicated servers, cell data is synced via PacketSyncCellData.
 * On integrated servers, direct access to server data is used as a fallback.
 */
@Mod.EventBusSubscriber(
    modid = TiedUpMod.MOD_ID,
    bus = Mod.EventBusSubscriber.Bus.FORGE,
    value = Dist.CLIENT
)
public class CellHighlightHandler {

    // Client-side cache of cell data (synced from server via PacketSyncCellData)
    private static final java.util.Map<UUID, CellDataV2> syncedCells =
        new java.util.concurrent.ConcurrentHashMap<>();

    // Legacy single-cell cache for backward compatibility
    private static CellDataV2 cachedCellData = null;
    private static UUID cachedCellId = null;

    /**
     * Render cell outlines after translucent blocks.
     */
    @SubscribeEvent
    public static void onRenderLevelStage(RenderLevelStageEvent event) {
        // Only render after translucent stage (so outlines appear on top)
        if (
            event.getStage() !=
            RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS
        ) {
            return;
        }

        Minecraft mc = Minecraft.getInstance();
        LocalPlayer player = mc.player;

        if (player == null) return;

        // Check if player is holding an Admin Wand
        ItemStack mainHand = player.getMainHandItem();
        ItemStack offHand = player.getOffhandItem();

        boolean holdingAdminWand =
            mainHand.getItem() instanceof ItemAdminWand ||
            offHand.getItem() instanceof ItemAdminWand;

        if (!holdingAdminWand) {
            cachedCellData = null;
            cachedCellId = null;
            return;
        }

        PoseStack poseStack = event.getPoseStack();
        Camera camera = event.getCamera();

        // If holding Admin Wand, render nearby structure markers and preview
        renderNearbyStructureMarkers(poseStack, camera, player);
        renderAdminWandPreview(poseStack, camera, player, mainHand, offHand);
    }

    /**
     * Render a preview outline showing where the Admin Wand will place a marker.
     */
    private static void renderAdminWandPreview(
        PoseStack poseStack,
        Camera camera,
        LocalPlayer player,
        ItemStack mainHand,
        ItemStack offHand
    ) {
        // Get the block the player is looking at
        net.minecraft.world.phys.HitResult hitResult =
            Minecraft.getInstance().hitResult;
        if (
            hitResult == null ||
            hitResult.getType() != net.minecraft.world.phys.HitResult.Type.BLOCK
        ) {
            return;
        }

        net.minecraft.world.phys.BlockHitResult blockHit =
            (net.minecraft.world.phys.BlockHitResult) hitResult;
        BlockPos targetPos = blockHit.getBlockPos().above(); // Marker goes above the clicked block

        // Get the current marker type from the wand
        MarkerType type;
        if (mainHand.getItem() instanceof ItemAdminWand) {
            type = ItemAdminWand.getCurrentType(mainHand);
        } else {
            type = ItemAdminWand.getCurrentType(offHand);
        }

        Vec3 cameraPos = camera.getPosition();
        float[] color = CellOutlineRenderer.getColorForType(type);

        // Make preview semi-transparent and pulsing
        float alpha =
            0.5f + 0.3f * (float) Math.sin(System.currentTimeMillis() / 200.0);
        float[] previewColor = { color[0], color[1], color[2], alpha };

        // Setup rendering (depth test off so preview shows through blocks)
        com.mojang.blaze3d.systems.RenderSystem.enableBlend();
        com.mojang.blaze3d.systems.RenderSystem.defaultBlendFunc();
        com.mojang.blaze3d.systems.RenderSystem.disableDepthTest();
        com.mojang.blaze3d.systems.RenderSystem.depthMask(false);
        com.mojang.blaze3d.systems.RenderSystem.setShader(
            net.minecraft.client.renderer.GameRenderer::getPositionColorShader
        );

        CellOutlineRenderer.renderFilledBlock(
            poseStack,
            targetPos,
            cameraPos,
            previewColor
        );

        com.mojang.blaze3d.systems.RenderSystem.depthMask(true);
        com.mojang.blaze3d.systems.RenderSystem.enableDepthTest();
        com.mojang.blaze3d.systems.RenderSystem.disableBlend();
    }

    /**
     * Render outlines for nearby structure markers (markers without cell IDs).
     */
    private static void renderNearbyStructureMarkers(
        PoseStack poseStack,
        Camera camera,
        LocalPlayer player
    ) {
        Level level = player.level();
        BlockPos playerPos = player.blockPosition();
        Vec3 cameraPos = camera.getPosition();

        // Collect markers first to check if we need to render anything
        java.util.List<
            java.util.Map.Entry<BlockPos, MarkerType>
        > markersToRender = new java.util.ArrayList<>();

        // Scan in a 32-block radius for structure markers
        int radius = 32;

        for (int x = -radius; x <= radius; x++) {
            for (int y = -radius / 2; y <= radius / 2; y++) {
                for (int z = -radius; z <= radius; z++) {
                    BlockPos pos = playerPos.offset(x, y, z);

                    if (
                        level.getBlockState(pos).getBlock() instanceof
                            BlockMarker
                    ) {
                        BlockEntity be = level.getBlockEntity(pos);
                        if (be instanceof MarkerBlockEntity marker) {
                            // Only render structure markers (no cell ID)
                            if (marker.getCellId() == null) {
                                markersToRender.add(
                                    java.util.Map.entry(
                                        pos,
                                        marker.getMarkerType()
                                    )
                                );
                            }
                        }
                    }
                }
            }
        }

        // Only setup rendering if we have markers to render
        if (!markersToRender.isEmpty()) {
            // Setup rendering state (depth test off so markers show through blocks)
            com.mojang.blaze3d.systems.RenderSystem.enableBlend();
            com.mojang.blaze3d.systems.RenderSystem.defaultBlendFunc();
            com.mojang.blaze3d.systems.RenderSystem.disableDepthTest();
            com.mojang.blaze3d.systems.RenderSystem.depthMask(false);
            com.mojang.blaze3d.systems.RenderSystem.setShader(
                net.minecraft.client.renderer
                    .GameRenderer::getPositionColorShader
            );

            for (var entry : markersToRender) {
                BlockPos pos = entry.getKey();
                MarkerType type = entry.getValue();
                float[] baseColor = CellOutlineRenderer.getColorForType(type);
                // Semi-transparent fill
                float[] fillColor = {
                    baseColor[0],
                    baseColor[1],
                    baseColor[2],
                    0.4f,
                };
                CellOutlineRenderer.renderFilledBlock(
                    poseStack,
                    pos,
                    cameraPos,
                    fillColor
                );
            }

            // Restore rendering state
            com.mojang.blaze3d.systems.RenderSystem.depthMask(true);
            com.mojang.blaze3d.systems.RenderSystem.enableDepthTest();
            com.mojang.blaze3d.systems.RenderSystem.disableBlend();
        }
    }

    /**
     * Get cell data on client side.
     * First checks the network-synced cache, then falls back to integrated server access.
     *
     * @param cellId The cell UUID to look up
     * @return CellDataV2 if found, null otherwise
     */
    private static CellDataV2 getCellDataClient(UUID cellId) {
        if (cellId == null) return null;

        // Priority 1: Check network-synced cache (works on dedicated servers)
        CellDataV2 synced = syncedCells.get(cellId);
        if (synced != null) {
            return synced;
        }

        // Priority 2: Check legacy single-cell cache
        if (cellId.equals(cachedCellId) && cachedCellData != null) {
            return cachedCellData;
        }

        // Priority 3: On integrated server, access server level directly (fallback)
        Minecraft mc = Minecraft.getInstance();
        if (mc.getSingleplayerServer() != null) {
            ServerLevel serverLevel = mc.getSingleplayerServer().overworld();
            if (serverLevel != null) {
                CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
                CellDataV2 cell = registry.getCell(cellId);
                if (cell != null) {
                    // Cache for future use
                    cachedCellId = cellId;
                    cachedCellData = cell;
                    return cell;
                }
            }
        }

        // Not found - on dedicated server, packet hasn't arrived yet
        return null;
    }

    /**
     * Update cached cell data (called from network sync - PacketSyncCellData).
     * Stores in both the synced map and legacy cache for compatibility.
     *
     * @param cell The cell data received from server
     */
    public static void updateCachedCell(CellDataV2 cell) {
        if (cell != null) {
            // Store in synced map
            syncedCells.put(cell.getId(), cell);

            // Also update legacy cache
            cachedCellId = cell.getId();
            cachedCellData = cell;
        }
    }

    /**
     * Remove a cell from the cache (e.g., when cell is deleted).
     *
     * @param cellId The cell UUID to remove
     */
    public static void removeCachedCell(UUID cellId) {
        if (cellId != null) {
            syncedCells.remove(cellId);
            if (cellId.equals(cachedCellId)) {
                cachedCellId = null;
                cachedCellData = null;
            }
        }
    }

    /**
     * Clear all cached cell data.
     * Called when disconnecting from server or on dimension change.
     */
    public static void clearCache() {
        syncedCells.clear();
        cachedCellId = null;
        cachedCellData = null;
    }
}
