package com.gregtechceu.gtceu.common.data.machines;

import com.gregtechceu.gtceu.GTCEu;
import com.gregtechceu.gtceu.api.GTValues;
import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability;
import com.gregtechceu.gtceu.api.data.RotationState;
import com.gregtechceu.gtceu.api.data.tag.TagPrefix;
import com.gregtechceu.gtceu.api.fluids.PropertyFluidFilter;
import com.gregtechceu.gtceu.api.machine.MachineDefinition;
import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition;
import com.gregtechceu.gtceu.api.machine.multiblock.CoilWorkableElectricMultiblockMachine;
import com.gregtechceu.gtceu.api.machine.multiblock.PartAbility;
import com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine;
import com.gregtechceu.gtceu.api.machine.property.GTMachineModelProperties;
import com.gregtechceu.gtceu.api.machine.trait.recipe.RecipeLogic;
import com.gregtechceu.gtceu.api.multiblock.PatternPredicate;
import com.gregtechceu.gtceu.api.multiblock.Predicates;
import com.gregtechceu.gtceu.api.multiblock.pattern.MultiblockPatternBuilder;
import com.gregtechceu.gtceu.client.renderer.machine.DynamicRenderHelper;
import com.gregtechceu.gtceu.client.util.TooltipHelper;
import com.gregtechceu.gtceu.common.block.BoilerFireboxType;
import com.gregtechceu.gtceu.common.data.*;
import com.gregtechceu.gtceu.common.machine.multiblock.electric.*;
import com.gregtechceu.gtceu.common.machine.multiblock.electric.BedrockOreMinerMachine;
import com.gregtechceu.gtceu.common.machine.multiblock.primitive.CharcoalPileIgniterMachine;
import com.gregtechceu.gtceu.common.machine.multiblock.primitive.CokeOvenMachine;
import com.gregtechceu.gtceu.common.machine.multiblock.primitive.PrimitiveBlastFurnaceMachine;
import com.gregtechceu.gtceu.common.machine.multiblock.primitive.PrimitivePumpMachine;
import com.gregtechceu.gtceu.common.machine.multiblock.steam.SteamParallelMultiblockMachine;
import com.gregtechceu.gtceu.common.mui.GTGuiTheme;
import com.gregtechceu.gtceu.common.mui.factory.CentralMonitorUIFactory;
import com.gregtechceu.gtceu.config.ConfigHolder;
import com.gregtechceu.gtceu.utils.FormattingUtil;
import com.gregtechceu.gtceu.utils.GTUtil;

import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.client.model.generators.BlockModelBuilder;
import net.minecraftforge.client.model.generators.ConfiguredModel;

import appeng.api.networking.pathing.ChannelMode;
import appeng.core.AEConfig;
import brachy.modularui.api.drawable.Text;
import brachy.modularui.value.sync.BooleanSyncValue;
import brachy.modularui.value.sync.IntSyncValue;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

import static com.gregtechceu.gtceu.api.GTValues.*;
import static com.gregtechceu.gtceu.api.multiblock.Predicates.*;
import static com.gregtechceu.gtceu.api.multiblock.util.RelativeDirection.*;
import static com.gregtechceu.gtceu.common.data.GTBlocks.*;
import static com.gregtechceu.gtceu.common.data.GTMachines.*;
import static com.gregtechceu.gtceu.common.data.GTMaterials.DrillingFluid;
import static com.gregtechceu.gtceu.common.data.GTRecipeModifiers.*;
import static com.gregtechceu.gtceu.common.data.GTRecipeTypes.DUMMY_RECIPES;
import static com.gregtechceu.gtceu.common.data.machines.GTMachineUtils.*;
import static com.gregtechceu.gtceu.common.data.models.GTMachineModels.*;
import static com.gregtechceu.gtceu.common.registry.GTRegistration.REGISTRATE;
import static com.gregtechceu.gtceu.utils.FormattingUtil.toRomanNumeral;

@SuppressWarnings("unused")
public class GTMultiMachines {

    //////////////////////////////////////
    // ******* Multiblock *******//
    //////////////////////////////////////
    public static final MultiblockMachineDefinition LARGE_BOILER_BRONZE = registerLargeBoiler(REGISTRATE, "bronze",
            CASING_BRONZE_BRICKS, CASING_BRONZE_PIPE, FIREBOX_BRONZE,
            GTCEu.id("block/casings/solid/machine_casing_bronze_plated_bricks"), BoilerFireboxType.BRONZE_FIREBOX,
            ConfigHolder.INSTANCE.machines.largeBoilers.bronzeBoilerMaxTemperature,
            ConfigHolder.INSTANCE.machines.largeBoilers.bronzeBoilerHeatSpeed);
    public static final MultiblockMachineDefinition LARGE_BOILER_STEEL = registerLargeBoiler(REGISTRATE, "steel",
            CASING_STEEL_SOLID, CASING_STEEL_PIPE, FIREBOX_STEEL,
            GTCEu.id("block/casings/solid/machine_casing_solid_steel"), BoilerFireboxType.STEEL_FIREBOX,
            ConfigHolder.INSTANCE.machines.largeBoilers.steelBoilerMaxTemperature,
            ConfigHolder.INSTANCE.machines.largeBoilers.steelBoilerHeatSpeed);
    public static final MultiblockMachineDefinition LARGE_BOILER_TITANIUM = registerLargeBoiler(REGISTRATE, "titanium",
            CASING_TITANIUM_STABLE, CASING_TITANIUM_PIPE, FIREBOX_TITANIUM,
            GTCEu.id("block/casings/solid/machine_casing_stable_titanium"), BoilerFireboxType.TITANIUM_FIREBOX,
            ConfigHolder.INSTANCE.machines.largeBoilers.titaniumBoilerMaxTemperature,
            ConfigHolder.INSTANCE.machines.largeBoilers.titaniumBoilerHeatSpeed);
    public static final MultiblockMachineDefinition LARGE_BOILER_TUNGSTENSTEEL = registerLargeBoiler(REGISTRATE,
            "tungstensteel",
            CASING_TUNGSTENSTEEL_ROBUST, CASING_TUNGSTENSTEEL_PIPE, FIREBOX_TUNGSTENSTEEL,
            GTCEu.id("block/casings/solid/machine_casing_robust_tungstensteel"),
            BoilerFireboxType.TUNGSTENSTEEL_FIREBOX,
            ConfigHolder.INSTANCE.machines.largeBoilers.tungstensteelBoilerMaxTemperature,
            ConfigHolder.INSTANCE.machines.largeBoilers.tungstensteelBoilerHeatSpeed);

