package com.tiedup.remake.v2.blocks;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.V2BlockEntities;
import com.tiedup.remake.v2.V2Blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
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.EntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;

/**
 * Pet Cage Block (Master) - Multi-block cage (3x3x2).
 *
 * <p>Collision is computed from the actual OBJ model dimensions. Each block in
 * the 3x3x2 grid gets collision shapes for any cage walls that pass through it,
 * clipped to the block boundaries. This gives pixel-accurate cage collision
 * that matches the visual model.
 */
public class PetCageBlock extends Block implements EntityBlock {

    public static final DirectionProperty FACING =
        HorizontalDirectionalBlock.FACING;

    // OBJ MODEL DIMENSIONS (in model space, before rotation/translation)
    private static final double MODEL_MIN_X = -1.137;
    private static final double MODEL_MAX_X = 1.0;
    private static final double MODEL_MIN_Z = -1.122;
    private static final double MODEL_MAX_Z = 1.0;
    private static final double MODEL_MAX_Y = 2.218;

    /** Wall thickness in blocks. */
    private static final double WALL_T = 0.125; // 2 pixels

    private static final VoxelShape OUTLINE_SHAPE = Block.box(
        0,
        0,
        0,
        16,
        16,
        16
    );

    public PetCageBlock(Properties properties) {
        super(properties);
        this.registerDefaultState(
            this.stateDefinition.any().setValue(FACING, Direction.NORTH)
        );
    }

    @Override
    protected void createBlockStateDefinition(
        StateDefinition.Builder<Block, BlockState> builder
    ) {
        builder.add(FACING);
    }

    @Override
    public BlockState getStateForPlacement(BlockPlaceContext context) {
        Direction facing = context.getHorizontalDirection().getOpposite();
        BlockPos pos = context.getClickedPos();

        for (BlockPos partPos : getPartPositions(pos, facing)) {
            if (
                !context
                    .getLevel()
                    .getBlockState(partPos)
                    .canBeReplaced(context)
            ) {
                return null;
            }
        }

        return this.defaultBlockState().setValue(FACING, facing);
    }

    @Override
    public void onPlace(
        BlockState state,
        Level level,
        BlockPos pos,
        BlockState oldState,
        boolean isMoving
    ) {
        super.onPlace(state, level, pos, oldState, isMoving);
        if (level.isClientSide || oldState.is(this)) return;

        Direction facing = state.getValue(FACING);
        BlockState partState = V2Blocks.PET_CAGE_PART.get()
            .defaultBlockState()
            .setValue(PetCagePartBlock.FACING, facing);

        for (BlockPos partPos : getPartPositions(pos, facing)) {
            level.setBlock(partPos, partState, 3);
        }
    }

    @Override
    public VoxelShape getShape(
        BlockState state,
        BlockGetter level,
        BlockPos pos,
        CollisionContext context
    ) {
        return OUTLINE_SHAPE;
    }

    @Override
    public VoxelShape getCollisionShape(
        BlockState state,
        BlockGetter level,
        BlockPos pos,
        CollisionContext context
    ) {
        Direction facing = state.getValue(FACING);
        return computeCageCollision(pos, facing, pos);
    }

    @Override
    public RenderShape getRenderShape(BlockState state) {
        return RenderShape.ENTITYBLOCK_ANIMATED;
    }

