// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0

package dan200.computercraft.shared;

import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftBlockIds;
import dan200.computercraft.api.ComputerCraftItemIds;
import dan200.computercraft.api.component.ComputerComponents;
import dan200.computercraft.api.detail.DetailProvider;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.command.UserLevel;
import dan200.computercraft.shared.command.arguments.ComputerArgumentType;
import dan200.computercraft.shared.command.arguments.RepeatArgumentType;
import dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType;
import dan200.computercraft.shared.common.ClearColourRecipe;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.computer.apis.CommandAPI;
import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.TerminalSize;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.computer.items.ServerComputerReference;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.details.BlockDetails;
import dan200.computercraft.shared.details.EntityDetails;
import dan200.computercraft.shared.details.ItemDetails;
import dan200.computercraft.shared.lectern.CustomLecternBlock;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.lectern.PocketComputerLecternMenu;
import dan200.computercraft.shared.media.MountMedia;
import dan200.computercraft.shared.media.PrintoutMenu;
import dan200.computercraft.shared.media.items.*;
import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
import dan200.computercraft.shared.peripheral.modem.wired.*;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity;
import dan200.computercraft.shared.peripheral.monitor.MonitorBlock;
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
import dan200.computercraft.shared.peripheral.printer.PrinterBlock;
import dan200.computercraft.shared.peripheral.printer.PrinterBlockEntity;
import dan200.computercraft.shared.peripheral.printer.PrinterMenu;
import dan200.computercraft.shared.peripheral.redstone.RedstoneRelayBlock;
import dan200.computercraft.shared.peripheral.redstone.RedstoneRelayBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlock;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.platform.PermissionRegistry;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RegistrationHelper;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.pocket.apis.PocketAPI;
import dan200.computercraft.shared.pocket.core.PocketComputerInternal;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
import dan200.computercraft.shared.recipe.TransformShapedRecipe;
import dan200.computercraft.shared.recipe.TransformShapelessRecipe;
import dan200.computercraft.shared.recipe.function.CopyComponents;
import dan200.computercraft.shared.recipe.function.RecipeFunction;
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
import dan200.computercraft.shared.turtle.apis.TurtleAPI;
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.turtle.core.TurtleAccessInternal;
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable;
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker;
import dan200.computercraft.shared.turtle.upgrades.TurtleTool;
import dan200.computercraft.shared.util.DataComponentUtil;
import dan200.computercraft.shared.util.NonNegativeId;
import dan200.computercraft.shared.util.StorageCapacity;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.*;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.world.item.component.TooltipProvider;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.GameMasterBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.BlockEntityTypes;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import org.jspecify.annotations.Nullable;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.*;

/**
 * Registers ComputerCraft's registry entries and additional objects, such as {@link DetailProvider}s.
 * <p>
 * The functions in this class should be called from a loader-specific class.
 */
public final class ModRegistry {
    private ModRegistry() {
    }

    public static final class Blocks {
        static final RegistrationHelper<Block> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK);

        private static <T extends Block> RegistryEntry<T> register(ResourceKey<Block> name, Function<BlockBehaviour.Properties, T> build, BlockBehaviour.Properties properties) {
            return REGISTRY.register(name, () -> build.apply(properties.setId(name)));
        }

        private static BlockBehaviour.Properties properties() {
            return BlockBehaviour.Properties.of().strength(2);
        }

        private static BlockBehaviour.Properties redstoneConductor() {
            // Computers shouldn't conduct redstone through them, so set isRedstoneConductor to false. This still allows
            // redstone to connect to computers though as it's a signal source.
            return properties().isRedstoneConductor((block, level, blockPos) -> false);
        }

        private static BlockBehaviour.Properties turtleProperties() {
            return BlockBehaviour.Properties.of().strength(2.5f);
        }

        private static BlockBehaviour.Properties modemProperties() {
            return BlockBehaviour.Properties.of().strength(1.5f);
        }

        public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = register(ComputerCraftBlockIds.COMPUTER_NORMAL,
            p -> new ComputerBlock<>(p, BlockEntities.COMPUTER_NORMAL), redstoneConductor().mapColor(MapColor.STONE));
        public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = register(ComputerCraftBlockIds.COMPUTER_ADVANCED,
            p -> new ComputerBlock<>(p, BlockEntities.COMPUTER_ADVANCED), redstoneConductor().mapColor(MapColor.GOLD));
        public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_COMMAND = register(ComputerCraftBlockIds.COMPUTER_COMMAND,
            p -> new CommandComputerBlock<>(p, BlockEntities.COMPUTER_COMMAND), redstoneConductor().strength(-1, 6000000.0F));

        public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = register(ComputerCraftBlockIds.TURTLE_NORMAL,
            p -> new TurtleBlock(p, BlockEntities.TURTLE_NORMAL), turtleProperties().mapColor(MapColor.STONE));
        public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = register(ComputerCraftBlockIds.TURTLE_ADVANCED,
            p -> new TurtleBlock(p, BlockEntities.TURTLE_ADVANCED), turtleProperties().mapColor(MapColor.GOLD).explosionResistance(TurtleBlock.IMMUNE_EXPLOSION_RESISTANCE));