    public static final MultiblockMachineDefinition COKE_OVEN = REGISTRATE.multiblock("coke_oven", CokeOvenMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.COKE_OVEN_RECIPES)
            .appearanceBlock(CASING_COKE_BRICKS)
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXX", "XXX", "XXX")
                    .slice("XXX", "X#X", "XXX")
                    .slice("XXX", "XYX", "XXX")
                    .where('X',
                            blocks(CASING_COKE_BRICKS.get()).or(blocks(COKE_OVEN_HATCH.get()).setMaxGlobalLimited(5)))
                    .where('#', Predicates.air())
                    .where('Y', Predicates.controller(blocks(definition.getBlock())))
                    .build())
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_coke_bricks"),
                    GTCEu.id("block/multiblock/coke_oven"))
            .themeId((i) -> GTGuiTheme.PRIMITIVE.getId())
            .register();

    public static final MultiblockMachineDefinition PRIMITIVE_BLAST_FURNACE = REGISTRATE
            .multiblock("primitive_blast_furnace", PrimitiveBlastFurnaceMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.PRIMITIVE_BLAST_FURNACE_RECIPES)
            .model(createWorkableCasingMachineModel(GTCEu.id("block/casings/solid/machine_primitive_bricks"),
                    GTCEu.id("block/multiblock/primitive_blast_furnace"))
                    .andThen(b -> b.addDynamicRenderer(DynamicRenderHelper::createPBFLavaRender)))
            .hasBER(true)
            .appearanceBlock(CASING_PRIMITIVE_BRICKS)
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXX", "XXX", "XXX", "XXX")
                    .slice("XXX", "X&X", "X#X", "X#X")
                    .slice("XXX", "XYX", "XXX", "XXX")
                    .where('X', blocks(CASING_PRIMITIVE_BRICKS.get()))
                    .where('#', Predicates.air())
                    .where('&', Predicates.air()
                            .or(Predicates.custom(bws -> GTUtil.isBlockSnow(bws.retrieveCurrentBlockState()) ?
                                    null : Predicates.PLACEHOLDER,
                                    null)))
                    .where('Y', Predicates.controller(blocks(definition.getBlock())))
                    .build())
            .themeId((i) -> GTGuiTheme.PRIMITIVE.getId())
            .register();

    public static final MultiblockMachineDefinition ELECTRIC_BLAST_FURNACE = REGISTRATE
            .multiblock("electric_blast_furnace", CoilWorkableElectricMultiblockMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.BLAST_RECIPES)
            .recipeModifiers(GTRecipeModifiers::ebfOverclock, BATCH_MODE)
            .appearanceBlock(CASING_INVAR_HEATPROOF)
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXX", "CCC", "CCC", "XXX")
                    .slice("XXX", "C#C", "C#C", "XMX")
                    .slice("XSX", "CCC", "CCC", "XXX")
                    .where('S', controller(blocks(definition.getBlock())))
                    .where('X', blocks(CASING_INVAR_HEATPROOF.get()).setMinGlobalLimited(9)
                            .or(autoAbilities(definition.getRecipeTypes()))
                            .or(autoAbilities(true, false, false)))
                    .where('M', abilities(PartAbility.MUFFLER))
                    .where('C', heatingCoils())
                    .where('#', air())
                    .build())
            .recoveryItems(
                    () -> new ItemLike[] {
                            GTMaterialItems.MATERIAL_ITEMS.get(TagPrefix.dustTiny, GTMaterials.Ash).get() })
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_heatproof"),
                    GTCEu.id("block/multiblock/electric_blast_furnace"))
            .tooltips(Component.translatable("gtceu.machine.electric_blast_furnace.tooltip.0"),
                    Component.translatable("gtceu.machine.electric_blast_furnace.tooltip.1"),
                    Component.translatable("gtceu.machine.electric_blast_furnace.tooltip.2"))
            .additionalDisplay((controller, syncManager) -> {
                if (!(controller instanceof CoilWorkableElectricMultiblockMachine coilMachine))
                    return Collections.emptyList();
                BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                        () -> new BooleanSyncValue(controller::isFormed));
                IntSyncValue coilTemperature = syncManager.getOrCreateSyncHandler("coilTemperature", IntSyncValue.class,
                        () -> new IntSyncValue(() -> coilMachine.getCoilType().getCoilTemperature()));
                IntSyncValue machineTier = syncManager.getOrCreateSyncHandler("machineTier", IntSyncValue.class,
                        () -> new IntSyncValue(() -> coilMachine.getTier()));

                return Collections.singletonList(Text
                        .dynamic(() -> Component.translatable("gtceu.multiblock.blast_furnace.max_temperature",
                                Component.literal(
                                        FormattingUtil.formatNumbers(coilTemperature.getIntValue() +
                                                100L * Math.max(0, machineTier.getIntValue() - GTValues.MV)) + "K")
                                        .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))))
                        .asWidget().setEnabledIf(w -> isFormed.getBoolValue()));
            })
            .register();

    public static final MultiblockMachineDefinition LARGE_CHEMICAL_REACTOR = REGISTRATE
            .multiblock("large_chemical_reactor", WorkableElectricMultiblockMachine::new)
            .conditionalTooltip(defaultEnvironmentRequirement(),
                    ConfigHolder.INSTANCE.gameplay.environmentalHazards)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.LARGE_CHEMICAL_RECIPES)
            .recipeModifiers(DEFAULT_ENVIRONMENT_REQUIREMENT, OC_PERFECT_SUBTICK, BATCH_MODE)
            .appearanceBlock(CASING_PTFE_INERT)
            .pattern(definition -> {
                var casing = blocks(CASING_PTFE_INERT.get()).setMinGlobalLimited(10);
                var abilities = Predicates.autoAbilities(definition.getRecipeTypes())
                        .or(Predicates.autoAbilities(true, false, false));
                return MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                        .slice("XXX", "XCX", "XXX")
                        .slice("XCX", "CPC", "XCX")
                        .slice("XXX", "XSX", "XXX")
                        .where('S', Predicates.controller(blocks(definition.getBlock())))
                        .where('X', casing.or(abilities))
                        .where('P', blocks(CASING_POLYTETRAFLUOROETHYLENE_PIPE.get()))
                        .where('C', Predicates.heatingCoils().setExactLimit(1)
                                .or(abilities)
                                .or(casing))
                        .build();
            })
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_inert_ptfe"),
                    GTCEu.id("block/multiblock/large_chemical_reactor"))
            .register();

    public static final MultiblockMachineDefinition IMPLOSION_COMPRESSOR = REGISTRATE
            .multiblock("implosion_compressor", WorkableElectricMultiblockMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.IMPLOSION_RECIPES)
            .recipeModifiers(OC_NON_PERFECT_SUBTICK, BATCH_MODE)
            .appearanceBlock(CASING_STEEL_SOLID)
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXX", "XXX", "XXX")
                    .slice("XXX", "X#X", "XXX")
                    .slice("XXX", "XSX", "XXX")
                    .where('S', controller(blocks(definition.get())))
                    .where('X', blocks(CASING_STEEL_SOLID.get()).setMinGlobalLimited(14)
                            .or(Predicates.autoAbilities(definition.getRecipeTypes()))
                            .or(Predicates.autoAbilities(true, true, false)))
                    .where('#', Predicates.air())
                    .build())
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_solid_steel"),
                    GTCEu.id("block/multiblock/implosion_compressor"))
            .register();

    public static final MultiblockMachineDefinition PYROLYSE_OVEN = REGISTRATE
            .multiblock("pyrolyse_oven", CoilWorkableElectricMultiblockMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.PYROLYSE_RECIPES)
            .recipeModifiers(GTRecipeModifiers::pyrolyseOvenOverclock, BATCH_MODE)
            .appearanceBlock(MACHINE_CASING_ULV)
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXX", "XXX", "XXX")
                    .slice("CCC", "C#C", "CCC")
                    .slice("CCC", "C#C", "CCC")
                    .slice("XXX", "XSX", "XXX")
                    .where('S', Predicates.controller(blocks(definition.get())))
                    .where('X',
                            blocks(MACHINE_CASING_ULV.get()).setMinGlobalLimited(6)
                                    .or(Predicates.autoAbilities(definition.getRecipeTypes()))
                                    .or(Predicates.autoAbilities(true, true, false)))
                    .where('C', Predicates.heatingCoils())
                    .where('#', Predicates.air())
                    .build())
            .workableCasingModel(GTCEu.id("block/casings/voltage/ulv/side"),
                    GTCEu.id("block/multiblock/pyrolyse_oven"))
            .tooltips(Component.translatable("gtceu.machine.pyrolyse_oven.tooltip.1"))
            .additionalDisplay((controller, syncManager) -> {
                if (!(controller instanceof CoilWorkableElectricMultiblockMachine coilMachine))
                    return Collections.emptyList();
                BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                        () -> new BooleanSyncValue(controller::isFormed));
                IntSyncValue coilTier = syncManager.getOrCreateSyncHandler("coilTier", IntSyncValue.class,
                        () -> new IntSyncValue(() -> coilMachine.getCoilTier()));

                return Collections.singletonList(Text
                        .dynamic(() -> Component.translatable("gtceu.multiblock.pyrolyse_oven.speed",
                                coilTier.getIntValue() == 0 ? 75 : 50 * (coilTier.getIntValue() + 1)))
                        .asWidget().setEnabledIf(w -> isFormed.getBoolValue()));
            })
            .register();

    public static final MultiblockMachineDefinition MULTI_SMELTER = REGISTRATE
            .multiblock("multi_smelter", CoilWorkableElectricMultiblockMachine::new)
            .rotationState(RotationState.ALL)
            .recipeTypes(GTRecipeTypes.FURNACE_RECIPES, GTRecipeTypes.ALLOY_SMELTER_RECIPES)
            .recipeModifiers(GTRecipeModifiers::multiSmelterParallel, BATCH_MODE)
            .appearanceBlock(CASING_INVAR_HEATPROOF)
            .tooltips(Component.translatable("gtceu.machine.available_recipe_map_2.tooltip",
                    Component.translatable("gtceu.electric_furnace"), Component.translatable("gtceu.alloy_smelter")))
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXX", "CCC", "XXX")
                    .slice("XXX", "C#C", "XMX")
                    .slice("XSX", "CCC", "XXX")
                    .where('S', controller(blocks(definition.get())))
                    .where('X', blocks(CASING_INVAR_HEATPROOF.get()).setMinGlobalLimited(9)
                            .or(autoAbilities(definition.getRecipeTypes()))
                            .or(autoAbilities(true, false, false)))
                    .where('M', abilities(PartAbility.MUFFLER))
                    .where('C', heatingCoils())
                    .where('#', air())
                    .build())
            .recoveryItems(
                    () -> new ItemLike[] {
                            GTMaterialItems.MATERIAL_ITEMS.get(TagPrefix.dustTiny, GTMaterials.Ash).get() })
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_heatproof"),
                    GTCEu.id("block/multiblock/multi_furnace"))
            .additionalDisplay((controller, syncManager) -> {
                if (!(controller instanceof CoilWorkableElectricMultiblockMachine coilMachine))
                    return Collections.emptyList();
                BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                        () -> new BooleanSyncValue(controller::isFormed));
                IntSyncValue coilLevel = syncManager.getOrCreateSyncHandler("coilLevel", IntSyncValue.class,
                        () -> new IntSyncValue(() -> coilMachine.getCoilType().getLevel()));
                IntSyncValue energyDiscount = syncManager.getOrCreateSyncHandler("energyDiscount", IntSyncValue.class,
                        () -> new IntSyncValue(() -> coilMachine.getCoilType().getEnergyDiscount()));

                return List.of(
                        Text.dynamic(() -> Component.translatable("gtceu.multiblock.multi_furnace.heating_coil_level",
                                coilLevel.getIntValue())).asWidget().setEnabledIf(w -> isFormed.getBoolValue()),
                        Text.dynamic(
                                () -> Component.translatable("gtceu.multiblock.multi_furnace.heating_coil_discount",
                                        energyDiscount.getIntValue()))
                                .asWidget().setEnabledIf(w -> isFormed.getBoolValue()));
            })
            .register();

    public static final MultiblockMachineDefinition CRACKER = REGISTRATE
            .multiblock("cracker", CoilWorkableElectricMultiblockMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.CRACKING_RECIPES)
            .recipeModifiers(GTRecipeModifiers::crackerOverclock, BATCH_MODE)
            .appearanceBlock(CASING_STAINLESS_CLEAN)
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("HCHCH", "HCHCH", "HCHCH")
                    .slice("HCHCH", "H###H", "HCHCH")
                    .slice("HCHCH", "HCOCH", "HCHCH")
                    .where('O', Predicates.controller(blocks(definition.get())))
                    .where('H', blocks(CASING_STAINLESS_CLEAN.get()).setMinGlobalLimited(12)
                            .or(Predicates.autoAbilities(definition.getRecipeTypes()))
                            .or(Predicates.autoAbilities(true, true, false)))
                    .where('#', Predicates.air())
                    .where('C', Predicates.heatingCoils())
                    .build())
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_clean_stainless_steel"),
                    GTCEu.id("block/multiblock/cracking_unit"))
            .tooltips(Component.translatable("gtceu.machine.cracker.tooltip.1"))
            .additionalDisplay((controller, syncManager) -> {
                if (!(controller instanceof CoilWorkableElectricMultiblockMachine coilMachine))
                    return Collections.emptyList();
                BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                        () -> new BooleanSyncValue(controller::isFormed));
                IntSyncValue coilTier = syncManager.getOrCreateSyncHandler("coilTier", IntSyncValue.class,
                        () -> new IntSyncValue(() -> coilMachine.getCoilTier()));

                return Collections.singletonList(Text
                        .dynamic(() -> Component.translatable("gtceu.multiblock.cracking_unit.energy",
                                100 - 10 * coilTier.getIntValue()))
                        .asWidget().setEnabledIf(w -> isFormed.getBoolValue()));
            })
            .register();

    public static final MultiblockMachineDefinition DISTILLATION_TOWER = REGISTRATE
            .multiblock("distillation_tower", DistillationTowerMachine::new)
            .rotationState(RotationState.NON_Y_AXIS)
            .recipeType(GTRecipeTypes.DISTILLATION_RECIPES)
            .recipeModifiers(OC_NON_PERFECT_SUBTICK, BATCH_MODE)
            .appearanceBlock(CASING_STAINLESS_CLEAN)
            .pattern(definition -> {
                PatternPredicate exportPredicate = abilities(PartAbility.EXPORT_FLUIDS_1X);
                if (GTCEu.Mods.isAE2Loaded()) {
                    exportPredicate = exportPredicate.or(blocks(GTAEMachines.FLUID_EXPORT_HATCH_ME.get()));
                }
                exportPredicate.setMaxLayerLimited(1);
                PatternPredicate maint = autoAbilities(true, false, false)
                        .setMaxGlobalLimited(1);
                return MultiblockPatternBuilder.start(UP, BACK, RIGHT)
                        .slice("YSY", "YYY", "YYY")
                        .slice("ZZZ", "Z#Z", "ZZZ")
                        .sliceRepeatable(0, 10, "XXX", "X#X", "XXX")
                        .slice("XXX", "XXX", "XXX")
                        .where('S', Predicates.controller(blocks(definition.getBlock())))
                        .where('Y', blocks(CASING_STAINLESS_CLEAN.get())
                                .or(Predicates.abilities(PartAbility.EXPORT_ITEMS).setMaxGlobalLimited(1))
                                .or(Predicates.abilities(PartAbility.INPUT_ENERGY).setMinGlobalLimited(1)
                                        .setMaxGlobalLimited(2))
                                .or(Predicates.abilities(PartAbility.IMPORT_FLUIDS).setExactLimit(1))
                                .or(maint))
                        .where('Z', blocks(CASING_STAINLESS_CLEAN.get())
                                .or(exportPredicate)
                                .or(maint))
                        .where('X', blocks(CASING_STAINLESS_CLEAN.get()).or(exportPredicate))
                        .where('#', Predicates.air())
                        .build();
            })
            .allowExtendedFacing(false)
            .partSorter(Comparator.comparingInt(p -> p.getBlockPos().getY()))
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_clean_stainless_steel"),
                    GTCEu.id("block/multiblock/distillation_tower"))
            .register();

    public static final MultiblockMachineDefinition VACUUM_FREEZER = REGISTRATE
            .multiblock("vacuum_freezer", WorkableElectricMultiblockMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.VACUUM_RECIPES)
            .recipeModifiers(GTRecipeModifiers.OC_NON_PERFECT_SUBTICK, BATCH_MODE)
            .appearanceBlock(CASING_ALUMINIUM_FROSTPROOF)
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXX", "XXX", "XXX")
                    .slice("XXX", "X#X", "XXX")
                    .slice("XXX", "XSX", "XXX")
                    .where('S', Predicates.controller(blocks(definition.getBlock())))
                    .where('X', blocks(CASING_ALUMINIUM_FROSTPROOF.get()).setMinGlobalLimited(14)
                            .or(Predicates.autoAbilities(definition.getRecipeTypes()))
                            .or(Predicates.autoAbilities(true, false, false)))
                    .where('#', Predicates.air())
                    .build())
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_frost_proof"),
                    GTCEu.id("block/multiblock/vacuum_freezer"))
            .register();

    public static final MultiblockMachineDefinition ASSEMBLY_LINE = REGISTRATE
            .multiblock("assembly_line", AssemblyLineMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.ASSEMBLY_LINE_RECIPES)
            .alwaysTryModifyRecipe(true)
            .recipeModifiers(DEFAULT_ENVIRONMENT_REQUIREMENT, OC_NON_PERFECT)
            .appearanceBlock(CASING_STEEL_SOLID)
            .pattern(definition -> MultiblockPatternBuilder.start(RIGHT, UP, BACK)
                    .slice("FIF", "RTR", "SAG", "#Y#")
                    .sliceRepeatable(3, 15, "FIF", "RTR", "DAG", "#Y#")
                    .slice("FOF", "RTR", "DAG", "#Y#")
                    .where('S', Predicates.controller(blocks(definition.getBlock())))
                    .where('F', blocks(CASING_STEEL_SOLID.get())
                            .or(!ConfigHolder.INSTANCE.machines.orderedAssemblyLineFluids ?
                                    Predicates.abilities(PartAbility.IMPORT_FLUIDS_1X,
                                            PartAbility.IMPORT_FLUIDS_4X, PartAbility.IMPORT_FLUIDS_9X) :
                                    Predicates.abilities(PartAbility.IMPORT_FLUIDS_1X).setMaxGlobalLimited(4)))
                    .where('O',
                            Predicates.abilities(PartAbility.EXPORT_ITEMS)
                                    .addTooltips(Component.translatable("gtceu.multiblock.pattern.location_end")))
                    .where('Y',
                            blocks(CASING_STEEL_SOLID.get()).or(Predicates.abilities(PartAbility.INPUT_ENERGY)
                                    .setMinGlobalLimited(1).setMaxGlobalLimited(2)))
                    .where('I', blocks(ITEM_IMPORT_BUS[0].getBlock()))
                    .where('G', blocks(CASING_GRATE.get()))
                    .where('A', blocks(CASING_ASSEMBLY_CONTROL.get()))
                    .where('R', blocks(CASING_LAMINATED_GLASS.get()))
                    .where('T', blocks(CASING_ASSEMBLY_LINE.get()))
                    .where('D', blocks(CASING_GRATE.get()).or(dataHatchPredicate()))
                    .where('#', Predicates.any())
                    .build())
            .partSorter(AssemblyLineMachine::partSorter)
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_solid_steel"),
                    GTCEu.id("block/multiblock/assembly_line"))
            .register();

    public static final MultiblockMachineDefinition PRIMITIVE_PUMP = REGISTRATE
            .multiblock("primitive_pump", PrimitivePumpMachine::new)
            .rotationState(RotationState.NON_Y_AXIS)
            .appearanceBlock(CASING_PUMP_DECK)
            .themeId(GTGuiTheme.PRIMITIVE.getId())
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXXX", "##F#", "##F#")
                    .slice("XXHX", "F##F", "FFFF")
                    .slice("SXXX", "##F#", "##F#")
                    .where('S', Predicates.controller(blocks(definition.getBlock())))
                    .where('X', blocks(CASING_PUMP_DECK.get()))
                    .where('F', Predicates.frames(GTMaterials.TreatedWood))
                    .where('H',
                            Predicates.abilities(PartAbility.PUMP_FLUID_HATCH)
                                    .or(blocks(FLUID_EXPORT_HATCH[ULV].get(), FLUID_EXPORT_HATCH[LV].get())))
                    .where('#', Predicates.any())
                    .build())
            .allowExtendedFacing(false)
            .modelProperty(GTMachineModelProperties.RECIPE_LOGIC_STATUS, RecipeLogic.Status.IDLE)
            .model(createSidedWorkableCasingMachineModel(GTCEu.id("block/casings/pump_deck"),
                    GTCEu.id("block/multiblock/primitive_pump"))
                    .andThen(builder -> {
                        // UV lock the model so the plank texture doesn't rotate weirdly
                        builder.replaceForAllStates((state, models) -> {
                            for (int i = 0; i < models.length; i++) {
                                models[i] = ConfiguredModel.builder()
                                        .modelFile(models[i].model).uvLock(true)
                                        .buildLast();
                            }
                            return models;
                        });
                    }))
            .register();

    public static final MultiblockMachineDefinition STEAM_GRINDER = REGISTRATE
            .multiblock("steam_grinder", SteamParallelMultiblockMachine::new)
            .rotationState(RotationState.ALL)
            .appearanceBlock(CASING_BRONZE_BRICKS)
            .themeId(ConfigHolder.INSTANCE.machines.steelSteamMultiblocks ? GTGuiTheme.STEEL.getId() :
                    GTGuiTheme.BRONZE.getId())
            .recipeType(GTRecipeTypes.MACERATOR_RECIPES)
            .recipeModifier(SteamParallelMultiblockMachine::recipeModifier, true)
            .addOutputLimit(ItemRecipeCapability.CAP, 1)
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXX", "XXX", "XXX")
                    .slice("XXX", "X#X", "XXX")
                    .slice("XXX", "XSX", "XXX")
                    .where('S', Predicates.controller(blocks(definition.getBlock())))
                    .where('#', Predicates.air())
                    .where('X', blocks(CASING_BRONZE_BRICKS.get()).setMinGlobalLimited(14)
                            .or(Predicates.abilities(PartAbility.STEAM_IMPORT_ITEMS).setPreviewCount(1))
                            .or(Predicates.abilities(PartAbility.STEAM_EXPORT_ITEMS).setPreviewCount(1))
                            .or(Predicates.abilities(PartAbility.STEAM).setExactLimit(1)))
                    .build())
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_bronze_plated_bricks"),
                    GTCEu.id("block/multiblock/steam_grinder"))
            .register();

    public static final MultiblockMachineDefinition STEAM_OVEN = REGISTRATE
            .multiblock("steam_oven", SteamParallelMultiblockMachine::new)
            .rotationState(RotationState.ALL)
            .appearanceBlock(CASING_BRONZE_BRICKS)
            .themeId(ConfigHolder.INSTANCE.machines.steelSteamMultiblocks ? GTGuiTheme.STEEL.getId() :
                    GTGuiTheme.BRONZE.getId())
            .recipeType(GTRecipeTypes.FURNACE_RECIPES)
            .recipeModifier(SteamParallelMultiblockMachine::recipeModifier, true)
            .addOutputLimit(ItemRecipeCapability.CAP, 1)
            .pattern(definition -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("FFF", "XXX", " X ")
                    .slice("FFF", "X#X", " X ")
                    .slice("FFF", "XSX", " X ")
                    .where('S', Predicates.controller(blocks(definition.getBlock())))
                    .where('#', Predicates.air())
                    .where(' ', Predicates.any())
                    .where('X', blocks(CASING_BRONZE_BRICKS.get()).setMinGlobalLimited(6)
                            .or(Predicates.abilities(PartAbility.STEAM_IMPORT_ITEMS).setPreviewCount(1))
                            .or(Predicates.abilities(PartAbility.STEAM_EXPORT_ITEMS).setPreviewCount(1)))
                    .where('F', blocks(FIREBOX_BRONZE.get())
                            .or(Predicates.abilities(PartAbility.STEAM).setExactLimit(1)))
                    .build())
            .modelProperty(GTMachineModelProperties.RECIPE_LOGIC_STATUS, RecipeLogic.Status.IDLE)
            .model(createWorkableCasingMachineModel(GTCEu.id("block/casings/solid/machine_casing_bronze_plated_bricks"),
                    GTCEu.id("block/multiblock/steam_oven"))
                    .andThen(b -> b.addDynamicRenderer(
                            () -> DynamicRenderHelper.makeBoilerPartRender(
                                    BoilerFireboxType.BRONZE_FIREBOX, CASING_BRONZE_BRICKS))))
            .register();

    public static final MultiblockMachineDefinition[] FUSION_REACTOR = registerTieredMultis(REGISTRATE,
            "fusion_reactor",
            FusionReactorMachine::new, (tier, builder) -> builder
                    .rotationState(RotationState.ALL)
                    .langValue("Fusion Reactor Computer MK %s".formatted(toRomanNumeral(tier - 5)))
                    .recipeType(GTRecipeTypes.FUSION_RECIPES)
                    .recipeModifiers(DEFAULT_ENVIRONMENT_REQUIREMENT,
                            FusionReactorMachine::recipeModifier, BATCH_MODE)
                    .tooltips(
                            Component.translatable("gtceu.machine.fusion_reactor.capacity",
                                    FusionReactorMachine.calculateEnergyStorageFactor(tier, 16) / 1000000L),
                            Component.translatable("gtceu.machine.fusion_reactor.overclocking"),
                            Component.translatable("gtceu.multiblock.%s_fusion_reactor.description"
                                    .formatted(VN[tier].toLowerCase(Locale.ROOT))))
                    .appearanceBlock(() -> FusionReactorMachine.getCasingState(tier))
                    .pattern((definition) -> {
                        var casing = blocks(FusionReactorMachine.getCasingState(tier));
                        return MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                                .slice("###############", "######OGO######", "###############")
                                .slice("######ICI######", "####GGAAAGG####", "######ICI######")
                                .slice("####CC###CC####", "###EAAOGOAAE###", "####CC###CC####")
                                .slice("###C#######C###", "##EKEG###GEKE##", "###C#######C###")
                                .slice("##C#########C##", "#GAE#######EAG#", "##C#########C##")
                                .slice("##C#########C##", "#GAG#######GAG#", "##C#########C##")
                                .slice("#I###########I#", "OAO#########OAO", "#I###########I#")
                                .slice("#C###########C#", "GAG#########GAG", "#C###########C#")
                                .slice("#I###########I#", "OAO#########OAO", "#I###########I#")
                                .slice("##C#########C##", "#GAG#######GAG#", "##C#########C##")
                                .slice("##C#########C##", "#GAE#######EAG#", "##C#########C##")
                                .slice("###C#######C###", "##EKEG###GEKE##", "###C#######C###")
                                .slice("####CC###CC####", "###EAAOGOAAE###", "####CC###CC####")
                                .slice("######ICI######", "####GGAAAGG####", "######ICI######")
                                .slice("###############", "######OSO######", "###############")
                                .where('S', controller(blocks(definition.get())))
                                .where('G', blocks(FUSION_GLASS.get()).or(casing))
                                .where('E', casing.or(
                                        blocks(PartAbility.INPUT_ENERGY.getBlockRange(tier, UV).toArray(Block[]::new))
                                                .setMinGlobalLimited(1).setPreviewCount(16)))
                                .where('C', casing)
                                .where('K', blocks(FusionReactorMachine.getCoilState(tier)))
                                .where('O', casing.or(abilities(PartAbility.EXPORT_FLUIDS)))
                                .where('A', air())
                                .where('I', casing.or(abilities(PartAbility.IMPORT_FLUIDS).setMinGlobalLimited(2)))
                                .where('#', any())
                                .build();
                    })
                    .modelProperty(GTMachineModelProperties.RECIPE_LOGIC_STATUS, RecipeLogic.Status.IDLE)
                    .model(createWorkableCasingMachineModel(FusionReactorMachine.getCasingType(tier).getTexture(),
                            GTCEu.id("block/multiblock/fusion_reactor"))
                            .andThen(b -> b.addDynamicRenderer(DynamicRenderHelper::createFusionRingRender)))
                    .hasBER(true)
                    .register(),
            LuV, ZPM, UV);

    public static final MultiblockMachineDefinition[] FLUID_DRILLING_RIG = registerTieredMultis(REGISTRATE,
            "fluid_drilling_rig", FluidDrillMachine::new, (tier, builder) -> builder
                    .rotationState(RotationState.ALL)
                    .langValue("%s Fluid Drilling Rig %s".formatted(VLVH[tier], VLVT[tier]))
                    .recipeType(DUMMY_RECIPES)
                    .tooltips(
                            Component.translatable("gtceu.machine.fluid_drilling_rig.description"),
                            Component.translatable("gtceu.machine.fluid_drilling_rig.depletion",
                                    FormattingUtil.formatNumbers(100.0 / FluidDrillMachine.getDepletionChance(tier))),
                            Component.translatable("gtceu.universal.tooltip.energy_tier_range", GTValues.VNF[tier],
                                    GTValues.VNF[tier + 1]),
                            Component.translatable("gtceu.machine.fluid_drilling_rig.production",
                                    FluidDrillMachine.getRigMultiplier(tier),
                                    FormattingUtil.formatNumbers(FluidDrillMachine.getRigMultiplier(tier) * 1.5)))
                    .appearanceBlock(() -> FluidDrillMachine.getCasingState(tier))
                    .pattern((definition) -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                            .slice("XXX", "#F#", "#F#", "#F#", "###", "###", "###")
                            .slice("XXX", "FCF", "FCF", "FCF", "#F#", "#F#", "#F#")
                            .slice("XSX", "#F#", "#F#", "#F#", "###", "###", "###")
                            .where('S', controller(blocks(definition.get())))
                            .where('X', blocks(FluidDrillMachine.getCasingState(tier)).setMinGlobalLimited(3)
                                    .or(abilities(PartAbility.INPUT_ENERGY).setMinGlobalLimited(1)
                                            .setMaxGlobalLimited(2))
                                    .or(abilities(PartAbility.EXPORT_FLUIDS).setMaxGlobalLimited(1)))
                            .where('C', blocks(FluidDrillMachine.getCasingState(tier)))
                            .where('F', frames(FluidDrillMachine.getFrameMaterial(tier)))
                            .where('#', any())
                            .build())
                    .workableCasingModel(FluidDrillMachine.getBaseTexture(tier),
                            GTCEu.id("block/multiblock/fluid_drilling_rig"))
                    .register(),
            MV, HV, EV);

    public static final MultiblockMachineDefinition[] LARGE_MINER = registerTieredMultis(REGISTRATE, "large_miner",
            (holder, tier) -> new LargeMinerMachine(holder, tier, 64 / tier, 2 * tier - 5, tier, 8 - (tier - 5)),
            (tier, builder) -> builder
                    .rotationState(RotationState.NON_Y_AXIS)
                    .langValue("%s Large Miner %s".formatted(VLVH[tier], VLVT[tier]))
                    .recipeType(GTRecipeTypes.MACERATOR_RECIPES)
                    .appearanceBlock(() -> LargeMinerMachine.getCasingState(tier))
                    .pattern((definition) -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                            .slice("XXX", "#F#", "#F#", "#F#", "###", "###", "###")
                            .slice("XXX", "FCF", "FCF", "FCF", "#F#", "#F#", "#F#")
                            .slice("XSX", "#F#", "#F#", "#F#", "###", "###", "###")
                            .where('S', controller(blocks(definition.getBlock())))
                            .where('X', blocks(LargeMinerMachine.getCasingState(tier))
                                    .or(abilities(PartAbility.EXPORT_ITEMS).setExactLimit(1).setPreviewCount(1))
                                    .or(abilities(PartAbility.IMPORT_FLUIDS).setExactLimit(1).setPreviewCount(1))
                                    .or(abilities(PartAbility.INPUT_ENERGY).setMinGlobalLimited(1)
                                            .setMaxGlobalLimited(2).setPreviewCount(1)))
                            .where('C', blocks(LargeMinerMachine.getCasingState(tier)))
                            .where('F', frames(LargeMinerMachine.getMaterial(tier)))
                            .where('#', any())
                            .build())
                    .allowExtendedFacing(true)
                    .modelProperty(GTMachineModelProperties.RECIPE_LOGIC_STATUS, RecipeLogic.Status.IDLE)
                    .model(createWorkableCasingMachineModel(
                            MATERIALS_TO_CASING_TEXTURES.get(LargeMinerMachine.getMaterial(tier)),
                            GTCEu.id("block/multiblock/large_miner"))
                            .andThen((ctx, prov, modelBuilder) -> {
                                // replace the parent model for the formed large miner
                                modelBuilder.replaceForAllStates((state, models) -> {
                                    if (!state.getValue(GTMachineModelProperties.IS_FORMED)) {
                                        return models;
                                    }

                                    var parentModel = prov.models()
                                            .getExistingFile(GTCEu.id("block/machine/large_miner_active"));
                                    for (ConfiguredModel model : models) {
                                        ((BlockModelBuilder) model.model).parent(parentModel);
                                    }
                                    return models;
                                });
                            }))
                    .tooltips(
                            Component.translatable("gtceu.machine.large_miner.%s.tooltip"
                                    .formatted(VN[tier].toLowerCase(Locale.ROOT))),
                            Component.translatable("gtceu.machine.miner.multi.description"))
                    .tooltipBuilder((stack, tooltip) -> {
                        int workingAreaChunks = (2 * tier - 5);
                        tooltip.add(Component.translatable("gtceu.machine.miner.multi.modes"));
                        tooltip.add(Component.translatable("gtceu.machine.miner.multi.production"));
                        tooltip.add(Component.translatable("gtceu.machine.miner.fluid_usage", 8 - (tier - 5),
                                DrillingFluid.getLocalizedName()));
                        tooltip.add(Component.translatable("gtceu.universal.tooltip.working_area_chunks",
                                workingAreaChunks, workingAreaChunks));
                        tooltip.add(Component.translatable("gtceu.universal.tooltip.energy_tier_range",
                                GTValues.VNF[tier], GTValues.VNF[tier + 1]));
                    })
                    .register(),
            EV, IV, LuV);

    public static final MultiblockMachineDefinition CLEANROOM = REGISTRATE
            .multiblock("cleanroom", CleanroomMachine::new)
            .rotationState(RotationState.NONE)
            .recipeType(DUMMY_RECIPES)
            .appearanceBlock(PLASTCRETE)
            .tooltips(Component.translatable("gtceu.machine.cleanroom.tooltip.0"),
                    Component.translatable("gtceu.machine.cleanroom.tooltip.1"),
                    Component.translatable("gtceu.machine.cleanroom.tooltip.2"),
                    Component.translatable("gtceu.machine.cleanroom.tooltip.3"))
            .tooltipBuilder((stack, tooltip) -> {
                if (GTUtil.isCtrlDown()) {
                    tooltip.add(Component.empty());
                    tooltip.add(Component.translatable("gtceu.machine.cleanroom.tooltip.4"));
                    tooltip.add(Component.translatable("gtceu.machine.cleanroom.tooltip.5"));
                    tooltip.add(Component.translatable("gtceu.machine.cleanroom.tooltip.6"));
                    tooltip.add(Component.translatable("gtceu.machine.cleanroom.tooltip.7"));
                    // tooltip.add(Component.translatable("gtceu.machine.cleanroom.tooltip.8"));
                    if (GTCEu.Mods.isAE2Loaded()) {
                        tooltip.add(
                                Component.translatable(AEConfig.instance().getChannelMode() == ChannelMode.INFINITE ?
                                        "gtceu.machine.cleanroom.tooltip.ae2.no_channels" :
                                        "gtceu.machine.cleanroom.tooltip.ae2.channels"));
                    }
                    tooltip.add(Component.empty());
                } else {
                    tooltip.add(Component.translatable("gtceu.machine.cleanroom.tooltip.hold_ctrl"));
                }
            })
            .pattern(CleanroomMachine.getPattern())
            .allowExtendedFacing(false)
            .allowFlip(false)
            .workableCasingModel(GTCEu.id("block/casings/cleanroom/plascrete"),
                    GTCEu.id("block/multiblock/cleanroom"))
            .register();

    public static final MultiblockMachineDefinition LARGE_COMBUSTION_ENGINE = registerLargeCombustionEngine(REGISTRATE,
            "large_combustion_engine", EV,
            CASING_TITANIUM_STABLE, CASING_TITANIUM_GEARBOX, CASING_ENGINE_INTAKE,
            GTCEu.id("block/casings/solid/machine_casing_stable_titanium"),
            GTCEu.id("block/multiblock/generator/large_combustion_engine"));

    public static final MultiblockMachineDefinition EXTREME_COMBUSTION_ENGINE = registerLargeCombustionEngine(
            REGISTRATE,
            "extreme_combustion_engine", IV,
            CASING_TUNGSTENSTEEL_ROBUST, CASING_TUNGSTENSTEEL_GEARBOX, CASING_EXTREME_ENGINE_INTAKE,
            GTCEu.id("block/casings/solid/machine_casing_robust_tungstensteel"),
            GTCEu.id("block/multiblock/generator/extreme_combustion_engine"));

    public static final MultiblockMachineDefinition LARGE_STEAM_TURBINE = registerLargeTurbine(REGISTRATE,
            "steam_large_turbine",
            HV,
            GTRecipeTypes.STEAM_TURBINE_FUELS,
            CASING_STEEL_TURBINE, CASING_STEEL_GEARBOX,
            GTCEu.id("block/casings/mechanic/machine_casing_turbine_steel"),
            GTCEu.id("block/multiblock/generator/large_steam_turbine"),
            false);

    public static final MultiblockMachineDefinition LARGE_GAS_TURBINE = registerLargeTurbine(REGISTRATE,
            "gas_large_turbine",
            EV,
            GTRecipeTypes.GAS_TURBINE_FUELS,
            CASING_STAINLESS_TURBINE, CASING_STAINLESS_STEEL_GEARBOX,
            GTCEu.id("block/casings/mechanic/machine_casing_turbine_stainless_steel"),
            GTCEu.id("block/multiblock/generator/large_gas_turbine"),
            true);

    public static final MultiblockMachineDefinition LARGE_PLASMA_TURBINE = registerLargeTurbine(REGISTRATE,
            "plasma_large_turbine",
            IV,
            GTRecipeTypes.PLASMA_GENERATOR_FUELS,
            CASING_TUNGSTENSTEEL_TURBINE, CASING_TUNGSTENSTEEL_GEARBOX,
            GTCEu.id("block/casings/mechanic/machine_casing_turbine_tungstensteel"),
            GTCEu.id("block/multiblock/generator/large_plasma_turbine"),
            false);

    public static final MultiblockMachineDefinition ACTIVE_TRANSFORMER = REGISTRATE
            .multiblock("active_transformer", ActiveTransformerMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.DUMMY_RECIPES)
            .appearanceBlock(HIGH_POWER_CASING)
            .tooltips(Component.translatable("gtceu.machine.active_transformer.tooltip.0"),
                    Component.translatable("gtceu.machine.active_transformer.tooltip.1"))
            .tooltipBuilder(
                    (stack,
                     components) -> components.add(Component.translatable("gtceu.machine.active_transformer.tooltip.2")
                             .append(Component.translatable("gtceu.machine.active_transformer.tooltip.3")
                                     .withStyle(TooltipHelper.RAINBOW_HSL_SLOW))))
            .pattern((definition) -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                    .slice("XXX", "XXX", "XXX")
                    .slice("XXX", "XCX", "XXX")
                    .slice("XXX", "XSX", "XXX")
                    .where('S', controller(blocks(definition.getBlock())))
                    .where('X', blocks(GTBlocks.HIGH_POWER_CASING.get()).setMinGlobalLimited(12)
                            .or(ActiveTransformerMachine.getHatchPredicates()))
                    .where('C', blocks(GTBlocks.SUPERCONDUCTING_COIL.get()))
                    .build())
            .workableCasingModel(GTCEu.id("block/casings/hpca/high_power_casing"),
                    GTCEu.id("block/multiblock/data_bank"))
            .register();

    public static final MultiblockMachineDefinition POWER_SUBSTATION = REGISTRATE
            .multiblock("power_substation", PowerSubstationMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(GTRecipeTypes.DUMMY_RECIPES)
            .tooltips(Component.translatable("gtceu.machine.power_substation.tooltip.0"),
                    Component.translatable("gtceu.machine.power_substation.tooltip.1"),
                    Component.translatable("gtceu.machine.power_substation.tooltip.2",
                            PowerSubstationMachine.MAX_BATTERY_LAYERS),
                    Component.translatable("gtceu.machine.power_substation.tooltip.3"),
                    Component.translatable("gtceu.machine.power_substation.tooltip.4",
                            PowerSubstationMachine.PASSIVE_DRAIN_MAX_PER_STORAGE / 1000))
            .tooltipBuilder(
                    (stack,
                     components) -> components.add(Component.translatable("gtceu.machine.power_substation.tooltip.5")
                             .append(Component.translatable("gtceu.machine.power_substation.tooltip.6")
                                     .withStyle(TooltipHelper.RAINBOW_HSL_SLOW))))
            .appearanceBlock(CASING_PALLADIUM_SUBSTATION)
            .pattern(definition -> MultiblockPatternBuilder.start(UP, BACK, RIGHT)
                    .slice("XXSXX", "XXXXX", "XXXXX", "XXXXX", "XXXXX")
                    .slice("XXXXX", "XCCCX", "XCCCX", "XCCCX", "XXXXX")
                    .sliceRepeatable(1, PowerSubstationMachine.MAX_BATTERY_LAYERS, "GGGGG", "GBBBG", "GBBBG", "GBBBG",
                            "GGGGG")
                    .slice("GGGGG", "GGGGG", "GGGGG", "GGGGG", "GGGGG")
                    .where('S', controller(blocks(definition.getBlock())))
                    .where('C', blocks(CASING_PALLADIUM_SUBSTATION.get()))
                    .where('X',
                            blocks(CASING_PALLADIUM_SUBSTATION.get())
                                    .setMinGlobalLimited(PowerSubstationMachine.MIN_CASINGS)
                                    .or(autoAbilities(true, false, false))
                                    .or(abilities(PartAbility.INPUT_ENERGY, PartAbility.SUBSTATION_INPUT_ENERGY,
                                            PartAbility.INPUT_LASER).setMinGlobalLimited(1))
                                    .or(abilities(PartAbility.OUTPUT_ENERGY, PartAbility.SUBSTATION_OUTPUT_ENERGY,
                                            PartAbility.OUTPUT_LASER).setMinGlobalLimited(1)))
                    .where('G', blocks(CASING_LAMINATED_GLASS.get()))
                    .where('B', Predicates.powerSubstationBatteries())
                    .build())
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_palladium_substation"),
                    GTCEu.id("block/multiblock/power_substation"))
            .register();

    public static final MultiblockMachineDefinition CHARCOAL_PILE_IGNITER = REGISTRATE
            .multiblock("charcoal_pile_igniter", CharcoalPileIgniterMachine::new)
            .rotationState(RotationState.NONE)
            .recipeType(DUMMY_RECIPES)
            .appearanceBlock(BRONZE_HULL)
            .tooltips(Component.translatable("gtceu.machine.charcoal_pile.tooltip.0"),
                    Component.translatable("gtceu.machine.charcoal_pile.tooltip.1"),
                    Component.translatable("gtceu.machine.charcoal_pile.tooltip.2"),
                    Component.translatable("gtceu.machine.charcoal_pile.tooltip.3"))
            .pattern(CharcoalPileIgniterMachine.getPattern())
            .allowFlip(false)
            .allowExtendedFacing(false)
            .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_bronze_plated_bricks"),
                    GTCEu.id("block/multiblock/charcoal_pile_igniter"))
            .register();

    public static MultiblockMachineDefinition[] BEDROCK_ORE_MINER = registerTieredMultis(REGISTRATE,
            "bedrock_ore_miner", BedrockOreMinerMachine::new, (tier, builder) -> builder
                    .rotationState(RotationState.NON_Y_AXIS)
                    .langValue("%s Bedrock Ore Miner %s".formatted(VLVH[tier], VLVT[tier]))
                    .recipeType(DUMMY_RECIPES)
                    .tooltips(
                            Component.translatable("gtceu.machine.bedrock_ore_miner.description"),
                            Component.translatable("gtceu.machine.bedrock_ore_miner.depletion",
                                    FormattingUtil.formatNumbers(
                                            100.0 / BedrockOreMinerMachine.getDepletionChance(tier))),
                            Component.translatable("gtceu.universal.tooltip.energy_tier_range",
                                    GTValues.VNF[tier], GTValues.VNF[tier + 1]),
                            Component.translatable("gtceu.machine.bedrock_ore_miner.production",
                                    BedrockOreMinerMachine.getRigMultiplier(tier),
                                    FormattingUtil.formatNumbers(
                                            BedrockOreMinerMachine.getRigMultiplier(tier) * 1.5)))
                    .appearanceBlock(() -> BedrockOreMinerMachine.getCasingState(tier))
                    .pattern((definition) -> MultiblockPatternBuilder.start(FRONT, UP, RIGHT)
                            .slice("XXX", "#F#", "#F#", "#F#", "###", "###", "###")
                            .slice("XXX", "FCF", "FCF", "FCF", "#F#", "#F#", "#F#")
                            .slice("XSX", "#F#", "#F#", "#F#", "###", "###", "###")
                            .where('S', controller(blocks(definition.get())))
                            .where('X',
                                    blocks(BedrockOreMinerMachine.getCasingState(tier)).setMinGlobalLimited(3)
                                            .or(abilities(PartAbility.INPUT_ENERGY).setMinGlobalLimited(1)
                                                    .setMaxGlobalLimited(2))
                                            .or(abilities(PartAbility.EXPORT_ITEMS).setMaxGlobalLimited(1)))
                            .where('C', blocks(BedrockOreMinerMachine.getCasingState(tier)))
                            .where('F', frames(BedrockOreMinerMachine.getFrameMaterial(tier)))
                            .where('#', any())
                            .build())
                    .workableCasingModel(BedrockOreMinerMachine.getBaseTexture(tier),
                            GTCEu.id("block/multiblock/bedrock_ore_miner"))
                    .register(),
            MV, HV, EV);

    // Multiblock Tanks
    public static final MachineDefinition WOODEN_TANK_VALVE = GTMachineUtils.registerTankValve(REGISTRATE,
            "wooden_tank_valve", "Wooden Tank Valve", false,
            (builder, overlay) -> builder.sidedWorkableCasingModel(GTCEu.id("block/casings/wood_wall"), overlay));
    public static final MultiblockMachineDefinition WOODEN_MULTIBLOCK_TANK = registerMultiblockTank(REGISTRATE,
            "wooden_multiblock_tank", "Wooden Multiblock Tank", 250 * 1000,
            CASING_WOOD_WALL, WOODEN_TANK_VALVE::getBlock,
            new PropertyFluidFilter(340, false, false, false, false),
            (builder, overlay) -> builder.sidedWorkableCasingModel(GTCEu.id("block/casings/wood_wall"), overlay));

    public static final MachineDefinition BRONZE_TANK_VALVE = GTMachineUtils.registerTankValve(REGISTRATE,
            "bronze_tank_valve", "Bronze Tank Valve", true,
            (builder, overlay) -> builder
                    .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_bronze_plated_bricks"), overlay));
    public static final MultiblockMachineDefinition BRONZE_MULTIBLOCK_TANK = registerMultiblockTank(REGISTRATE,
            "bronze_multiblock_tank", "Bronze Multiblock Tank", 500 * 1000,
            CASING_BRONZE_BRICKS, BRONZE_TANK_VALVE::getBlock,
            new PropertyFluidFilter(1696, true, false, false, false),
            (builder, overlay) -> builder
                    .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_bronze_plated_bricks"), overlay));

    public static final MachineDefinition STEEL_TANK_VALVE = GTMachineUtils.registerTankValve(REGISTRATE,
            "steel_tank_valve", "Steel Tank Valve", true,
            (builder, overlay) -> builder.workableCasingModel(
                    GTCEu.id("block/casings/solid/machine_casing_solid_steel"), overlay));
    public static final MultiblockMachineDefinition STEEL_MULTIBLOCK_TANK = registerMultiblockTank(REGISTRATE,
            "steel_multiblock_tank", "Steel Multiblock Tank", 1000 * 1000,
            CASING_STEEL_SOLID, STEEL_TANK_VALVE::getBlock,
            null,
            (builder, overlay) -> builder.workableCasingModel(
                    GTCEu.id("block/casings/solid/machine_casing_solid_steel"), overlay));

    public static final MultiblockMachineDefinition CENTRAL_MONITOR = REGISTRATE
            .multiblock("central_monitor", CentralMonitorMachine::new)
            .rotationState(RotationState.ALL)
            .recipeType(DUMMY_RECIPES)
            .appearanceBlock(CASING_ALUMINIUM_FROSTPROOF)
            .pattern(CentralMonitorMachine::getPattern)
            .modelProperty(RecipeLogic.STATUS_PROPERTY, RecipeLogic.Status.IDLE)
            .model(createWorkableCasingMachineModel(
                    GTCEu.id("block/casings/solid/machine_casing_frost_proof"),
                    GTCEu.id("block/multiblock/central_monitor"))
                    .andThen(b -> b.addDynamicRenderer(DynamicRenderHelper::createCentralMonitorRender)))
            .hasBER(true)
            .ui(CentralMonitorUIFactory.INSTANCE)
            .register();

    public static void init() {}
}
