(function CustomBlock() {
    const texture = "data:image/png;base64,[encoded data, 12876 characters]";
    ModAPI.meta.title("Custom Block Demo");
    ModAPI.meta.version("v1.0");
    ModAPI.meta.description("Adds a block that blows up when used.");
    ModAPI.meta.credits("By <author_name>");

    ModAPI.meta.icon(texture);

    function BlockRegistrationFunction() {
        function fixupBlockIds() { //function to correct ids for block states after registering a new item
            var blockRegistry = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.blockRegistry).getCorrective(); //get the blockregistry, corrected for weird property suffixes
            var BLOCK_STATE_IDS = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.BLOCK_STATE_IDS).getCorrective(); //get the BLOCK_STATE_IDS variable, also corrected for weird teavm suffixes
            blockRegistry.registryObjects.hashTableKToV.forEach(entry => { //Go through the key-to-value map entries of ID to Block
                if (entry) { //if the entry exists
                    var block = entry.value; //get the block
                    var validStates = block.getBlockState().getValidStates(); //get the blocks valid states
                    var stateArray = validStates.array || [validStates.element]; //get the array of valid states. TeaVM will use .array when there are multiple values, and .element when there is only one. This just accounts for edge cases.
                    stateArray.forEach(iblockstate => { //For each valid block state
                        var i = blockRegistry.getIDForObject(block.getRef()) << 4 | block.getMetaFromState(iblockstate.getRef()); //Do some bitwise math to get the id for that blockstate
                        BLOCK_STATE_IDS.put(iblockstate.getRef(), i); //Store it in the BLOCK_STATE_IDS map.
                    });
                }
            });
        }
        var ItemClass = ModAPI.reflect.getClassById("net.minecraft.item.Item");
        var BlockClass = ModAPI.reflect.getClassById("net.minecraft.block.Block");

        var blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2); //Get the super function for the block

        var creativeBlockTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs")
            .staticVariables
            .tabBlock; //The block tab in the creative inventory

        var breakBlockMethod = blockClass.methods.breakBlock.method; //Get the break block method

        function MyCustomBlock() { //Define constructor function for our custom block
            blockSuper(this, ModAPI.materials.rock.getRef()); //Call block super function with the current MyCustomBlock instance, and the material we want to use.
            this.$defaultBlockState = this.$blockState.$getBaseState(); //Set the default block state
            this.$setCreativeTab(creativeBlockTab); //Set the creative tab to the creativeBlockTab variable from earlier.
        }
        ModAPI.reflect.prototypeStack(BlockClass, MyCustomBlock); //The equivalent of `MyCustomBlock extends Block` in Java.

        //Override the breakBlock function in the custom block's prototype
        //We are using a $ prefix because the method needs to be useable by TeaVM without ModAPI's intervention. The process is fairly standard, just put a $ before the actual method's name.
        //As for the $ in the arguments, I use that to represent a raw TeaVM object.
        MyCustomBlock.prototype.$breakBlock = function ($world, $blockpos, $blockstate) {
            var world = ModAPI.util.wrap($world); //Wrap the $world argument to make it easy to use without stupid $ prefixes
            var blockpos = ModAPI.util.wrap($blockpos); //Wrap the $blockpos argument to make it easy to use without stupid $ prefixes

            world.newExplosion(
                null, //Exploding entity. This would usually be a primed TNT or a creeper, but null is used when those aren't applicable.
                blockpos.getX() + 0.5, //The X position of the explosion.
                blockpos.getY() + 0.5, //The Y position of the explosion.
                blockpos.getZ() + 0.5, //The Z position of the explosion.
                9, //The explosion strength. For reference, 3=creeper, 6=charged creeper, 5=bed in nether/end
                1, //Should the ground be set on fire after the explosion. 1=yes, 0=no
                0 //Should the explosion have smoke particles. 1=yes, 0=no
            ); //Call the newExplosion method on the world.

            return breakBlockMethod(this, $world, $blockpos, $blockstate); //Call the original breakBlock method. ( Equivalent of `super.breakBlock(world, blockpos, blockstate);` )
        }
        function internalRegistration() {
            //Make an instance of the custom block
            var custom_block = (new MyCustomBlock())
                .$setHardness(3.0) //Set the block hardness. -1 is unbreakable, 0 is instant, 0.5 is dirt, 1.5 is stone.
                .$setStepSound(BlockClass.staticVariables.soundTypePiston) //Set the step sound. For some reason, the stone sounds are named `soundTypePiston`
                .$setUnlocalizedName(
                    ModAPI.util.str("custom_block") //Set the translation ID. ModAPI.util.str() is used to convert it into a Java string
                );
            BlockClass.staticMethods.registerBlock0.method( //Use the registerBlock0 method to register the block.
                ModAPI.keygen.block("custom_block"), //Get a working numeric ID from a text block ID
                ModAPI.util.str("custom_block"), //The text block id
                custom_block //The custom block instance
            );
            ItemClass.staticMethods.registerItemBlock0.method(custom_block); //Register the block as a valid item in the inventory.
            fixupBlockIds(); //Call the fix up block IDs function to clean up the block state registry.
            ModAPI.blocks["custom_block"] = custom_block; //Define it onto the ModAPI.blocks global.

            return custom_block; //Return the function
        }

        if (ModAPI.materials) { // Check if ModAPI.materials has been initialised. If it isn't, we are on the server which loads after mods.
            return internalRegistration(); //We are on the client. Register our block and return the block's instance for texturing and model registration.
        } else {
            ModAPI.addEventListener("bootstrap", internalRegistration); //We are on the server. Attatch the internalRegistration function to the bootstrap event
        }
    }

    ModAPI.dedicatedServer.appendCode(BlockRegistrationFunction); //Run the code on the server
    var custom_block = BlockRegistrationFunction(); //Get the registered block instance

    ModAPI.addEventListener("lib:asyncsink", async () => { //Add an asyncronous listener to AsyncSink loading.
        ModAPI.addEventListener("lib:asyncsink:registeritems", (renderItem)=>{
            renderItem.registerBlock(custom_block, ModAPI.util.str("custom_block"));
        });
        AsyncSink.L10N.set("tile.custom_block.name", "My Custom Block"); //Set the name of the block

        //Required boilerplate for block and item models.
        AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/block/custom_block.json", JSON.stringify(
            {
                "parent": "block/cube_all",
                "textures": {
                    "all": "blocks/custom_block"
                }
            }
        ));
        AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/item/custom_block.json", JSON.stringify(
            {
                "parent": "block/custom_block",
                "display": {
                    "thirdperson": {
                        "rotation": [10, -45, 170],
                        "translation": [0, 1.5, -2.75],
                        "scale": [0.375, 0.375, 0.375]
                    }
                }
            }
        ));
        AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/blockstates/custom_block.json", JSON.stringify(
            {
                "variants": {
                    "normal": [
                        { "model": "custom_block" },
                    ]
                }
            }
        ));

        //Finally, fetch the texture and store it.
        AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/blocks/custom_block.png", await (await fetch(
            texture
        )).arrayBuffer());
    });
})();