package com.trunksbomb.minetriad.blockentity;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import com.trunksbomb.minetriad.game.BoardCell;
import com.trunksbomb.minetriad.game.DuelSession;
import com.trunksbomb.minetriad.game.DuelSessionManager;
import com.trunksbomb.minetriad.game.MoveResult;
import com.trunksbomb.minetriad.registry.TriadBlockEntities;
import com.trunksbomb.minetriad.registry.TriadItems;

import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.network.chat.Component;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;

public class DuelTableBlockEntity extends BlockEntity {
    public static final int SLOT_COUNT = 9;
    public static final int OWNER_NONE = 0;
    public static final int OWNER_FIRST = 1;
    public static final int OWNER_SECOND = 2;
    private static final int PLACEMENT_DURATION = 10;
    private static final int CAPTURE_DURATION = 10;
    private static final int OVERVIEW_SWEEP_DURATION = 18;
    private static final int CLEAR_WAVE_DURATION = 10;

    private final NonNullList<ItemStack> boardCards = NonNullList.withSize(SLOT_COUNT, ItemStack.EMPTY);
    private final int[] ownerSlots = new int[SLOT_COUNT];
    private final int[] slotAges = new int[SLOT_COUNT];
    private final ArrayDeque<AnimationStep> pendingAnimations = new ArrayDeque<>();
    private UUID firstParticipantId;
    private UUID secondParticipantId;
    private AnimationType animationType = AnimationType.NONE;
    private int[] animationSlots = new int[0];
    private int animationTargetOwner = OWNER_NONE;
    private int animationAge;
    private int animationDuration;
    private boolean animationOwnerApplied;
    private TableAnimationType tableAnimationType = TableAnimationType.NONE;
    private int tableAnimationAge;
    private int tableAnimationDuration;

    public DuelTableBlockEntity(BlockPos pos, BlockState blockState) {
        super(TriadBlockEntities.DUEL_TABLE.get(), pos, blockState);
    }

    public ItemStack getCard(int slot) {
        return boardCards.get(slot);
    }

    public int ownerAt(int slot) {
        return ownerSlots[slot];
    }

    public int slotAge(int slot) {
        return slot >= 0 && slot < slotAges.length ? slotAges[slot] : 0;
    }

    public Optional<UUID> firstParticipantId() {
        return Optional.ofNullable(firstParticipantId);
    }

    public Optional<UUID> secondParticipantId() {
        return Optional.ofNullable(secondParticipantId);
    }

    public boolean hasActiveAnimation() {
        return animationType != AnimationType.NONE || tableAnimationType != TableAnimationType.NONE;
    }

    public String animationTypeName() {
        return animationType.name();
    }

    public boolean isPlacementAnimation() {
        return animationType == AnimationType.PLACEMENT;
    }

    public boolean isCaptureAnimation() {
        return animationType == AnimationType.CAPTURE;
    }

    public int animationSlot() {
        return animationSlots.length > 0 ? animationSlots[0] : -1;
    }

    public boolean isAnimatingSlot(int slot) {
        for (int animatedSlot : animationSlots) {
            if (animatedSlot == slot) {
                return true;
            }
        }
        return false;
    }

    public float animationProgress(float partialTick) {
        if (!hasActiveAnimation() || animationDuration <= 0) {
            return 0.0F;
        }
        return Math.clamp((animationAge + partialTick) / animationDuration, 0.0F, 1.0F);
    }

    public boolean hasOverviewSweepAnimation() {
        return tableAnimationType == TableAnimationType.OVERVIEW_SWEEP;
    }

    public boolean hasClearWaveAnimation() {
        return tableAnimationType == TableAnimationType.CLEARING;
    }

    public float tableAnimationProgress(float partialTick) {
        if (tableAnimationType == TableAnimationType.NONE || tableAnimationDuration <= 0) {
            return 0.0F;
        }
        return Math.clamp((tableAnimationAge + partialTick) / tableAnimationDuration, 0.0F, 1.0F);
    }

    public boolean animationOwnerApplied() {
        return animationOwnerApplied;
    }

    public void setParticipants(UUID firstParticipantId, UUID secondParticipantId) {
        this.firstParticipantId = firstParticipantId;
        this.secondParticipantId = secondParticipantId;
        sync();
    }

