package net.xevianlight.aphelion.util;

import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.*;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.phys.AABB;
import net.xevianlight.aphelion.block.custom.base.IRocketEnergyUpgrade;
import net.xevianlight.aphelion.block.custom.base.IRocketFluidUpgrade;
import net.xevianlight.aphelion.block.custom.base.IRocketFuelUpgrade;
import net.xevianlight.aphelion.block.custom.base.IRocketInventoryUpgrade;
import org.apache.commons.lang3.NotImplementedException;

import java.util.ArrayList;
import java.util.List;

public final class RocketStructure {


    private final List<BlockState> palette = new ArrayList<>();
    private final IntList packedPositions = new IntArrayList();
    private final IntList paletteIndices = new IntArrayList();

    private final IntList seatOffsets = new IntArrayList();

    public RocketStructure(Builder builder) {
        builder.build(this);
    }

    @FunctionalInterface
    public interface Builder {
        void build(RocketStructure s);
    }

    public int size() { return packedPositions.size(); }

    public void clear() {
        palette.clear();
        packedPositions.clear();
        paletteIndices.clear();
        seatOffsets.clear();
    }

    public void add(int x, int y, int z, BlockState state) {
        if (state == Blocks.AIR.defaultBlockState()) return;
        int idx = palette.indexOf(state);
        if (idx < 0) {
            palette.add(state);
            idx = palette.size() - 1;
        }

        packedPositions.add(packPos(x, y, z));
        paletteIndices.add(idx); // ← REQUIRED
    }


    public BlockState stateAt(int i) { return palette.get(paletteIndices.getInt(i)); }
    public int packedPosAt(int i) { return packedPositions.getInt(i); }

    public CompoundTag save() {
        CompoundTag tag = new CompoundTag();

        // palette
        ListTag pal = new ListTag();
        for (BlockState st : palette) {
            pal.add(BlockState.CODEC.encodeStart(NbtOps.INSTANCE, st)
                    .getOrThrow());
        }
        tag.put("palette", pal);

        // blocks
        IntArrayTag posArr = new IntArrayTag(packedPositions.toIntArray());
        IntArrayTag idxArr = new IntArrayTag(paletteIndices.toIntArray());
        tag.put("pos", posArr);
        tag.put("idx", idxArr);

        tag.put("seats", new IntArrayTag(seatOffsets.toIntArray()));

        return tag;
    }

    public void load(CompoundTag tag) {
        clear();

        // palette
        ListTag pal = tag.getList("palette", Tag.TAG_COMPOUND);
        for (int i = 0; i < pal.size(); i++) {
            Tag stTag = pal.get(i);
            BlockState st = BlockState.CODEC.parse(NbtOps.INSTANCE, stTag)
                    .getOrThrow();
            palette.add(st);
        }

        // blocks
        int[] pos = tag.getIntArray("pos");
        int[] idx = tag.getIntArray("idx");

        for (int i = 0; i < pos.length; i++) {
            packedPositions.add(pos[i]);
            paletteIndices.add(idx[i]);
        }

        if (tag.contains("seats", Tag.TAG_INT_ARRAY)) {
            int[] seats = tag.getIntArray("seats");
            for (int s : seats) seatOffsets.add(s);
        }
    }

    public static int packPos (int x, int y, int z) {
        return (x & 0xFF) | ((y & 0xFF) << 8) | ((z & 0xFF) << 16);
    }

    public static int unpackX (int p) { return (byte) (p & 0xFF); }
    public static int unpackY (int p) { return (byte) (( p >> 8) & 0xFF); }
    public static int unpackZ (int p) { return (byte) (( p >> 16) & 0xFF); }

    public record Extents(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
        public int sizeX() { return maxX - minX + 1; }
        public int sizeY() { return maxY - minY + 1; }
        public int sizeZ() { return maxZ - minZ + 1; }

        public AABB toLocalAABB() {
            return new AABB(minX - 0.5, minY, minZ - 0.5, maxX + 0.5, maxY + 1, maxZ + 0.5);
        }
    }

    public Extents computeExtents() {
        if (size() == 0) {
            return new Extents(0, 0, 0, 0, 0, 0);
        }

        int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, minZ = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE, maxZ = Integer.MIN_VALUE;

        for (int i = 0; i < size(); i++) {
            int p = packedPosAt(i);
            int x = unpackX(p);
            int y = unpackY(p);
            int z = unpackZ(p);

            if (x < minX) minX = x;
            if (y < minY) minY = y;
            if (z < minZ) minZ = z;

            if (x > maxX) maxX = x;
            if (y > maxY) maxY = y;
            if (z > maxZ) maxZ = z;
        }

        return new Extents(minX, minY, minZ, maxX, maxY, maxZ);
    }