        public static final RegistryEntry<SpeakerBlock> SPEAKER = register(ComputerCraftBlockIds.SPEAKER, SpeakerBlock::new, properties().mapColor(MapColor.STONE));
        public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = register(ComputerCraftBlockIds.DISK_DRIVE, DiskDriveBlock::new, properties().mapColor(MapColor.STONE));
        public static final RegistryEntry<PrinterBlock> PRINTER = register(ComputerCraftBlockIds.PRINTER, PrinterBlock::new, properties().mapColor(MapColor.STONE));

        public static final RegistryEntry<MonitorBlock> MONITOR_NORMAL = register(ComputerCraftBlockIds.MONITOR_NORMAL,
            p -> new MonitorBlock(p, BlockEntities.MONITOR_NORMAL), properties().mapColor(MapColor.STONE));
        public static final RegistryEntry<MonitorBlock> MONITOR_ADVANCED = register(ComputerCraftBlockIds.MONITOR_ADVANCED,
            p -> new MonitorBlock(p, BlockEntities.MONITOR_ADVANCED), properties().mapColor(MapColor.GOLD));

        public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_NORMAL = register(ComputerCraftBlockIds.WIRELESS_MODEM_NORMAL,
            p -> new WirelessModemBlock(p, BlockEntities.WIRELESS_MODEM_NORMAL), properties().mapColor(MapColor.STONE));
        public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_ADVANCED = register(ComputerCraftBlockIds.WIRELESS_MODEM_ADVANCED,
            p -> new WirelessModemBlock(p, BlockEntities.WIRELESS_MODEM_ADVANCED), properties().mapColor(MapColor.GOLD));

        public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = register(ComputerCraftBlockIds.WIRED_MODEM_FULL,
            WiredModemFullBlock::new, modemProperties().mapColor(MapColor.STONE));
        public static final RegistryEntry<CableBlock> CABLE = register(ComputerCraftBlockIds.CABLE, CableBlock::new, modemProperties().mapColor(MapColor.STONE));

        public static final RegistryEntry<CustomLecternBlock> LECTERN = register(ComputerCraftBlockIds.LECTERN, CustomLecternBlock::new,
            BlockBehaviour.Properties.ofFullCopy(net.minecraft.world.level.block.Blocks.LECTERN)
                .overrideDescription(net.minecraft.world.level.block.Blocks.LECTERN.getDescriptionId())
        );