    public boolean setCard(int slot, ItemStack stack, int owner) {
        if (slot < 0 || slot >= boardCards.size() || !boardCards.get(slot).isEmpty() || !stack.is(TriadItems.TRIAD_CARD.get())) {
            return false;
        }

        boardCards.set(slot, stack.copyWithCount(1));
        ownerSlots[slot] = owner;
        slotAges[slot] = 0;
        sync();
        return true;
    }

    public void setOwner(int slot, int owner) {
        if (slot < 0 || slot >= ownerSlots.length || boardCards.get(slot).isEmpty()) {
            return;
        }
        ownerSlots[slot] = owner;
        sync();
    }

    public void clearBoard() {
        for (int index = 0; index < boardCards.size(); index++) {
            boardCards.set(index, ItemStack.EMPTY);
            ownerSlots[index] = OWNER_NONE;
            slotAges[index] = 0;
        }
        pendingAnimations.clear();
        clearAnimationState();
        firstParticipantId = null;
        secondParticipantId = null;
        sync();
    }

    public void dropBoardCards(Level level, BlockPos pos) {
        for (int slot = 0; slot < boardCards.size(); slot++) {
            ItemStack stack = boardCards.get(slot);
            if (!stack.isEmpty()) {
                Block.popResource(level, pos, stack);
                boardCards.set(slot, ItemStack.EMPTY);
                ownerSlots[slot] = OWNER_NONE;
                slotAges[slot] = 0;
            }
        }
        pendingAnimations.clear();
        clearAnimationState();
        firstParticipantId = null;
        secondParticipantId = null;
        sync();
    }

    public void startMoveAnimation(MoveResult moveResult, int owner) {
        pendingAnimations.clear();
        clearAnimationState();
        clearTableAnimationState();
        if (moveResult.playedCell() == null) {
            return;
        }

        pendingAnimations.addLast(new AnimationStep(AnimationType.PLACEMENT, new int[] {moveResult.playedCell().index()}, owner));
        if (!moveResult.capturedCells().isEmpty()) {
            int[] captureSlots = moveResult.capturedCells().stream()
                    .mapToInt(BoardCell::index)
                    .toArray();
            pendingAnimations.addLast(new AnimationStep(AnimationType.CAPTURE, captureSlots, owner));
        }
        advanceAnimationStep();
    }

    public void startOverviewSweepAnimation() {
        clearAnimationState();
        tableAnimationType = TableAnimationType.OVERVIEW_SWEEP;
        tableAnimationAge = 0;
        tableAnimationDuration = OVERVIEW_SWEEP_DURATION;
        sync();
    }

    public void startClearWaveAnimation() {
        clearAnimationState();
        tableAnimationType = TableAnimationType.CLEARING;
        tableAnimationAge = 0;
        tableAnimationDuration = CLEAR_WAVE_DURATION;
        sync();
    }

    public static void tick(Level level, BlockPos pos, BlockState state, DuelTableBlockEntity blockEntity) {
        if (blockEntity.hasActiveAnimation()) {
            if (blockEntity.animationType != AnimationType.NONE) {
                blockEntity.animationAge++;
                if (!level.isClientSide
                        && blockEntity.animationType == AnimationType.CAPTURE
                        && !blockEntity.animationOwnerApplied
                        && blockEntity.animationAge >= Math.max(1, blockEntity.animationDuration / 2)) {
                    for (int slot : blockEntity.animationSlots) {
                        blockEntity.setOwner(slot, blockEntity.animationTargetOwner);
                    }
                    blockEntity.animationOwnerApplied = true;
                    blockEntity.sync();
                }

                if (blockEntity.animationAge >= blockEntity.animationDuration) {
                    if (!level.isClientSide) {
                        blockEntity.advanceAnimationStep();
                        if (blockEntity.animationType == AnimationType.NONE && blockEntity.tableAnimationType == TableAnimationType.NONE) {
                            blockEntity.resolvePostAnimation(level, pos);
                        }
                    } else {
                        blockEntity.clearAnimationState();
                    }
                }
            }

            if (blockEntity.tableAnimationType != TableAnimationType.NONE) {
                blockEntity.tableAnimationAge++;
                if (!level.isClientSide
                        && blockEntity.tableAnimationType == TableAnimationType.CLEARING) {
                    blockEntity.updateClearWaveState(level, pos);
                }
                if (blockEntity.tableAnimationAge >= blockEntity.tableAnimationDuration) {
                    if (!level.isClientSide) {
                        blockEntity.clearTableAnimationState();
                        blockEntity.resolvePostAnimation(level, pos);
                    } else {
                        blockEntity.clearTableAnimationState();
                    }
                }
            }
        }

        for (int slot = 0; slot < SLOT_COUNT; slot++) {
            if (!blockEntity.boardCards.get(slot).isEmpty()) {
                blockEntity.slotAges[slot]++;
            } else {
                blockEntity.slotAges[slot] = 0;
            }
        }
    }