    public static void clearCaptured(Level level, BlockPos origin, RocketStructure struct) {
        final int flags = Block.UPDATE_CLIENTS;

        // Pass 1: remove blocks which implement DOUBLE_BLOCK_HALF like doors to try and prevent duplication.
        for (int i = 0; i < struct.size(); i++) {
            int packed = struct.packedPosAt(i);
            BlockPos wp = origin.offset(
                    RocketStructure.unpackX(packed),
                    RocketStructure.unpackY(packed),
                    RocketStructure.unpackZ(packed)
            );

            BlockState st = level.getBlockState(wp);
            if (st.isAir()) continue;

            if (st.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)) {
                DoubleBlockHalf half = st.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF);
                BlockPos bottom = (half == DoubleBlockHalf.LOWER) ? wp : null;

                // Break the BOTTOM block  to stop potential dupes, as it seems that is the "master" block for doors.
                // If you set the top block to air, the bottom one breaks a moment later and drops.
                // If this doesn't work I declare it NOT MY FAULT!
                // DoubleBlockHalf blocks should have a way to delete the entire thing at once god damnit
                if (bottom != null && !level.getBlockState(bottom).isAir()) {
                    level.setBlock(bottom, Blocks.AIR.defaultBlockState(), flags);
                }
            }
        }

        // Pass 2: remove likely-attached blocks first. This should stop duplication of torches/buttons/whatever else may break due to its supporting block being broken
        for (int i = 0; i < struct.size(); i++) {
            int packed = struct.packedPosAt(i);
            BlockPos wp = origin.offset(unpackX(packed), unpackY(packed), unpackZ(packed));
            BlockState st = level.getBlockState(wp);
            if (st.isAir()) continue;

            // Heuristic: if it isn't a full collision cube, it's often "attached" (buttons, torches, etc.)
            if (!st.isCollisionShapeFullBlock(level, wp)) {
                level.setBlock(wp, Blocks.AIR.defaultBlockState(), flags);
            }
        }

        // Pass 3: remove the rest
        for (int i = 0; i < struct.size(); i++) {
            int packed = struct.packedPosAt(i);
            BlockPos wp = origin.offset(unpackX(packed), unpackY(packed), unpackZ(packed));
            if (!level.getBlockState(wp).isAir()) {
                level.setBlock(wp, Blocks.AIR.defaultBlockState(), flags);
            }
        }
    }

    public int seatCount() { return seatOffsets.size(); }
    public int packedSeatAt(int i) { return seatOffsets.getInt(i); }

    public void addSeatOffset(int dx, int dy, int dz) {
        seatOffsets.add(packPos(dx, dy, dz));
    }

    public static int calculateInventoryCapacity(RocketStructure structure) {
        int totalSlots = 0;
        for (int i = 0; i < structure.size(); i++) {
            BlockState st = structure.stateAt(i);
            Block block = st.getBlock();

            if (block instanceof IRocketInventoryUpgrade upgrade) {
                int slots = upgrade.getSlotCapacity();
                if (slots > 0) totalSlots += slots;
            }
        }
        return totalSlots;
    }

    public static int calculateFuelCapacity(RocketStructure structure) {
        int totalMB = 0;
        for (int i = 0; i < structure.size(); i++) {
            BlockState st = structure.stateAt(i);
            Block block = st.getBlock();

            if (block instanceof IRocketFuelUpgrade upgrade) {
                int mb = upgrade.getFuelCapacity();
                if (mb > 0) totalMB += mb;
            }
        }
        return totalMB;
    }

    public static int calculateFluidCapacity(RocketStructure structure) {
        int totalMB = 0;
        for (int i = 0; i < structure.size(); i++) {
            BlockState st = structure.stateAt(i);
            Block block = st.getBlock();

            if (block instanceof IRocketFluidUpgrade upgrade) {
                int mb = upgrade.getFluidCapacity();
                if (mb > 0) totalMB += mb;
            }
        }
        return totalMB;
    }

    public static int calculateEnergyCapacity(RocketStructure structure) {
        int totalFE = 0;
        for (int i = 0; i < structure.size(); i++) {
            BlockState st = structure.stateAt(i);
            Block block = st.getBlock();

            if (block instanceof IRocketEnergyUpgrade upgrade) {
                int fe = upgrade.getEnergyCapacity();
                if (fe > 0) totalFE += fe;
            }
        }
        return totalFE;
    }
}
