package com.tiedup.remake.v2.bondage.client.diagnostic;

import com.tiedup.remake.client.gltf.diagnostic.GlbDiagnostic;
import com.tiedup.remake.client.gltf.diagnostic.GlbDiagnosticRegistry;
import com.tiedup.remake.client.gltf.diagnostic.GlbValidationResult;
import com.tiedup.remake.client.gltf.diagnostic.GlbValidator;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.toasts.SystemToast;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * Reload listener that validates all data-driven item GLB models on resource
 * reload (F3+T or startup).
 *
 * <p>Must be registered AFTER {@link com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener}
 * so that the item registry is populated before validation runs.</p>
 *
 * <p>On each reload:
 * <ol>
 *   <li>Clears the {@link GlbDiagnosticRegistry}</li>
 *   <li>Iterates all {@link DataDrivenItemDefinition}s</li>
 *   <li>For each definition with a model location, attempts to open the GLB
 *       resource and runs {@link GlbValidator#validate} on it</li>
 *   <li>Records results into the diagnostic registry and logs a summary</li>
 * </ol>
 */
@OnlyIn(Dist.CLIENT)
public class GlbValidationReloadListener
    extends SimplePreparableReloadListener<Void>
{

    private static final Logger LOGGER = LogManager.getLogger("GltfValidation");

    @Override
    protected Void prepare(
        ResourceManager resourceManager,
        ProfilerFiller profiler
    ) {
        return null;
    }

    @Override
    protected void apply(
        Void nothing,
        ResourceManager resourceManager,
        ProfilerFiller profiler
    ) {
        GlbDiagnosticRegistry.clear();

        Collection<DataDrivenItemDefinition> definitions =
            DataDrivenItemRegistry.getAll();

        if (definitions.isEmpty()) {
            LOGGER.warn(
                "[GltfValidation] No data-driven item definitions found — skipping GLB validation"
            );
            return;
        }

        int passed = 0;
        int withWarnings = 0;
        int withErrors = 0;

        for (DataDrivenItemDefinition def : definitions) {
            ResourceLocation modelLoc = def.modelLocation();
            if (modelLoc == null) {
                continue;
            }

            Optional<Resource> resourceOpt =
                resourceManager.getResource(modelLoc);

            if (resourceOpt.isEmpty()) {
                // GLB file not found in any resource pack
                GlbValidationResult missingResult = GlbValidationResult.of(
                    modelLoc,
                    List.of(new GlbDiagnostic(
                        modelLoc,
                        def.id(),
                        GlbDiagnostic.Severity.ERROR,
                        "MISSING_GLB",
                        "GLB file not found: " + modelLoc
                            + " (referenced by item " + def.id() + ")"
                    ))
                );
                GlbDiagnosticRegistry.addResult(missingResult);
                withErrors++;
                continue;
            }

            try (InputStream stream = resourceOpt.get().open()) {
                GlbValidationResult result =
                    GlbValidator.validate(stream, modelLoc);
                GlbDiagnosticRegistry.addResult(result);

                if (!result.passed()) {
                    withErrors++;
                } else if (hasWarnings(result)) {
                    withWarnings++;
                } else {
                    passed++;
                }
            } catch (Exception e) {
                GlbValidationResult errorResult = GlbValidationResult.of(
                    modelLoc,
                    List.of(new GlbDiagnostic(
                        modelLoc,
                        def.id(),
                        GlbDiagnostic.Severity.ERROR,
                        "GLB_READ_ERROR",
                        "Failed to read GLB file: " + e.getMessage()
                    ))
                );
                GlbDiagnosticRegistry.addResult(errorResult);
                withErrors++;
            }
        }

        int total = passed + withWarnings + withErrors;
        LOGGER.info(
            "[GltfValidation] Validated {} GLBs: {} passed, {} with warnings, {} with errors",
            total, passed, withWarnings, withErrors
        );

        // Show toast notification for errors so artists don't have to check logs
        if (withErrors > 0) {
            int errorCount = withErrors;
            Minecraft.getInstance().tell(() ->
                Minecraft.getInstance().getToasts().addToast(
                    SystemToast.multiline(
                        Minecraft.getInstance(),
                        SystemToast.SystemToastIds.PACK_LOAD_FAILURE,
                        Component.literal("TiedUp! GLB Validation"),
                        Component.literal(errorCount + " model(s) have errors. Run /tiedup validate")
                    )
                )
            );
        }
    }

    private static boolean hasWarnings(GlbValidationResult result) {
        return result.diagnostics().stream()
            .anyMatch(d -> d.severity() == GlbDiagnostic.Severity.WARNING);
    }
}
