// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
//
// SPDX-License-Identifier: LicenseRef-CCPL

package dan200.computercraft.shared.computer.blocks;

import dan200.computercraft.shared.common.IBundledRedstoneBlock;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.util.RandomSource;
import net.minecraft.util.Util;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.ScheduledTickAccess;
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.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.redstone.Orientation;
import net.minecraft.world.phys.BlockHitResult;
import org.jspecify.annotations.Nullable;

public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock {
    protected final RegistryEntry<BlockEntityType<T>> type;
    private final BlockEntityTicker<T> serverTicker = (level, pos, state, computer) -> computer.serverTick();

    protected AbstractComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
        super(settings);
        this.type = type;
    }

    @Override
    protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean isMoving) {
        super.onPlace(state, world, pos, oldState, isMoving);

        var tile = world.getBlockEntity(pos);
        if (tile instanceof AbstractComputerBlockEntity computer) computer.updateInputsImmediately();
    }

    @Override
    protected boolean isSignalSource(BlockState state) {
        return true;
    }

    @Override
    protected int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
        var entity = world.getBlockEntity(pos);
        if (!(entity instanceof AbstractComputerBlockEntity computerEntity)) return 0;

        var computer = computerEntity.getServerComputer();
        if (computer == null) return 0;

        var localSide = computerEntity.remapToLocalSide(incomingSide.getOpposite());
        return computer.getRedstoneOutput(localSide);
    }

    @Override
    protected int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
        return getDirectSignal(state, world, pos, incomingSide);
    }

    @Override
    public int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side) {
        var entity = world.getBlockEntity(pos);
        if (!(entity instanceof AbstractComputerBlockEntity computerEntity)) return 0;

        var computer = computerEntity.getServerComputer();
        if (computer == null) return 0;

        var localSide = computerEntity.remapToLocalSide(side);
        return computer.getBundledRedstoneOutput(localSide);
    }

    @Override
    public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state, boolean includeData) {
        var stack = super.getCloneItemStack(world, pos, state, includeData);
        if (world.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) {
            stack.applyComponents(computer.collectComponents());
        }
        return stack;
    }

    @Override
    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity tile, ItemStack tool) {
        // Use the same trick as DoublePlantBlock, to skip dropping items. See playerWillDestroy.
        super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), tile, tool);
    }

    @Override
    public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
        // We drop the item here instead of doing it in the harvest method, as we should
        // drop computers for creative players too.
        Block.dropResources(state, world, pos, world.getBlockEntity(pos), player, player.getMainHandItem());

        return super.playerWillDestroy(world, pos, state, player);
    }

    @Override
    protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
        if (!player.isCrouching() && level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) {
            // Regular right click to activate computer
            if (!level.isClientSide() && computer.isUsable(player)) {
                // TODO(1.21.11): Call BaseContainerBlockEntity.sendChestLockedNotifications
                var serverComputer = computer.createServerComputer();
                serverComputer.turnOn();

                var stack = new ItemStack(this);
                stack.applyComponents(Util.make(DataComponentMap.builder(), computer::collectSafeComponents).build());

                PlatformHelper.get().openMenu(player, computer.getName(), computer, new ComputerContainerData(serverComputer, stack));
            }
            return InteractionResult.SUCCESS;
        }

        return super.useWithoutItem(state, level, pos, player, hit);
    }

    @Override
    protected void neighborChanged(BlockState blockState, Level level, BlockPos blockPos, Block block, @Nullable Orientation orientation, boolean isMoving) {
        if (level.getBlockEntity(blockPos) instanceof AbstractComputerBlockEntity computer) computer.neighborChanged();
    }

    @Override
    protected BlockState updateShape(BlockState state, LevelReader level, ScheduledTickAccess ticker, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource randomSource) {
        var be = level.getBlockEntity(pos);
        if (be instanceof AbstractComputerBlockEntity computer) computer.neighbourShapeChanged(direction);

        return super.updateShape(state, level, ticker, pos, direction, neighborPos, neighborState, randomSource);
    }

    @Override
    @Nullable
    public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
        return level.isClientSide() ? null : BlockEntityHelpers.createTickerHelper(type, this.type.get(), serverTicker);
    }

    @Nullable
    @Override
    public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
        return type.get().create(pos, state);
    }
}