    @Nullable
    @Override
    public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
        return new PetCageBlockEntity(
            V2BlockEntities.PET_CAGE.get(),
            pos,
            state
        );
    }

    @Override
    public InteractionResult use(
        BlockState state,
        Level level,
        BlockPos pos,
        Player player,
        InteractionHand hand,
        BlockHitResult hit
    ) {
        if (level.isClientSide) {
            return InteractionResult.CONSUME;
        }

        if (!(player instanceof ServerPlayer sp)) {
            return InteractionResult.PASS;
        }

        BlockEntity be = level.getBlockEntity(pos);
        if (!(be instanceof PetCageBlockEntity cageBE)) {
            return InteractionResult.PASS;
        }

        if (cageBE.hasOccupant()) {
            ServerPlayer occupant = cageBE.getOccupant(level.getServer());
            if (occupant != null) {
                PetCageManager.releasePlayer(occupant);
                TiedUpMod.LOGGER.debug(
                    "[PetCageBlock] {} released {} from cage at {}",
                    player.getName().getString(),
                    occupant.getName().getString(),
                    pos
                );
            } else {
                cageBE.clearOccupant();
            }
            return InteractionResult.SUCCESS;
        }

        double cx = pos.getX() + 0.5;
        double cy = pos.getY();
        double cz = pos.getZ() + 0.5;

        for (ServerPlayer nearby : level
            .getServer()
            .getPlayerList()
            .getPlayers()) {
            if (nearby == player) continue;
            if (nearby.distanceToSqr(cx, cy, cz) > 9.0) continue;

            IBondageState kidState = KidnappedHelper.getKidnappedState(nearby);
            if (kidState != null && kidState.isTiedUp()) {
                PetCageManager.cagePlayer(nearby, pos);
                cageBE.setOccupant(nearby.getUUID());
                TiedUpMod.LOGGER.debug(
                    "[PetCageBlock] {} caged {} at {}",
                    player.getName().getString(),
                    nearby.getName().getString(),
                    pos
                );
                return InteractionResult.SUCCESS;
            }
        }

        return InteractionResult.PASS;
    }

    @Override
    public void onRemove(
        BlockState state,
        Level level,
        BlockPos pos,
        BlockState newState,
        boolean isMoving
    ) {
        if (!state.is(newState.getBlock())) {
            BlockEntity be = level.getBlockEntity(pos);
            if (
                be instanceof PetCageBlockEntity cageBE &&
                cageBE.hasOccupant() &&
                level.getServer() != null
            ) {
                ServerPlayer occupant = cageBE.getOccupant(level.getServer());
                if (occupant != null) {
                    PetCageManager.releasePlayer(occupant);
                }
            }

            Direction facing = state.getValue(FACING);
            for (BlockPos partPos : getPartPositions(pos, facing)) {
                BlockState partState = level.getBlockState(partPos);
                if (partState.getBlock() instanceof PetCagePartBlock) {
                    level.setBlock(
                        partPos,
                        Blocks.AIR.defaultBlockState(),
                        3 | 64
                    );
                }
            }
        }
        super.onRemove(state, level, pos, newState, isMoving);
    }

    // CAGE COLLISION (model-accurate)

    /**
     * Compute the cage AABB in world coordinates for a given master position and facing.
     * The renderer does translate(0.5, 0, 0.5) then rotate, so vertices are
     * first rotated then translated.
     *
     * @return {minX, minZ, maxX, maxZ, maxY}
     */
    public static double[] getCageWorldBounds(
        BlockPos masterPos,
        Direction facing
    ) {
        double cx = masterPos.getX() + 0.5;
        double cz = masterPos.getZ() + 0.5;

        double wMinX, wMaxX, wMinZ, wMaxZ;

        switch (facing) {
            case SOUTH -> {
                // 180°: (x,z) → (-x,-z)
                wMinX = cx - MODEL_MAX_X;
                wMaxX = cx - MODEL_MIN_X;
                wMinZ = cz - MODEL_MAX_Z;
                wMaxZ = cz - MODEL_MIN_Z;
            }
            case WEST -> {
                // 90°: (x,z) → (z,-x)
                wMinX = cx + MODEL_MIN_Z;
                wMaxX = cx + MODEL_MAX_Z;
                wMinZ = cz - MODEL_MAX_X;
                wMaxZ = cz - MODEL_MIN_X;
            }
            case EAST -> {
                // -90°: (x,z) → (-z,x)
                wMinX = cx - MODEL_MAX_Z;
                wMaxX = cx - MODEL_MIN_Z;
                wMinZ = cz + MODEL_MIN_X;
                wMaxZ = cz + MODEL_MAX_X;
            }
            default -> {
                // NORTH, 0°: (x,z) → (x,z)
                wMinX = cx + MODEL_MIN_X;
                wMaxX = cx + MODEL_MAX_X;
                wMinZ = cz + MODEL_MIN_Z;
                wMaxZ = cz + MODEL_MAX_Z;
            }
        }

        return new double[] {
            wMinX,
            wMinZ,
            wMaxX,
            wMaxZ,
            masterPos.getY() + MODEL_MAX_Y,
        };
    }

    /**
     * Compute the cage collision VoxelShape for any block position within the cage grid.
     * Clips the 4 cage walls + floor to the block's boundaries.
     */
    public static VoxelShape computeCageCollision(
        BlockPos masterPos,
        Direction facing,
        BlockPos blockPos
    ) {
        double[] bounds = getCageWorldBounds(masterPos, facing);
        double cMinX = bounds[0],
            cMinZ = bounds[1];
        double cMaxX = bounds[2],
            cMaxZ = bounds[3];
        double cMaxY = bounds[4];
        double cMinY = masterPos.getY(); // Cage base = master Y

        double bx = blockPos.getX();
        double by = blockPos.getY();
        double bz = blockPos.getZ();

        VoxelShape shape = Shapes.empty();

        // Left wall (at cMinX, perpendicular to X) — full height from base to ceiling
        shape = addClippedWall(
            shape,
            cMinX - WALL_T / 2,
            cMinY,
            cMinZ,
            cMinX + WALL_T / 2,
            cMaxY,
            cMaxZ,
            bx,
            by,
            bz
        );

        // Right wall (at cMaxX)
        shape = addClippedWall(
            shape,
            cMaxX - WALL_T / 2,
            cMinY,
            cMinZ,
            cMaxX + WALL_T / 2,
            cMaxY,
            cMaxZ,
            bx,
            by,
            bz
        );

        // Front wall (at cMinZ, perpendicular to Z)
        shape = addClippedWall(
            shape,
            cMinX,
            cMinY,
            cMinZ - WALL_T / 2,
            cMaxX,
            cMaxY,
            cMinZ + WALL_T / 2,
            bx,
            by,
            bz
        );

        // Back wall (at cMaxZ)
        shape = addClippedWall(
            shape,
            cMinX,
            cMinY,
            cMaxZ - WALL_T / 2,
            cMaxX,
            cMaxY,
            cMaxZ + WALL_T / 2,
            bx,
            by,
            bz
        );

        // Floor (at cage base only)
        shape = addClippedWall(
            shape,
            cMinX,
            cMinY,
            cMinZ,
            cMaxX,
            cMinY + WALL_T,
            cMaxZ,
            bx,
            by,
            bz
        );

        // Ceiling: 1px thin at [1.9375, 2.0] — player head at 1.925 < 1.9375, works from both sides
        double ceilY = cMinY + 2.0;
        double ceilT = 0.0625; // 1 pixel thin so player (1.8 tall) fits under it
        shape = addClippedWall(
            shape,
            cMinX,
            ceilY - ceilT,
            cMinZ,
            cMaxX,
            ceilY,
            cMaxZ,
            bx,
            by,
            bz
        );

        return shape;
    }

    /**
     * Clip a world-space AABB to a block's local bounds and add it as a VoxelShape.
     * X and Z are clipped to [0,1] (entities in other blocks won't query this one).
     * Y max is allowed to extend above 1.0 (like fences) since entities below
     * will still query this block and collide with the extended shape.
     */
    private static VoxelShape addClippedWall(
        VoxelShape existing,
        double wMinX,
        double wMinY,
        double wMinZ,
        double wMaxX,
        double wMaxY,
        double wMaxZ,
        double blockX,
        double blockY,
        double blockZ
    ) {
        double lMinX = Math.max(0, wMinX - blockX);
        double lMinY = Math.max(0, wMinY - blockY);
        double lMinZ = Math.max(0, wMinZ - blockZ);
        double lMaxX = Math.min(1, wMaxX - blockX);
        double lMaxY = Math.min(1.5, wMaxY - blockY); // Allow extending up to 0.5 above block (like fences)
        double lMaxZ = Math.min(1, wMaxZ - blockZ);

        if (lMinX >= lMaxX || lMinY >= lMaxY || lMinZ >= lMaxZ || lMaxY <= 0) {
            return existing;
        }

        return Shapes.or(
            existing,
            Shapes.box(lMinX, lMinY, lMinZ, lMaxX, lMaxY, lMaxZ)
        );
    }

    // MULTI-BLOCK LAYOUT (3x3x2)

    private static final int[][] GRID_OFFSETS;

    static {
        java.util.List<int[]> offsets = new java.util.ArrayList<>();
        for (int dy = 0; dy <= 1; dy++) {
            for (int dl = -1; dl <= 1; dl++) {
                for (int df = -1; df <= 1; df++) {
                    if (dl == 0 && dy == 0 && df == 0) continue;
                    offsets.add(new int[] { dl, dy, df });
                }
            }
        }
        GRID_OFFSETS = offsets.toArray(new int[0][]);
    }

    public static BlockPos[] getPartPositions(
        BlockPos masterPos,
        Direction facing
    ) {
        Direction left = facing.getCounterClockWise();

        int lx = left.getStepX();
        int lz = left.getStepZ();
        int fx = facing.getStepX();
        int fz = facing.getStepZ();

        BlockPos[] positions = new BlockPos[GRID_OFFSETS.length];
        for (int i = 0; i < GRID_OFFSETS.length; i++) {
            int dl = GRID_OFFSETS[i][0];
            int dy = GRID_OFFSETS[i][1];
            int df = GRID_OFFSETS[i][2];

            int wx = dl * lx + df * fx;
            int wz = dl * lz + df * fz;
            positions[i] = masterPos.offset(wx, dy, wz);
        }
        return positions;
    }

    @Nullable
    public static int[] getPartLocalOffset(
        BlockPos masterPos,
        Direction facing,
        BlockPos partPos
    ) {
        Direction left = facing.getCounterClockWise();

        int dx = partPos.getX() - masterPos.getX();
        int dy = partPos.getY() - masterPos.getY();
        int dz = partPos.getZ() - masterPos.getZ();

        int lx = left.getStepX(),
            lz = left.getStepZ();
        int fx = facing.getStepX(),
            fz = facing.getStepZ();

        int det = lx * fz - fx * lz;
        if (det == 0) return null;

        int dl = (dx * fz - dz * fx) / det;
        int df = (dz * lx - dx * lz) / det;

        if (
            dl < -1 || dl > 1 || df < -1 || df > 1 || dy < 0 || dy > 1
        ) return null;
        if (dl == 0 && dy == 0 && df == 0) return null;

        return new int[] { dl, dy, df };
    }
}