    @Override
    protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
        super.saveAdditional(tag, registries);
        ContainerHelper.saveAllItems(tag, boardCards, registries);
        tag.putIntArray("OwnerSlots", ownerSlots);
        tag.putIntArray("SlotAges", slotAges);
        saveAnimation(tag);
        saveTableAnimation(tag);
        if (firstParticipantId != null) {
            tag.putUUID("FirstParticipantId", firstParticipantId);
        }
        if (secondParticipantId != null) {
            tag.putUUID("SecondParticipantId", secondParticipantId);
        }
    }

    @Override
    protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
        super.loadAdditional(tag, registries);
        clearBoardContents();
        ContainerHelper.loadAllItems(tag, boardCards, registries);
        loadOwnerData(tag);
        loadAnimation(tag);
        loadTableAnimation(tag);
    }

    @Override
    public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
        CompoundTag tag = super.getUpdateTag(registries);
        ContainerHelper.saveAllItems(tag, boardCards, registries);
        tag.putIntArray("OwnerSlots", ownerSlots);
        tag.putIntArray("SlotAges", slotAges);
        saveAnimation(tag);
        saveTableAnimation(tag);
        if (firstParticipantId != null) {
            tag.putUUID("FirstParticipantId", firstParticipantId);
        }
        if (secondParticipantId != null) {
            tag.putUUID("SecondParticipantId", secondParticipantId);
        }
        return tag;
    }

    @Override
    public ClientboundBlockEntityDataPacket getUpdatePacket() {
        return ClientboundBlockEntityDataPacket.create(this);
    }

    @Override
    public void onDataPacket(Connection connection, ClientboundBlockEntityDataPacket packet, HolderLookup.Provider registries) {
        CompoundTag tag = packet.getTag();
        if (tag != null) {
            clearBoardContents();
            ContainerHelper.loadAllItems(tag, boardCards, registries);
            loadOwnerData(tag);
            loadAnimation(tag);
            loadTableAnimation(tag);
        }
    }

    private void clearBoardContents() {
        for (int index = 0; index < boardCards.size(); index++) {
            boardCards.set(index, ItemStack.EMPTY);
            ownerSlots[index] = OWNER_NONE;
            slotAges[index] = 0;
        }
        pendingAnimations.clear();
        clearAnimationState();
        firstParticipantId = null;
        secondParticipantId = null;
    }

    private void loadOwnerData(CompoundTag tag) {
        int[] loadedOwners = tag.getIntArray("OwnerSlots");
        for (int index = 0; index < ownerSlots.length; index++) {
            ownerSlots[index] = index < loadedOwners.length ? loadedOwners[index] : OWNER_NONE;
        }
        int[] loadedAges = tag.getIntArray("SlotAges");
        for (int index = 0; index < slotAges.length; index++) {
            slotAges[index] = index < loadedAges.length ? loadedAges[index] : 0;
        }
        firstParticipantId = tag.hasUUID("FirstParticipantId") ? tag.getUUID("FirstParticipantId") : null;
        secondParticipantId = tag.hasUUID("SecondParticipantId") ? tag.getUUID("SecondParticipantId") : null;
    }

    private void saveAnimation(CompoundTag tag) {
        tag.putString("AnimationType", animationType.name());
        tag.putIntArray("AnimationSlots", animationSlots);
        tag.putInt("AnimationTargetOwner", animationTargetOwner);
        tag.putInt("AnimationAge", animationAge);
        tag.putInt("AnimationDuration", animationDuration);
        tag.putBoolean("AnimationOwnerApplied", animationOwnerApplied);
    }

    private void loadAnimation(CompoundTag tag) {
        animationType = AnimationType.valueOf(tag.getString("AnimationType").isEmpty() ? AnimationType.NONE.name() : tag.getString("AnimationType"));
        animationSlots = tag.getIntArray("AnimationSlots");
        animationTargetOwner = tag.getInt("AnimationTargetOwner");
        animationAge = tag.getInt("AnimationAge");
        animationDuration = tag.getInt("AnimationDuration");
        animationOwnerApplied = tag.getBoolean("AnimationOwnerApplied");
    }

    private void saveTableAnimation(CompoundTag tag) {
        tag.putString("TableAnimationType", tableAnimationType.name());
        tag.putInt("TableAnimationAge", tableAnimationAge);
        tag.putInt("TableAnimationDuration", tableAnimationDuration);
    }

    private void loadTableAnimation(CompoundTag tag) {
        tableAnimationType = TableAnimationType.valueOf(tag.getString("TableAnimationType").isEmpty() ? TableAnimationType.NONE.name() : tag.getString("TableAnimationType"));
        tableAnimationAge = tag.getInt("TableAnimationAge");
        tableAnimationDuration = tag.getInt("TableAnimationDuration");
    }

    private void advanceAnimationStep() {
        AnimationStep nextStep = pendingAnimations.pollFirst();
        if (nextStep == null) {
            clearAnimationState();
            sync();
            return;
        }

        animationType = nextStep.type();
        animationSlots = nextStep.slots();
        animationTargetOwner = nextStep.targetOwner();
        animationAge = 0;
        animationDuration = nextStep.type() == AnimationType.PLACEMENT ? PLACEMENT_DURATION : CAPTURE_DURATION;
        animationOwnerApplied = nextStep.type() != AnimationType.CAPTURE;
        sync();
    }

    private void clearAnimationState() {
        animationType = AnimationType.NONE;
        animationSlots = new int[0];
        animationTargetOwner = OWNER_NONE;
        animationAge = 0;
        animationDuration = 0;
        animationOwnerApplied = false;
    }

    private void clearTableAnimationState() {
        tableAnimationType = TableAnimationType.NONE;
        tableAnimationAge = 0;
        tableAnimationDuration = 0;
    }

    private void resolvePostAnimation(Level level, BlockPos pos) {
        DuelSession session = DuelSessionManager.getAt(pos);
        if (session == null) {
            return;
        }

        if (session.isClosingAnimation()) {
            clearBoard();
            var player = level.getServer() == null ? null : level.getServer().getPlayerList().getPlayer(session.playerParticipantId());
            if (player != null) {
                DuelSessionManager.end(player, true);
                player.displayClientMessage(Component.literal("Duel finished. All played cards have been returned.").withStyle(ChatFormatting.GREEN), false);
            }
            return;
        }

        if (session.isAwaitingPlayerAnimation()) {
            DuelTableAnimationResolver.handleAfterPlayerAnimation(level, pos, this, session);
            return;
        }

        if (session.isAwaitingOpponentAnimation()) {
            DuelTableAnimationResolver.handleAfterOpponentAnimation(level, pos, this, session);
        }
    }

    private void updateClearWaveState(Level level, BlockPos pos) {
        for (int slot = 0; slot < SLOT_COUNT; slot++) {
            if (boardCards.get(slot).isEmpty()) {
                continue;
            }
            if (clearWaveSlotProgress(slot, 0.0F) >= 1.0F) {
                boardCards.set(slot, ItemStack.EMPTY);
                ownerSlots[slot] = OWNER_NONE;
                slotAges[slot] = 0;
            }
        }
        sync();
    }

    public float overviewSweepSlotProgress(int slot, float partialTick) {
        return waveSlotProgress(slot, partialTick, SLOT_COUNT, tableAnimationAge, tableAnimationDuration);
    }

    public float clearWaveSlotProgress(int slot, float partialTick) {
        return waveSlotProgress(slot, partialTick, SLOT_COUNT, tableAnimationAge, tableAnimationDuration);
    }

    private float waveSlotProgress(int slot, float partialTick, int slotCount, int age, int duration) {
        if (slot < 0 || slot >= slotCount || duration <= 0) {
            return 0.0F;
        }
        float global = Math.clamp((age + partialTick) / duration, 0.0F, 1.0F);
        float delay = slot / (float) slotCount;
        float window = 0.28F;
        return Math.clamp((global - delay) / window, 0.0F, 1.0F);
    }

    private void sync() {
        setChanged();
        if (level != null) {
            level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), Block.UPDATE_ALL);
        }
    }

    private enum AnimationType {
        NONE,
        PLACEMENT,
        CAPTURE
    }

    private enum TableAnimationType {
        NONE,
        OVERVIEW_SWEEP,
        CLEARING
    }

    private record AnimationStep(AnimationType type, int[] slots, int targetOwner) {
    }
}