        public static final RegistryEntry<RedstoneRelayBlock> REDSTONE_RELAY = register(ComputerCraftBlockIds.REDSTONE_RELAY, RedstoneRelayBlock::new,
            redstoneConductor().mapColor(MapColor.STONE));
    }

    public static class BlockEntities {
        static final RegistrationHelper<BlockEntityType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK_ENTITY_TYPE);

        private static final class AdminBlockEntityType<T extends BlockEntity> extends BlockEntityType<T> {
            AdminBlockEntityType(BlockEntitySupplier<? extends T> factory, Set<Block> validBlocks) {
                super(factory, validBlocks);
            }

            @Override
            public boolean onlyOpCanSetNbt() {
                return true;
            }
        }

        private static <T extends BlockEntity> RegistryEntry<BlockEntityType<T>> ofBlock(RegistryEntry<? extends Block> block, BlockEntityType.BlockEntitySupplier<T> factory) {
            return REGISTRY.register(block.id().getPath(), () -> block.get() instanceof GameMasterBlock
                ? new AdminBlockEntityType<>(factory, Set.of(block.get()))
                : new BlockEntityType<>(factory, Set.of(block.get()))
            );
        }

        public static final RegistryEntry<BlockEntityType<MonitorBlockEntity>> MONITOR_NORMAL =
            ofBlock(Blocks.MONITOR_NORMAL, (p, s) -> new MonitorBlockEntity(BlockEntities.MONITOR_NORMAL.get(), p, s, false));
        public static final RegistryEntry<BlockEntityType<MonitorBlockEntity>> MONITOR_ADVANCED =
            ofBlock(Blocks.MONITOR_ADVANCED, (p, s) -> new MonitorBlockEntity(BlockEntities.MONITOR_ADVANCED.get(), p, s, true));

        public static final RegistryEntry<BlockEntityType<ComputerBlockEntity>> COMPUTER_NORMAL =
            ofBlock(Blocks.COMPUTER_NORMAL, (p, s) -> new ComputerBlockEntity(BlockEntities.COMPUTER_NORMAL.get(), p, s, ComputerFamily.NORMAL));
        public static final RegistryEntry<BlockEntityType<ComputerBlockEntity>> COMPUTER_ADVANCED =
            ofBlock(Blocks.COMPUTER_ADVANCED, (p, s) -> new ComputerBlockEntity(BlockEntities.COMPUTER_ADVANCED.get(), p, s, ComputerFamily.ADVANCED));
        public static final RegistryEntry<BlockEntityType<ComputerBlockEntity>> COMPUTER_COMMAND =
            ofBlock(Blocks.COMPUTER_COMMAND, (p, s) -> new ComputerBlockEntity(BlockEntities.COMPUTER_COMMAND.get(), p, s, ComputerFamily.COMMAND));

        public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_NORMAL =
            ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, () -> Config.turtleFuelLimit, ComputerFamily.NORMAL));
        public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_ADVANCED =
            ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, () -> Config.advancedTurtleFuelLimit, ComputerFamily.ADVANCED));

        public static final RegistryEntry<BlockEntityType<SpeakerBlockEntity>> SPEAKER =
            ofBlock(Blocks.SPEAKER, (p, s) -> new SpeakerBlockEntity(BlockEntities.SPEAKER.get(), p, s));
        public static final RegistryEntry<BlockEntityType<DiskDriveBlockEntity>> DISK_DRIVE =
            ofBlock(Blocks.DISK_DRIVE, (p, s) -> new DiskDriveBlockEntity(BlockEntities.DISK_DRIVE.get(), p, s));
        public static final RegistryEntry<BlockEntityType<PrinterBlockEntity>> PRINTER =
            ofBlock(Blocks.PRINTER, (p, s) -> new PrinterBlockEntity(BlockEntities.PRINTER.get(), p, s));
        public static final RegistryEntry<BlockEntityType<WiredModemFullBlockEntity>> WIRED_MODEM_FULL =
            ofBlock(Blocks.WIRED_MODEM_FULL, (p, s) -> new WiredModemFullBlockEntity(BlockEntities.WIRED_MODEM_FULL.get(), p, s));
        public static final RegistryEntry<BlockEntityType<CableBlockEntity>> CABLE =
            ofBlock(Blocks.CABLE, (p, s) -> new CableBlockEntity(BlockEntities.CABLE.get(), p, s));

        public static final RegistryEntry<BlockEntityType<WirelessModemBlockEntity>> WIRELESS_MODEM_NORMAL =
            ofBlock(Blocks.WIRELESS_MODEM_NORMAL, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_NORMAL.get(), p, s, false));
        public static final RegistryEntry<BlockEntityType<WirelessModemBlockEntity>> WIRELESS_MODEM_ADVANCED =
            ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_ADVANCED.get(), p, s, true));

        public static final RegistryEntry<BlockEntityType<CustomLecternBlockEntity>> LECTERN = ofBlock(Blocks.LECTERN, CustomLecternBlockEntity::new);

        public static final RegistryEntry<BlockEntityType<RedstoneRelayBlockEntity>> REDSTONE_RELAY = ofBlock(Blocks.REDSTONE_RELAY, RedstoneRelayBlockEntity::new);
    }

    public static final class Items {
        static final RegistrationHelper<Item> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.ITEM);

        private static Item.Properties properties() {
            return new Item.Properties();
        }

        private static Item.Properties dyeableProperties() {
            return properties().component(
                net.minecraft.core.component.DataComponents.TOOLTIP_DISPLAY,
                TooltipDisplay.DEFAULT.withHidden(net.minecraft.core.component.DataComponents.DYED_COLOR, true)
            );
        }

        private static <T extends Item> RegistryEntry<T> register(ResourceKey<Item> name, Function<Item.Properties, T> build, Supplier<Item.Properties> properties) {
            return REGISTRY.register(name, () -> build.apply(properties.get().setId(name)));
        }

        private static <T extends Item> RegistryEntry<T> register(ResourceKey<Item> name, Function<Item.Properties, T> build, Item.Properties properties) {
            return register(name, build, () -> properties);
        }

        private static <B extends Block, I extends Item> RegistryEntry<I> ofBlock(RegistryEntry<B> parent, BiFunction<B, Item.Properties, I> supplier, Item.Properties properties) {
            return register(ResourceKey.create(Registries.ITEM, parent.id()), p -> supplier.apply(parent.get(), p), properties.useBlockDescriptionPrefix());
        }

        public static final RegistryEntry<BlockItem> COMPUTER_NORMAL = ofBlock(Blocks.COMPUTER_NORMAL, BlockItem::new, properties());
        public static final RegistryEntry<BlockItem> COMPUTER_ADVANCED = ofBlock(Blocks.COMPUTER_ADVANCED, BlockItem::new, properties());
        public static final RegistryEntry<GameMasterBlockItem> COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, GameMasterBlockItem::new, properties());

        public static final RegistryEntry<PocketComputerItem> POCKET_COMPUTER_NORMAL = register(ComputerCraftItemIds.POCKET_COMPUTER_NORMAL,
            p -> new PocketComputerItem(p, ComputerFamily.NORMAL), dyeableProperties().stacksTo(1));
        public static final RegistryEntry<PocketComputerItem> POCKET_COMPUTER_ADVANCED = register(ComputerCraftItemIds.POCKET_COMPUTER_ADVANCED,
            p -> new PocketComputerItem(p, ComputerFamily.ADVANCED), dyeableProperties().stacksTo(1));

        public static final RegistryEntry<TurtleItem> TURTLE_NORMAL = ofBlock(Blocks.TURTLE_NORMAL, TurtleItem::new, dyeableProperties());
        public static final RegistryEntry<TurtleItem> TURTLE_ADVANCED = ofBlock(Blocks.TURTLE_ADVANCED, TurtleItem::new, dyeableProperties());

        public static final RegistryEntry<DiskItem> DISK =
            register(ComputerCraftItemIds.DISK, DiskItem::new, dyeableProperties().stacksTo(1));
        public static final RegistryEntry<DiskItem> TREASURE_DISK =
            register(ComputerCraftItemIds.TREASURE_DISK, DiskItem::new, dyeableProperties().stacksTo(1));

        private static Item.Properties printoutProperties() {
            return properties().stacksTo(1).component(DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
        }

        public static final RegistryEntry<PrintoutItem> PRINTED_PAGE = register(ComputerCraftItemIds.PRINTED_PAGE,
            PrintoutItem::new, Items::printoutProperties);
        public static final RegistryEntry<PrintoutItem> PRINTED_PAGES = register(ComputerCraftItemIds.PRINTED_PAGES,
            PrintoutItem::new, Items::printoutProperties);
        public static final RegistryEntry<PrintoutItem> PRINTED_BOOK = register(ComputerCraftItemIds.PRINTED_BOOK,
            PrintoutItem::new, Items::printoutProperties);

        public static final RegistryEntry<BlockItem> SPEAKER = ofBlock(Blocks.SPEAKER, BlockItem::new, properties());
        public static final RegistryEntry<BlockItem> DISK_DRIVE = ofBlock(Blocks.DISK_DRIVE, BlockItem::new, properties());
        public static final RegistryEntry<BlockItem> PRINTER = ofBlock(Blocks.PRINTER, BlockItem::new, properties());
        public static final RegistryEntry<BlockItem> MONITOR_NORMAL = ofBlock(Blocks.MONITOR_NORMAL, BlockItem::new, properties());
        public static final RegistryEntry<BlockItem> MONITOR_ADVANCED = ofBlock(Blocks.MONITOR_ADVANCED, BlockItem::new, properties());
        public static final RegistryEntry<BlockItem> WIRELESS_MODEM_NORMAL = ofBlock(Blocks.WIRELESS_MODEM_NORMAL, BlockItem::new, properties());
        public static final RegistryEntry<BlockItem> WIRELESS_MODEM_ADVANCED = ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, BlockItem::new, properties());
        public static final RegistryEntry<BlockItem> WIRED_MODEM_FULL = ofBlock(Blocks.WIRED_MODEM_FULL, BlockItem::new, properties());
        public static final RegistryEntry<BlockItem> REDSTONE_RELAY = ofBlock(Blocks.REDSTONE_RELAY, BlockItem::new, properties());

        public static final RegistryEntry<CableBlockItem.Cable> CABLE = register(ComputerCraftItemIds.CABLE,
            p -> new CableBlockItem.Cable(Blocks.CABLE.get(), p), properties().useBlockDescriptionPrefix());
        public static final RegistryEntry<CableBlockItem.WiredModem> WIRED_MODEM = register(ComputerCraftItemIds.WIRED_MODEM,
            p -> new CableBlockItem.WiredModem(Blocks.CABLE.get(), p), properties().useBlockDescriptionPrefix());
    }

    public static final class DataComponents {
        static final RegistrationHelper<DataComponentType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.DATA_COMPONENT_TYPE);

        private static <T> RegistryEntry<DataComponentType<T>> register(String name, UnaryOperator<DataComponentType.Builder<T>> unaryOperator) {
            return REGISTRY.register(name, () -> unaryOperator.apply(DataComponentType.builder()).build());
        }

        /**
         * The id of a computer.
         */
        public static final RegistryEntry<DataComponentType<NonNegativeId.Computer>> COMPUTER_ID = register("computer_id", b -> b
            .persistent(NonNegativeId.Computer.CODEC).networkSynchronized(NonNegativeId.Computer.STREAM_CODEC)
        );

        /**
         * The storage capacity of a computer or disk.
         */
        public static final RegistryEntry<DataComponentType<StorageCapacity>> STORAGE_CAPACITY = register("storage_capacity", b -> b
            .persistent(StorageCapacity.CODEC).networkSynchronized(StorageCapacity.STREAM_CODEC)
        );

        /**
         * The terminal size of a computer.
         */
        public static final RegistryEntry<DataComponentType<TerminalSize>> TERMINAL_SIZE = register("terminal_size", b -> b
            .persistent(TerminalSize.CODEC).networkSynchronized(TerminalSize.STREAM_CODEC)
        );

        /**
         * The left upgrade of a turtle.
         *
         * @see TurtleItem
         */
        public static final RegistryEntry<DataComponentType<UpgradeData<ITurtleUpgrade>>> LEFT_TURTLE_UPGRADE = register("left_turtle_upgrade", b -> b
            .persistent(TurtleUpgrades.instance().upgradeDataCodec()).networkSynchronized(TurtleUpgrades.instance().upgradeDataStreamCodec())
        );

        /**
         * The right upgrade of a turtle.
         *
         * @see TurtleItem
         */
        public static final RegistryEntry<DataComponentType<UpgradeData<ITurtleUpgrade>>> RIGHT_TURTLE_UPGRADE = register("right_turtle_upgrade", b -> b
            .persistent(TurtleUpgrades.instance().upgradeDataCodec()).networkSynchronized(TurtleUpgrades.instance().upgradeDataStreamCodec())
        );

        /**
         * The fuel level of a turtle.
         */
        public static final RegistryEntry<DataComponentType<Integer>> FUEL = register("fuel", b -> b
            .persistent(Codec.INT).networkSynchronized(ByteBufCodecs.VAR_INT)
        );

        /**
         * The overlay on a turtle.
         */
        public static final RegistryEntry<DataComponentType<Identifier>> OVERLAY = register("overlay", b -> b
            .persistent(Identifier.CODEC).networkSynchronized(Identifier.STREAM_CODEC).cacheEncoding()
        );

        /**
         * The top upgrade of a pocket computer.
         *
         * @see PocketComputerItem
         */
        public static final RegistryEntry<DataComponentType<UpgradeData<IPocketUpgrade>>> TOP_POCKET_UPGRADE = register("top_pocket_upgrade", b -> b
            .persistent(PocketUpgrades.instance().upgradeDataCodec()).networkSynchronized(PocketUpgrades.instance().upgradeDataStreamCodec())
        );

        /**
         * The back upgrade of a pocket computer.
         *
         * @see PocketComputerItem
         */
        public static final RegistryEntry<DataComponentType<UpgradeData<IPocketUpgrade>>> BACK_POCKET_UPGRADE = register("back_pocket_upgrade", b -> b
            .persistent(PocketUpgrades.instance().upgradeDataCodec()).networkSynchronized(PocketUpgrades.instance().upgradeDataStreamCodec())
        );

        /**
         * A reference to the currently running {@link dan200.computercraft.shared.computer.core.ServerComputer}.
         *
         * @see ServerComputerReference
         * @see PocketComputerItem
         */
        public static final RegistryEntry<DataComponentType<ServerComputerReference>> COMPUTER = register("computer", b -> b
            .persistent(ServerComputerReference.CODEC).networkSynchronized(ServerComputerReference.STREAM_CODEC)
        );

        /**
         * Whether this item is currently on.
         *
         * @see PocketComputerItem
         * @see TurtleModem
         */
        public static final RegistryEntry<DataComponentType<Boolean>> ON = register("on", b -> b
            .persistent(Codec.BOOL).networkSynchronized(ByteBufCodecs.BOOL)
        );

        /**
         * Information about a treasure disk's mount.
         */
        public static final RegistryEntry<DataComponentType<TreasureDisk>> TREASURE_DISK = register("treasure_disk", b -> b
            .persistent(TreasureDisk.CODEC).networkSynchronized(TreasureDisk.STREAM_CODEC)
        );

        /**
         * The id of a disk.
         */
        public static final RegistryEntry<DataComponentType<NonNegativeId.Disk>> DISK_ID = register("disk_id", b -> b
            .persistent(NonNegativeId.Disk.CODEC).networkSynchronized(NonNegativeId.Disk.STREAM_CODEC)
        );

        /**
         * The contents of a printed page/printed pages.
         *
         * @see PrintoutItem
         * @see PrintoutData
         */
        public static final RegistryEntry<DataComponentType<PrintoutData>> PRINTOUT = register("printout", b -> b
            .persistent(PrintoutData.CODEC).networkSynchronized(PrintoutData.STREAM_CODEC)
        );

        public static final List<RegistryEntry<? extends DataComponentType<? extends TooltipProvider>>> TOOLTIP_COMPONENTS = List.of(
            DataComponents.COMPUTER_ID,
            DataComponents.DISK_ID,
            DataComponents.TREASURE_DISK,
            DataComponents.PRINTOUT
        );
    }

    public static class TurtleUpgradeTypes {
        static final RegistrationHelper<UpgradeType<? extends ITurtleUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(ITurtleUpgrade.typeRegistry());

        public static final RegistryEntry<UpgradeType<TurtleSpeaker>> SPEAKER =
            REGISTRY.register("speaker", () -> UpgradeType.simpleWithCustomItem(TurtleSpeaker::new));
        public static final RegistryEntry<UpgradeType<TurtleCraftingTable>> WORKBENCH =
            REGISTRY.register("workbench", () -> UpgradeType.simpleWithCustomItem(TurtleCraftingTable::new));
        public static final RegistryEntry<UpgradeType<TurtleModem>> WIRELESS_MODEM =
            REGISTRY.register("wireless_modem", () -> UpgradeType.create(TurtleModem.CODEC));

        public static final RegistryEntry<UpgradeType<TurtleTool>> TOOL = REGISTRY.register("tool", () -> UpgradeType.create(TurtleTool.CODEC));
    }

    public static class PocketUpgradeTypes {
        static final RegistrationHelper<UpgradeType<? extends IPocketUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(IPocketUpgrade.typeRegistry());

        public static final RegistryEntry<UpgradeType<PocketSpeaker>> SPEAKER =
            REGISTRY.register("speaker", () -> UpgradeType.simpleWithCustomItem(PocketSpeaker::new));
        public static final RegistryEntry<UpgradeType<PocketModem>> WIRELESS_MODEM = REGISTRY.register("wireless_modem", () -> UpgradeType.create(PocketModem.CODEC));
    }

    public static class Menus {
        static final RegistrationHelper<MenuType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.MENU);

        public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> COMPUTER = REGISTRY.register("computer",
            () -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.COMPUTER.get(), id, inv, data)));

        public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> POCKET_COMPUTER_NO_TERM = REGISTRY.register("pocket_computer_no_term",
            () -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER_NO_TERM.get(), id, inv, data)));

        public static final RegistryEntry<MenuType<PocketComputerLecternMenu>> POCKET_COMPUTER_LECTERN = REGISTRY.register("pocket_computer_lectern",
            () -> ContainerData.toType(PocketComputerLecternMenu.Data.STREAM_CODEC, PocketComputerLecternMenu::new));

        public static final RegistryEntry<MenuType<TurtleMenu>> TURTLE = REGISTRY.register("turtle",
            () -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, TurtleMenu::ofMenuData));

        public static final RegistryEntry<MenuType<DiskDriveMenu>> DISK_DRIVE = REGISTRY.register("disk_drive",
            () -> new MenuType<>(DiskDriveMenu::new, FeatureFlags.VANILLA_SET));

        public static final RegistryEntry<MenuType<PrinterMenu>> PRINTER = REGISTRY.register("printer",
            () -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));

        public static final RegistryEntry<MenuType<PrintoutMenu>> PRINTOUT = REGISTRY.register("printout",
            () -> new MenuType<>((i, c) -> PrintoutMenu.createRemote(i), FeatureFlags.VANILLA_SET));
    }

    static class ArgumentTypes {
        static final RegistrationHelper<ArgumentTypeInfo<?, ?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.COMMAND_ARGUMENT_TYPE);

        @SuppressWarnings("unchecked")
        private static <T extends ArgumentType<?>> void registerUnsafe(String name, Class<T> type, ArgumentTypeInfo<?, ?> serializer) {
            REGISTRY.register(name, () -> PlatformHelper.get().registerArgumentTypeInfo(type, (ArgumentTypeInfo<T, ?>) serializer));
        }

        private static <T extends ArgumentType<?>> void register(String name, Class<T> type, ArgumentTypeInfo<T, ?> serializer) {
            REGISTRY.register(name, () -> PlatformHelper.get().registerArgumentTypeInfo(type, serializer));
        }

        private static <T extends ArgumentType<?>> void register(String name, Class<T> type, T instance) {
            register(name, type, SingletonArgumentInfo.contextFree(() -> instance));
        }

        static {
            register("tracking_field", TrackingFieldArgumentType.class, TrackingFieldArgumentType.metric());
            register("computer", ComputerArgumentType.class, ComputerArgumentType.get());
            registerUnsafe("repeat", RepeatArgumentType.class, new RepeatArgumentType.Info());
        }
    }

    public static class LootItemConditionTypes {
        static final RegistrationHelper<MapCodec<? extends LootItemCondition>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.LOOT_CONDITION_TYPE);

        public static final RegistryEntry<MapCodec<BlockNamedEntityLootCondition>> BLOCK_NAMED = REGISTRY.register("block_named", () -> BlockNamedEntityLootCondition.CODEC);
        public static final RegistryEntry<MapCodec<PlayerCreativeLootCondition>> PLAYER_CREATIVE = REGISTRY.register("player_creative", () -> PlayerCreativeLootCondition.CODEC);
        public static final RegistryEntry<MapCodec<? extends LootItemCondition>> HAS_ID = REGISTRY.register("has_id", () -> HasComputerIdLootCondition.CODEC);
    }

    public static class RecipeSerializers {
        static final RegistrationHelper<RecipeSerializer<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.RECIPE_SERIALIZER);

        public static final RegistryEntry<RecipeSerializer<ImpostorShapedRecipe>> IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", () -> ImpostorShapedRecipe.SERIALIZER);
        public static final RegistryEntry<RecipeSerializer<ImpostorShapelessRecipe>> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", () -> ImpostorShapelessRecipe.SERIALIZER);

        public static final RegistryEntry<RecipeSerializer<TransformShapedRecipe>> TRANSFORM_SHAPED = REGISTRY.register("transform_shaped", () -> TransformShapedRecipe.SERIALIZER);
        public static final RegistryEntry<RecipeSerializer<TransformShapelessRecipe>> TRANSFORM_SHAPELESS = REGISTRY.register("transform_shapeless", () -> TransformShapelessRecipe.SERIALIZER);

        public static final RegistryEntry<RecipeSerializer<ColourableRecipe>> DYEABLE_ITEM = REGISTRY.register("colour", () -> ColourableRecipe.SERIALIZER);
        public static final RegistryEntry<RecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = REGISTRY.register("clear_colour", () -> ClearColourRecipe.SERIALIZER);
        public static final RegistryEntry<RecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = REGISTRY.register("turtle_upgrade", () -> TurtleUpgradeRecipe.SERIALIZER);
        public static final RegistryEntry<RecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = REGISTRY.register("pocket_computer_upgrade", () -> PocketComputerUpgradeRecipe.SERIALIZER);
        public static final RegistryEntry<RecipeSerializer<PrintoutRecipe>> PRINTOUT = REGISTRY.register("printout", () -> PrintoutRecipe.SERIALIZER);
        public static final RegistryEntry<RecipeSerializer<DiskRecipe>> DISK = REGISTRY.register("disk", () -> DiskRecipe.SERIALIZER);
    }

    public static class RecipeFunctions {
        static final RegistrationHelper<RecipeFunction.Type<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(RecipeFunction.REGISTRY);

        private static <T extends RecipeFunction> RegistryEntry<RecipeFunction.Type<T>> register(String name, MapCodec<T> codec, StreamCodec<RegistryFriendlyByteBuf, T> streamCodec) {
            return REGISTRY.register(name, () -> new RecipeFunction.Type<>(codec, streamCodec));
        }

        public static final RegistryEntry<RecipeFunction.Type<CopyComponents>> COPY_COMPONENTS = register("copy_components", CopyComponents.CODEC, CopyComponents.STREAM_CODEC);
    }

    public static class Permissions {
        static final PermissionRegistry REGISTRY = PlatformHelper.get().createPermissionRegistry();

        public static final Predicate<CommandSourceStack> PERMISSION_DUMP = REGISTRY.registerCommand("dump", UserLevel.OWNER_OP);
        public static final Predicate<CommandSourceStack> PERMISSION_SHUTDOWN = REGISTRY.registerCommand("shutdown", UserLevel.OWNER_OP);
        public static final Predicate<CommandSourceStack> PERMISSION_TURN_ON = REGISTRY.registerCommand("turn_on", UserLevel.OWNER_OP);
        public static final Predicate<CommandSourceStack> PERMISSION_TP = REGISTRY.registerCommand("tp", UserLevel.OP);
        public static final Predicate<CommandSourceStack> PERMISSION_TRACK = REGISTRY.registerCommand("track", UserLevel.OWNER_OP);
        public static final Predicate<CommandSourceStack> PERMISSION_QUEUE = REGISTRY.registerCommand("queue", UserLevel.ANYONE);
        public static final Predicate<CommandSourceStack> PERMISSION_VIEW = REGISTRY.registerCommand("view", UserLevel.OP);
    }

    static class CreativeTabs {
        static final RegistrationHelper<CreativeModeTab> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.CREATIVE_MODE_TAB);

        @SuppressWarnings("unused")
        private static final RegistryEntry<CreativeModeTab> TAB = REGISTRY.register("tab", () -> PlatformHelper.get().newCreativeModeTab()
            .icon(() -> new ItemStack(Items.COMPUTER_NORMAL.get()))
            .title(Component.translatable("itemGroup.computercraft"))
            .displayItems((context, out) -> {
                out.accept(new ItemStack(Items.COMPUTER_NORMAL.get()));
                out.accept(new ItemStack(Items.COMPUTER_ADVANCED.get()));
                if (context.hasPermissions()) out.accept(new ItemStack(Items.COMPUTER_COMMAND.get()));
                addTurtle(out, Items.TURTLE_NORMAL.get(), context.holders());
                addTurtle(out, Items.TURTLE_ADVANCED.get(), context.holders());
                addPocket(out, Items.POCKET_COMPUTER_NORMAL.get(), context.holders());
                addPocket(out, Items.POCKET_COMPUTER_ADVANCED.get(), context.holders());

                out.accept(Items.WIRELESS_MODEM_NORMAL.get());
                out.accept(Items.WIRELESS_MODEM_ADVANCED.get());
                out.accept(Items.CABLE.get());
                out.accept(Items.WIRED_MODEM.get());
                out.accept(Items.WIRED_MODEM_FULL.get());
                out.accept(Items.REDSTONE_RELAY.get());

                out.accept(Items.MONITOR_NORMAL.get());
                out.accept(Items.MONITOR_ADVANCED.get());

                out.accept(Items.SPEAKER.get());

                out.accept(Items.PRINTER.get());
                out.accept(Items.PRINTED_PAGE.get());
                out.accept(Items.PRINTED_PAGES.get());
                out.accept(Items.PRINTED_BOOK.get());

                out.accept(Items.DISK_DRIVE.get());
                for (var colour : DyeColor.values()) {
                    out.accept(DataComponentUtil.createDyedStack(Items.DISK.get(), colour.getTextureDiffuseColor()));
                }
            })
            .build());
    }

    /**
     * Register any objects which don't have to be done on the main thread.
     */
    public static void register() {
        Blocks.REGISTRY.register();
        BlockEntities.REGISTRY.register();
        DataComponents.REGISTRY.register();
        Items.REGISTRY.register();
        TurtleUpgradeTypes.REGISTRY.register();
        PocketUpgradeTypes.REGISTRY.register();
        Menus.REGISTRY.register();
        ArgumentTypes.REGISTRY.register();
        LootItemConditionTypes.REGISTRY.register();
        RecipeSerializers.REGISTRY.register();
        RecipeFunctions.REGISTRY.register();
        Permissions.REGISTRY.register();
        CreativeTabs.REGISTRY.register();

        // Register bundled power providers
        ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider());
        ComputerCraftAPI.registerRefuelHandler(new FurnaceRefuelHandler());

        ComputerCraftAPI.registerAPIFactory(computer -> {
            var turtle = computer.getComponent(ComputerComponents.TURTLE);
            var metrics = Objects.requireNonNull(computer.getComponent(ServerComputer.METRICS));
            return turtle == null ? null : new TurtleAPI(metrics, (TurtleAccessInternal) turtle);
        });

        ComputerCraftAPI.registerAPIFactory(computer -> {
            var pocket = computer.getComponent(ComputerComponents.POCKET);
            return pocket == null ? null : new PocketAPI((PocketComputerInternal) pocket);
        });

        ComputerCraftAPI.registerAPIFactory(computer -> {
            var admin = computer.getComponent(ComputerComponents.ADMIN_COMPUTER);
            return admin == null ? null : new CommandAPI(computer, admin);
        });

        VanillaDetailRegistries.ITEM_STACK.addProvider(ItemDetails::fill);
        VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill);
        VanillaDetailRegistries.ENTITY.addProvider(EntityDetails::fill);
    }

    /**
     * Register our peripherals.
     *
     * @param peripherals The object to register our peripheral capability/lookups with.
     */
    public static void registerPeripherals(BlockComponent<IPeripheral, Direction> peripherals) {
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.COMPUTER_NORMAL.get(), (b, d) -> b.peripheral());
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.COMPUTER_ADVANCED.get(), (b, d) -> b.peripheral());
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), (b, d) -> b.peripheral());
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), (b, d) -> b.peripheral());
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.SPEAKER.get(), (b, d) -> b.peripheral());
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.PRINTER.get(), (b, d) -> b.peripheral());
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.DISK_DRIVE.get(), (b, d) -> b.peripheral());
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), (b, d) -> b.peripheral());
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), (b, d) -> b.peripheral());
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.WIRELESS_MODEM_NORMAL.get(), WirelessModemBlockEntity::getPeripheral);
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.WIRELESS_MODEM_ADVANCED.get(), WirelessModemBlockEntity::getPeripheral);
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.WIRED_MODEM_FULL.get(), WiredModemFullBlockEntity::getPeripheral);
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.CABLE.get(), CableBlockEntity::getPeripheral);
        peripherals.registerForBlockEntity(ModRegistry.BlockEntities.REDSTONE_RELAY.get(), (b, d) -> b.peripheral());

        peripherals.registerForBlockEntity(BlockEntityTypes.COMMAND_BLOCK, (b, d) -> Config.enableCommandBlock ? new CommandBlockPeripheral(b) : null);
    }

    public static void registerWiredElements(BlockComponent<WiredElement, Direction> wiredElements) {
        wiredElements.registerForBlockEntity(ModRegistry.BlockEntities.WIRED_MODEM_FULL.get(), (b, d) -> b.getElement());
        wiredElements.registerForBlockEntity(ModRegistry.BlockEntities.CABLE.get(), CableBlockEntity::getWiredElement);
    }

    /**
     * Register our custom {@link IMedia} implementations.
     *
     * @param media The object to register our media capabilities/lookups with.
     */
    public static void registerMedia(ItemComponent<IMedia> media) {
        media.registerForItems((s, c) -> MountMedia.COMPUTER,
            ModRegistry.Items.COMPUTER_NORMAL.get(), ModRegistry.Items.COMPUTER_ADVANCED.get(),
            ModRegistry.Items.TURTLE_NORMAL.get(), ModRegistry.Items.TURTLE_ADVANCED.get(),
            ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get()
        );
        media.registerForItems((s, c) -> MountMedia.DISK, ModRegistry.Items.DISK.get());
        media.registerForItems((s, c) -> TreasureDiskMedia.INSTANCE, ModRegistry.Items.TREASURE_DISK.get());
        media.registerFallback((stack, ctx) -> {
            if (stack.getItem() instanceof IMedia m) return m;
            if (stack.has(net.minecraft.core.component.DataComponents.JUKEBOX_PLAYABLE)) return RecordMedia.INSTANCE;
            return null;
        });
    }

    /**
     * An abstraction for registering capabilities/block lookups for blocks and block entities.
     *
     * @param <T> The type of the component.
     * @param <C> The context parameter to the component.
     */
    public interface BlockComponent<T, C extends @Nullable Object> {
        <B extends BlockEntity> void registerForBlockEntity(BlockEntityType<B> blockEntityType, BiFunction<? super B, C, @Nullable T> provider);
    }

    /**
     * An abstraction for registering capabilities/block lookups for items.
     *
     * @param <T> The type of the component.
     */
    public interface ItemComponent<T> {
        void registerForItems(BiFunction<ItemStack, @Nullable Void, @Nullable T> provider, ItemLike... items);

        void registerFallback(BiFunction<ItemStack, @Nullable Void, @Nullable T> provider);
    }

    private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle, HolderLookup.Provider registries) {
        out.accept(new ItemStack(turtle));
        registries.lookupOrThrow(ITurtleUpgrade.REGISTRY).listElements().forEach(x -> out.accept(
            DataComponentUtil.createStack(turtle, DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(x)),
            getUpgradeTabVisibility(x)
        ));
    }

    private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket, HolderLookup.Provider registries) {
        out.accept(new ItemStack(pocket));
        registries.lookupOrThrow(IPocketUpgrade.REGISTRY).listElements().forEach(x -> out.accept(
            DataComponentUtil.createStack(pocket, DataComponents.BACK_POCKET_UPGRADE.get(), UpgradeData.ofDefault(x)),
            getUpgradeTabVisibility(x)
        ));
    }

    private static CreativeModeTab.TabVisibility getUpgradeTabVisibility(Holder.Reference<? extends UpgradeBase> upgrade) {
        var namespace = upgrade.key().identifier().getNamespace();
        return namespace.equals("minecraft") || namespace.equals(ComputerCraftAPI.MOD_ID)
            ? CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS
            : CreativeModeTab.TabVisibility.SEARCH_TAB_ONLY;
    }
}
