package com.gregtechceu.gtceu.common.mui;

import com.gregtechceu.gtceu.api.GTValues;
import com.gregtechceu.gtceu.api.capability.recipe.FluidRecipeCapability;
import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability;
import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine;
import com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine;
import com.gregtechceu.gtceu.api.machine.multiblock.WorkableMultiblockMachine;
import com.gregtechceu.gtceu.api.machine.steam.SteamEnergyRecipeHandler;
import com.gregtechceu.gtceu.api.multiblock.error.PatternError;
import com.gregtechceu.gtceu.api.recipe.GTRecipe;
import com.gregtechceu.gtceu.api.recipe.content.Content;
import com.gregtechceu.gtceu.api.recipe.ingredient.IntProviderFluidIngredient;
import com.gregtechceu.gtceu.api.recipe.ingredient.IntProviderIngredient;
import com.gregtechceu.gtceu.client.renderer.AABBHighlightRenderer;
import com.gregtechceu.gtceu.utils.FormattingUtil;
import com.gregtechceu.gtceu.utils.GTUtil;

import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.fluids.FluidStack;

import brachy.modularui.api.drawable.Text;
import brachy.modularui.drawable.FluidDrawable;
import brachy.modularui.drawable.ItemDrawable;
import brachy.modularui.screen.RichTooltip;
import brachy.modularui.screen.viewport.ModularGuiContext;
import brachy.modularui.utils.Alignment;
import brachy.modularui.value.sync.*;
import brachy.modularui.widget.Widget;
import brachy.modularui.widgets.ButtonWidget;
import brachy.modularui.widgets.TextWidget;
import brachy.modularui.widgets.dynamic.DynamicWidget;
import brachy.modularui.widgets.layout.Flow;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;

public class GTMultiblockTextUtil {

    public static DynamicWidget<?> addUnformedWarning(MultiblockControllerMachine controller,
                                                      PanelSyncManager syncManager) {
        BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                () -> new BooleanSyncValue(controller::isFormed));
        BooleanSyncValue hasSyncError = syncManager.getOrCreateSyncHandler("hasSyncError", BooleanSyncValue.class,
                () -> new BooleanSyncValue(
                        () -> controller.getPatternState(MultiblockControllerMachine.DEFAULT_STRUCTURE).getErrors() !=
                                null));
        GenericListSyncHandler<PatternError> patternErrors = syncManager.getOrCreateSyncHandler("patternErrors",
                GenericListSyncHandler.class,
                () -> GenericListSyncHandler.<PatternError>builder()
                        .getter(() -> {
                            var list = new ArrayList<PatternError>();
                            for (String structureName : controller.getStructurePatterns().keySet()) {
                                var errors = controller.getPatternState(structureName)
                                        .getErrors();
                                if (errors != null && !errors.isEmpty()) {
                                    list.addAll(errors);
                                }
                            }
                            return list;
                        })
                        .adapter(GTByteBufAdapters.PATTERN_ERRORS)
                        .build());

        DynamicLinkedSyncHandler<GenericListSyncHandler<PatternError>> dynamicLinkedSyncHandler = new DynamicLinkedSyncHandler<>(
                patternErrors)
                .widgetProvider((widgetSyncManager, listSyncHandler) -> {
                    Flow unformed = Flow.col().coverChildrenHeight()
                            .collapseDisabledChildren()
                            .crossAxisAlignment(Alignment.CrossAxis.START)
                            .widthRel(1);

                    unformed.child(Text.lang("gtceu.multiblock.invalid_structure")
                            .withStyle(ChatFormatting.RED)
                            .asWidget()
                            .setEnabledIf(w -> !listSyncHandler.getValue().isEmpty()));
                    if (listSyncHandler.getValue().isEmpty()) {
                        return unformed;
                    }
                    BlockPos pos = listSyncHandler.getValue().stream().toList().get(0).getPos();
                    unformed.child(new ButtonWidget<>()
                            .onMousePressed((c, b) -> {
                                ((ModularGuiContext) c).getScreen().getMainPanel().closeIfOpen();
                                AABBHighlightRenderer.INSTANCE.addHighlight(AABBHighlightRenderer.builder()
                                        .aabb(pos)
                                        .colorARGB(255, 255, 0, 0)
                                        .thickness(0.025)
                                        .durationMillis(10000)
                                        .phaseMillis(300)
                                        .build());
                                return true;
                            })
                            .setEnabledIf(w -> pos != null)
                            .tooltip(r -> r.add("Highlight the missing predicate in world")));

                    for (var error : listSyncHandler.getValue()) {
                        error.getPatternErrorUIModifier().apply(unformed);
                    }
                    return unformed;
                });

        return new DynamicWidget<>()
                .widthRel(1)
                .coverChildrenHeight()
                .setEnabledIf(w -> hasSyncError.getBoolValue())
                .syncHandler(dynamicLinkedSyncHandler);
    }

    public static TextWidget<?> addEnergyUsageLine(WorkableElectricMultiblockMachine weMachine,
                                                   PanelSyncManager syncManager) {
        LongSyncValue energyUsage = syncManager.getOrCreateSyncHandler("energyUsage", LongSyncValue.class,
                () -> new LongSyncValue(() -> {
                    var energyList = weMachine.getEnergyContainer();
                    return Math.max(energyList.getInputVoltage(), energyList.getOutputVoltage());
                }));
        BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                () -> new BooleanSyncValue(weMachine::isFormed));
        BooleanSyncValue isActive = syncManager.getOrCreateSyncHandler("isActive", BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> weMachine.getRecipeLogic().isActive()));

        var widget = Text.dynamic(() -> {
            String energyFormatted = FormattingUtil.formatNumbers(energyUsage.getLongValue());

            byte voltageTier = GTUtil.getFloorTierByVoltage(energyUsage.getLongValue());
            Component voltageName = Component.literal(
                    GTValues.VNF[voltageTier]);

            MutableComponent bodyText = Component.translatable("gtceu.multiblock.max_energy_per_tick",
                    energyFormatted, voltageName).withStyle(ChatFormatting.GRAY);
            Component hoverText = Component.translatable("gtceu.multiblock.max_energy_per_tick_hover")
                    .withStyle(ChatFormatting.GRAY);
            return bodyText
                    .withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverText)))
                    .withStyle(ChatFormatting.WHITE);
        })
                .asWidget()
                .setEnabledIf($ -> isFormed.getBoolValue() && isActive.getBoolValue());

        return widget;
    }

    public static TextWidget<?> addEnergyUsageExactLine(WorkableElectricMultiblockMachine weMachine,
                                                        PanelSyncManager syncManager) {
        LongSyncValue energyUsage = syncManager.getOrCreateSyncHandler("energyUsage", LongSyncValue.class,
                () -> new LongSyncValue(() -> {
                    var energyList = weMachine.getEnergyContainer();
                    return Math.max(energyList.getInputVoltage(), energyList.getOutputVoltage());
                }));
        return addEnergyUsageExactLine(weMachine, syncManager, energyUsage);
    }

    public static TextWidget<?> addEnergyUsageExactLine(WorkableElectricMultiblockMachine weMachine,
                                                        PanelSyncManager syncManager, LongSyncValue energyUsage) {
        BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                () -> new BooleanSyncValue(weMachine::isFormed));

        return Text.dynamic(() -> {
            if (energyUsage.getLongValue() <= 0) return Component.empty();
            String energyFormatted = FormattingUtil.formatNumbers(energyUsage.getLongValue());
            // wrap in text component to keep it from being formatted
            Component voltageName = Component.literal(
                    GTValues.VNF[GTUtil.getTierByVoltage(energyUsage.getLongValue())]);

            return Component.translatable("gtceu.multiblock.energy_consumption",
                    energyFormatted, voltageName).withStyle(ChatFormatting.GRAY);
        })
                .asWidget()
                .setEnabledIf(widget -> isFormed.getBoolValue());
    }

    public static Component addEnergyTierLine(boolean formed, int tier) {
        if (!formed || tier < GTValues.ULV || tier > GTValues.MAX)
            return Text.EMPTY;

        Component voltageName = Component.literal(GTValues.VNF[tier]);
        MutableComponent bodyText = Component.translatable(
                "gtceu.multiblock.max_recipe_tier",
                voltageName).withStyle(ChatFormatting.GRAY);
        Component hoverText = Component.translatable("gtceu.multiblock.max_recipe_tier_hover")
                .withStyle(ChatFormatting.GRAY);
        return Text.dynamic(() -> bodyText
                .withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverText))));
    }

    public static TextWidget<?> addProgressLine(WorkableMultiblockMachine rlMachine, PanelSyncManager syncManager) {
        BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                () -> new BooleanSyncValue(rlMachine::isFormed));

        BooleanSyncValue isActive = syncManager.getOrCreateSyncHandler("isActive", BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> rlMachine.getRecipeLogic().isActive()));
        IntSyncValue currentProgress = syncManager.getOrCreateSyncHandler("currentProgress", IntSyncValue.class,
                () -> new IntSyncValue(() -> rlMachine.getRecipeLogic().getProgress()));
        IntSyncValue maxProgress = syncManager.getOrCreateSyncHandler("maxProgress", IntSyncValue.class,
                () -> new IntSyncValue(() -> rlMachine.getRecipeLogic().getMaxProgress()));
        DoubleSyncValue progressPercent = syncManager.getOrCreateSyncHandler("progressPercent", DoubleSyncValue.class,
                () -> new DoubleSyncValue(() -> rlMachine.getRecipeLogic().getProgressPercent()));

        return Text.dynamic(() -> {
            int progress = (int) (progressPercent.getDoubleValue() * 100.f);
            float current = (float) currentProgress.getDoubleValue() / 20.f;
            float max = (float) maxProgress.getDoubleValue() / 20.f;
            return Component.translatable("gtceu.multiblock.progress",
                    String.format("%.2f", current), String.format("%.2f", max), progress)
                    .withStyle(ChatFormatting.WHITE);
        })
                .asWidget()
                .setEnabledIf(widget -> isFormed.getBoolValue() && isActive.getBoolValue());
    }

    public static TextWidget<?> addProgressLinePercentOnly(WorkableMultiblockMachine rlMachine,
                                                           PanelSyncManager syncManager) {
        BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                () -> new BooleanSyncValue(rlMachine::isFormed));
        BooleanSyncValue isActive = syncManager.getOrCreateSyncHandler("isActive", BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> rlMachine.getRecipeLogic().isActive()));
        DoubleSyncValue progressPercent = syncManager.getOrCreateSyncHandler("progressPercent", DoubleSyncValue.class,
                () -> new DoubleSyncValue(() -> rlMachine.getRecipeLogic().getProgressPercent()));

        return Text.dynamic(() -> {
            int currentProgress = (int) (progressPercent.getDoubleValue() * 100);
            return Component.translatable("gtceu.multiblock.progress_percent", currentProgress)
                    .withStyle(ChatFormatting.WHITE);
        })
                .asWidget()
                .setEnabledIf(widget -> isFormed.getBoolValue() && isActive.getBoolValue());
    }

    public static TextWidget<?> addEnergyTierLine(WorkableElectricMultiblockMachine rlMachine,
                                                  PanelSyncManager syncManager) {
        BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                () -> new BooleanSyncValue(rlMachine::isFormed));

        IntSyncValue tier = syncManager.getOrCreateSyncHandler("energyTier", IntSyncValue.class,
                () -> new IntSyncValue(rlMachine::getTier));

        return Text.dynamic(() -> {
            Component voltageName = Component.literal(GTValues.VNF[tier.getIntValue()]);
            return Component.translatable(
                    "gtceu.multiblock.max_recipe_tier",
                    voltageName).withStyle(ChatFormatting.GRAY);
        })
                .asWidget()
                .tooltip(new RichTooltip().add(Component.translatable("gtceu.multiblock.max_recipe_tier_hover")
                        .withStyle(ChatFormatting.GRAY)))
                .setEnabledIf(widget -> isFormed.getBoolValue());
    }

    public static TextWidget<?> addParallelLine(WorkableMultiblockMachine rlMachine, PanelSyncManager syncManager) {
        IntSyncValue parallelAmount = syncManager.getOrCreateSyncHandler("parallelAmount", IntSyncValue.class,
                () -> new IntSyncValue(() -> {
                    if (rlMachine.getRecipeLogic().getLastUnrolledRecipe() == null) return 0;
                    return rlMachine.getRecipeLogic().getLastUnrolledRecipe().parallels;
                }));

        return Text.dynamic(() -> {
            Component runs = Component.literal(FormattingUtil.formatNumbers(parallelAmount.getIntValue()))
                    .withStyle(ChatFormatting.DARK_PURPLE);
            String key = "gtceu.multiblock.parallel";
            return Component.translatable(key, runs)
                    .withStyle(ChatFormatting.GRAY);
        }).asWidget()
                .setEnabledIf(widget -> parallelAmount.getIntValue() > 1);
    }

    public static TextWidget<?> addBatchModeLine(WorkableMultiblockMachine rlMachine, PanelSyncManager syncManager) {
        BooleanSyncValue batchEnabled = syncManager.getOrCreateSyncHandler("batchEnabled", BooleanSyncValue.class,
                () -> new BooleanSyncValue(rlMachine::isBatchEnabled));
        IntSyncValue batchAmount = syncManager.getOrCreateSyncHandler("batchAmount", IntSyncValue.class,
                () -> new IntSyncValue(() -> {
                    if (rlMachine.getRecipeLogic().getLastUnrolledRecipe() == null) return 0;
                    return rlMachine.getRecipeLogic().getLastUnrolledRecipe().batchParallels;
                }));

        return Text.dynamic(() -> {
            Component runs = Component.literal(FormattingUtil.formatNumbers(batchAmount.getIntValue()))
                    .withStyle(ChatFormatting.DARK_PURPLE);
            String key = "gtceu.multiblock.batch_enabled";
            return Component.translatable(key, runs)
                    .withStyle(ChatFormatting.GRAY);
        }).asWidget()
                .setEnabledIf(widget -> batchEnabled.getBoolValue() && batchAmount.getIntValue() > 1);
    }

    public static TextWidget<?> addSubtickParallelsLine(WorkableMultiblockMachine rlMachine,
                                                        PanelSyncManager syncManager) {
        IntSyncValue subtickAmount = syncManager.getOrCreateSyncHandler("subtickAmount", IntSyncValue.class,
                () -> new IntSyncValue(() -> {
                    if (rlMachine.getRecipeLogic().getLastUnrolledRecipe() == null) return 0;
                    return rlMachine.getRecipeLogic().getLastUnrolledRecipe().subtickParallels;
                }));

        return Text.dynamic(() -> {
            Component runs = Component.literal(FormattingUtil.formatNumbers(subtickAmount.getIntValue()))
                    .withStyle(ChatFormatting.DARK_PURPLE);
            String key = "gtceu.multiblock.subtick_parallels";
            return Component.translatable(key, runs)
                    .withStyle(ChatFormatting.GRAY);
        }).asWidget()
                .setEnabledIf(widget -> subtickAmount.getIntValue() > 1);
    }

    public static TextWidget<?> addTotalRunsLine(WorkableMultiblockMachine rlMachine, PanelSyncManager syncManager) {
        IntSyncValue totalRunAmount = syncManager.getOrCreateSyncHandler("totalRunAmount", IntSyncValue.class,
                () -> new IntSyncValue(() -> {
                    if (rlMachine.getRecipeLogic().getLastUnrolledRecipe() == null) return 0;
                    return rlMachine.getRecipeLogic().getLastUnrolledRecipe().getTotalRuns();
                }));

        return Text.dynamic(() -> {
            Component runs = Component.literal(FormattingUtil.formatNumbers(totalRunAmount.getIntValue()))
                    .withStyle(ChatFormatting.DARK_PURPLE);
            String key = "gtceu.multiblock.total_runs";
            return Component.translatable(key, runs)
                    .withStyle(ChatFormatting.GRAY);
        }).asWidget()
                .setEnabledIf(widget -> totalRunAmount.getIntValue() > 1);
    }

    public static TextWidget<?> addSteamUsageLine(@Nullable SteamEnergyRecipeHandler steamRH,
                                                  PanelSyncManager syncManager) {
        IntSyncValue steamAmount = syncManager.getOrCreateSyncHandler("steamTank", IntSyncValue.class,
                () -> new IntSyncValue(() -> {
                    if (steamRH == null) return 0;
                    return steamRH.getSteamTank().getFluidInTank(0).getAmount();
                }));
        IntSyncValue steamCapacity = syncManager.getOrCreateSyncHandler("steamCapacity", IntSyncValue.class,
                () -> new IntSyncValue(() -> {
                    if (steamRH == null) return 0;
                    return steamRH.getSteamTank().getTankCapacity(0);
                }));

        BooleanSyncValue hasSteamHandler = syncManager.getOrCreateSyncHandler("hasSteam", BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> steamRH != null));

        return Text
                .dynamic(() -> Component.translatable("gtceu.multiblock.steam.steam_stored",
                        FormattingUtil.formatNumbers(steamAmount.getIntValue()),
                        FormattingUtil.formatNumbers(steamCapacity.getIntValue())).withStyle(ChatFormatting.WHITE))
                .asWidget()
                .setEnabledIf((w) -> hasSteamHandler.getBoolValue());
    }

    public static TextWidget<?> addWorkingStatusLine(WorkableMultiblockMachine rlMachine,
                                                     PanelSyncManager syncManager) {
        return addWorkingStatusLine(rlMachine, syncManager,
                () -> Component.translatable("gtceu.multiblock.work_paused").withStyle(ChatFormatting.GOLD),
                () -> Component.translatable("gtceu.multiblock.running").withStyle(ChatFormatting.GREEN),
                () -> Component.translatable("gtceu.multiblock.idling").withStyle(ChatFormatting.GRAY),
                () -> Component.translatable("gtceu.recipe_logic.recipe_waiting").withStyle(ChatFormatting.RED));
    }

    public static TextWidget<?> addWorkingStatusLine(WorkableMultiblockMachine rlMachine,
                                                     PanelSyncManager syncManager,
                                                     Supplier<Component> runningPerfectly) {
        return addWorkingStatusLine(rlMachine, syncManager,
                () -> Component.translatable("gtceu.multiblock.work_paused").withStyle(ChatFormatting.GOLD),
                runningPerfectly,
                () -> Component.translatable("gtceu.multiblock.idling").withStyle(ChatFormatting.GRAY),
                () -> Component.translatable("gtceu.recipe_logic.recipe_waiting").withStyle(ChatFormatting.RED));
    }

    public static TextWidget<?> addWorkingStatusLine(WorkableMultiblockMachine rlMachine,
                                                     PanelSyncManager syncManager,
                                                     Supplier<Component> runningPerfectly,
                                                     Supplier<Component> waiting) {
        return addWorkingStatusLine(rlMachine, syncManager,
                () -> Component.translatable("gtceu.multiblock.work_paused").withStyle(ChatFormatting.GOLD),
                runningPerfectly,
                () -> Component.translatable("gtceu.multiblock.idling").withStyle(ChatFormatting.GRAY),
                waiting);
    }

    public static TextWidget<?> addWorkingStatusLine(WorkableMultiblockMachine rlMachine, PanelSyncManager syncManager,
                                                     Supplier<Component> workPaused,
                                                     Supplier<Component> runningPerfectly, Supplier<Component> idling,
                                                     Supplier<Component> waiting) {
        BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                () -> new BooleanSyncValue(rlMachine::isFormed));
        BooleanSyncValue isWaiting = syncManager.getOrCreateSyncHandler("isWaiting",
                BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> rlMachine.getRecipeLogic().isWaiting()));
        BooleanSyncValue isActive = syncManager.getOrCreateSyncHandler("isActive", BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> rlMachine.getRecipeLogic().isActive()));
        BooleanSyncValue isWorkingEnabled = syncManager.getOrCreateSyncHandler("isWorkingEnabled",
                BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> rlMachine.getRecipeLogic().isWorkingEnabled()));

        return Text
                .dynamic(() -> {
                    if (!isFormed.getBoolValue()) return Component.empty();
                    if (isWaiting.getBoolValue()) {
                        return waiting.get();
                    }
                    if (!isWorkingEnabled.getBoolValue()) {
                        return workPaused.get();
                    }
                    if (isActive.getBoolValue()) {
                        return runningPerfectly.get();
                    }
                    return idling.get();
                })
                .asWidget()
                .setEnabledIf((w) -> isFormed.getBoolValue());
    }

    @SuppressWarnings("unchecked")
    public static List<TextWidget<?>> addRecipeFailReasonLines(WorkableMultiblockMachine rlMachine,
                                                               PanelSyncManager syncManager) {
        BooleanSyncValue isFormed = syncManager.getOrCreateSyncHandler("isFormed", BooleanSyncValue.class,
                () -> new BooleanSyncValue(rlMachine::isFormed));

        BooleanSyncValue isIdle = syncManager.getOrCreateSyncHandler("isIdle", BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> rlMachine.getRecipeLogic().isIdle()));
        BooleanSyncValue hasRunningRecipe = syncManager.getOrCreateSyncHandler("hasRunningRecipe",
                BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> rlMachine.getRecipeLogic().getLastRecipe() != null));
        BooleanSyncValue isWaiting = syncManager.getOrCreateSyncHandler("isWaiting", BooleanSyncValue.class,
                () -> new BooleanSyncValue(() -> rlMachine.getRecipeLogic().isWaiting()));
        GenericSyncValue<Component> bestFailureReason = (GenericSyncValue<Component>) syncManager
                .getOrCreateSyncHandler("bestFailureReason", GenericSyncValue.class,
                        () -> GenericSyncValue.builder(Component.class)
                                .nullable()
                                .adapter(GTByteBufAdapters.COMPONENT)
                                .getter(() -> rlMachine.getRecipeLogic().getBestFailureReason())
                                .build());
        var lineList = new ArrayList<TextWidget<?>>();

        lineList.add(Text
                .dynamic(() -> Component.translatable("gtceu.recipe_logic.setup_fail").withStyle(ChatFormatting.RED))
                .asWidget()
                .setEnabledIf((w) -> isFormed.getBoolValue() && (isIdle.getBoolValue() || isWaiting.getBoolValue()) &&
                        !hasRunningRecipe.getBoolValue() && bestFailureReason.getValue() != null));
        lineList.add(Text
                .dynamic(() -> {
                    var reason = bestFailureReason.getValue();
                    if (reason == null) return Component.empty();
                    return Component.literal(" - ").append(reason);
                })
                .asWidget()
                .setEnabledIf((w) -> isFormed.getBoolValue() && (isIdle.getBoolValue() || isWaiting.getBoolValue()) &&
                        bestFailureReason.getValue() != null));
        return lineList;
    }

    @SuppressWarnings("unchecked")
    public static TextWidget<?> addRecipeTypeField(WorkableMultiblockMachine rlMachine, PanelSyncManager syncManager) {
        StringSyncValue recipeTypeName = syncManager.getOrCreateSyncHandler("recipeTypeName", StringSyncValue.class,
                () -> new StringSyncValue(
                        () -> FormattingUtil.toEnglishName(rlMachine.getRecipeType().registryName.getPath())));

        // No need to sync the rlMachine.getRecipeTypes().length > 1 condition because the recipe type array is the same
        // on client side.
        return Text
                .dynamic(() -> Component.translatable("gtceu.gui.machinemode",
                        Component.literal(recipeTypeName.getStringValue())
                                .withStyle(ChatFormatting.GOLD))
                        .withStyle(ChatFormatting.GRAY))
                .asWidget()
                .tooltip((tooltip) -> tooltip.add(Component.translatable("gtceu.gui.machinemode.title")))
                .setEnabledIf((w) -> rlMachine.getRecipeTypes().length > 1);
    }

    @SuppressWarnings("unchecked")
    public static DynamicWidget<?> addOutputLines(WorkableMultiblockMachine rlmachine,
                                                  PanelSyncManager syncManager) {
        GenericSyncValue<GTRecipe> recipeSyncValue = (GenericSyncValue<GTRecipe>) syncManager.getOrCreateSyncHandler(
                "GTRecipe",
                GenericSyncValue.class,
                () -> GenericSyncValue.builder(GTRecipe.class)
                        .getter(() -> rlmachine.getRecipeLogic().getLastUnrolledRecipe())
                        .setter((newRecipe) -> {})
                        .adapter(GTByteBufAdapters.GTRECIPE)
                        .copy(GTRecipe::copy)
                        .build());

        DynamicLinkedSyncHandler<GenericSyncValue<GTRecipe>> dynamicLinkedSyncHandler = new DynamicLinkedSyncHandler<>(
                recipeSyncValue)
                .widgetProvider((syncManager1, recipeSyncHandler) -> {
                    var list = Flow.column()
                            .widthRel(1)
                            .coverChildrenHeight()
                            .crossAxisAlignment(Alignment.CrossAxis.START);
                    GTRecipe recipe = recipeSyncHandler.getValue();
                    if (recipe == null) return list;

                    for (var output : recipe.getOutputContents(ItemRecipeCapability.CAP)) {
                        var widget = createItemLineForOutput(output, recipe);
                        if (widget.isEmpty()) continue;
                        list.child(widget.get().width(187 - 3 - 3 - 2 - 2));
                    }

                    for (var output : recipe.getOutputContents(FluidRecipeCapability.CAP)) {
                        var widget = createFluidLineForOutput(output, recipe);
                        if (widget.isEmpty()) continue;
                        list.child(widget.get().width(187 - 3 - 3 - 2 - 2));
                    }

                    return list;
                });

        return new DynamicWidget<>()
                .widthRel(1)
                .coverChildrenHeight()
                .syncHandler(dynamicLinkedSyncHandler)
                .setEnabledIf(w -> rlmachine.getRecipeLogic().getLastRecipe() != null);
    }

    public static Optional<Widget<?>> createItemLineForOutput(Content itemOutput, GTRecipe recipe) {
        int runs = recipe.getTotalRuns();
        double maxDurationSec = (double) recipe.duration / 20.0;

        boolean rounded = false;
        ItemStack stack;
        // number of items output by a non-ranged ingredient
        int count = 0;
        // number of items output, but stored as a double. Used for accurate items/second display.
        double countD = 1;
        // number of items output which is actually displayed. Can be either a number, or a range.
        Component displaycount;
        if (itemOutput.content() instanceof IntProviderIngredient provider) {
            rounded = true;
            stack = provider.getMaxSizeStack();
            displaycount = Component.translatable("gtceu.gui.content.range",
                    provider.getCountProvider().getMinValue(),
                    provider.getCountProvider().getMaxValue());
            if (itemOutput.chance() < itemOutput.maxChance()) {
                countD = countD * runs * itemOutput.chance() / itemOutput.maxChance();
            }
            countD = countD * provider.getMidRoll();
        } else {
            var stacks = ItemRecipeCapability.CAP.of(itemOutput.content()).getItems();
            if (stacks.length == 0) return Optional.empty();
            stack = stacks[0];
            count = stack.getCount();
            countD *= count;
            if (itemOutput.chance() < itemOutput.maxChance()) {
                rounded = true;
                countD = countD * runs * itemOutput.chance() / itemOutput.maxChance();
            }
            count = Math.max(1, (int) Math.round(countD));
            stack.setCount(count);
            displaycount = Component.literal(String.valueOf(count));
        }
        if (countD < maxDurationSec) {
            String key = "gtceu.multiblock.output_line." + (rounded ? "2" : "0");
            return Optional.of(
                    Flow.row()
                            .coverChildren()
                            .childPadding(2)
                            .child(new ItemDrawable(stack).asWidget()
                                    .size(16)
                                    .tooltip(r -> r.addFromItem(stack)))
                            .child(
                                    Text.lang(
                                            key, stack.getHoverName(), displaycount,
                                            FormattingUtil.formatNumber2Places(maxDurationSec / countD))
                                            .asWidget()));
        } else {
            String key = "gtceu.multiblock.output_line." + (rounded ? "3" : "1");
            return Optional.of(
                    Flow.row()
                            .coverChildren()
                            .childPadding(2)
                            .child(new ItemDrawable(stack).asWidget()
                                    .size(16)
                                    .tooltip(r -> r.addFromItem(stack)))
                            .child(
                                    Text.lang(
                                            key, stack.getHoverName(), displaycount,
                                            FormattingUtil.formatNumber2Places(countD / maxDurationSec))
                                            .asWidget()));
        }
    }

    public static Optional<Widget<?>> createFluidLineForOutput(Content fluidOutput, GTRecipe recipe) {
        int runs = recipe.getTotalRuns();

        double maxDurationSec = (double) recipe.duration / 20.0;

        boolean rounded = false;
        FluidStack stack;
        // amount of fluid output by a non-ranged ingredient
        int amount = 0;
        // amount of fluid output, but stored as a double. Used for accurate fluid/second display.
        double amountD = 1;
        // amount of fluid output which is actually displayed. Can be either a number, or a range.
        Component displaycount;
        if (fluidOutput.content() instanceof IntProviderFluidIngredient provider) {
            rounded = true;
            stack = provider.getMaxSizeStack();
            displaycount = Component.translatable("gtceu.gui.content.range",
                    provider.getCountProvider().getMinValue(),
                    provider.getCountProvider().getMaxValue());
            if (fluidOutput.chance() < fluidOutput.maxChance()) {
                amountD = amountD * runs * fluidOutput.chance() / fluidOutput.maxChance();
            }
            amountD = amountD * provider.getMidRoll();
        } else {
            var stacks = FluidRecipeCapability.CAP.of(fluidOutput.content()).getStacks();
            if (stacks.length == 0) return Optional.empty();
            stack = stacks[0];
            amount = stack.getAmount();
            amountD *= amount;
            if (fluidOutput.chance() < fluidOutput.maxChance()) {
                rounded = true;
                amountD = amountD * runs * fluidOutput.chance() / fluidOutput.maxChance();
            }
            amount = Math.max(1, (int) Math.round(amountD));
            stack.setAmount(amount);
            displaycount = Component.literal(String.valueOf(amount));
        }
        if (amountD < maxDurationSec) {
            String key = "gtceu.multiblock.output_line." + (rounded ? "2" : "0");
            return Optional.of(
                    Flow.row()
                            .coverChildren()
                            .childPadding(2)
                            .child(new FluidDrawable(stack).asWidget()
                                    .size(16)
                                    .tooltip(r -> r.add(stack.getDisplayName())))
                            .child(
                                    Text.lang(
                                            key, stack.getDisplayName(), displaycount,
                                            FormattingUtil.formatNumber2Places(maxDurationSec / amountD))
                                            .asWidget()));
        } else {
            String key = "gtceu.multiblock.output_line." + (rounded ? "3" : "1");
            return Optional.of(
                    Flow.row()
                            .coverChildren()
                            .childPadding(2)
                            .child(new FluidDrawable(stack).asWidget()
                                    .size(16)
                                    .tooltip(r -> r.add(stack.getDisplayName())))
                            .child(
                                    Text.lang(key, stack.getDisplayName(), displaycount,
                                            FormattingUtil.formatNumber2Places(amountD / maxDurationSec))
                                            .asWidget()));
        }
    }
}
