package org.implab.gradle.variants.artifacts.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.gradle.api.Action;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.attributes.AttributeContainer;
import org.implab.gradle.internal.ReplayableQueue;
import org.implab.gradle.variants.artifacts.ArtifactAssemblies;
import org.implab.gradle.variants.artifacts.ArtifactAssembly;
import org.implab.gradle.variants.artifacts.ArtifactSlot;
import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec;
import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
import org.implab.gradle.variants.artifacts.OutgoingVariant;
import org.implab.gradle.variants.core.Variant;

/**
 * Handles outgoing artifact materialization policy.
 *
 * <p>Materialization is the phase where the plugin interprets variant artifact
 * declarations as Gradle outgoing publication state: a consumable configuration,
 * its primary artifact set, secondary artifact variants, attributes, and backing
 * assembly tasks.
 *
 * <p>The handler provides extension points for customizing the materialized
 * Gradle-facing objects. These hooks intentionally expose Gradle API objects.
 * This allows advanced customization, but also means that callers can bypass
 * the plugin model. Such customizations are considered caller responsibility.
 *
 * <p>The internal binding mechanics are not part of the public contract. The
 * contract is the materialized outgoing state exposed through the specification
 * objects.
 */
@NonNullByDefault
public class MaterializationPolicyHandler implements Action<OutgoingVariant> {

    private final ArtifactAssemblies resolver;

    private final ReplayableQueue<OutgoingConfigurationSpec> variantMaterialization = new ReplayableQueue<>();
    private final ReplayableQueue<OutgoingConfigurationSlotSpec> slotMaterialization = new ReplayableQueue<>();

    public MaterializationPolicyHandler(ArtifactAssemblies resolver) {
        this.resolver = resolver;
    }

    @Override
    public void execute(OutgoingVariant outgoingVariant) {
        var slots = outgoingVariant.getSlots();
        var primarySlotProvider = outgoingVariant.getPrimarySlot();
        var variant = outgoingVariant.getVariant();

        // связываем конфигурацию
        outgoingVariant.configureOutgoing(configuration -> {
            var primarySlot = primarySlotProvider.get();
            var outgoing = configuration.getOutgoing();

            variantMaterialized(variant, configuration);

            slotMaterialized(new ArtifactSlot(variant, primarySlot), true, outgoing.getAttributes());

            outgoing.getVariants().configureEach(variantConfiguration -> {
                var slotName = variantConfiguration.getName();
                var slot = slots.findByName(slotName);
                if (slot != null) {
                    slotMaterialized(new ArtifactSlot(variant, slot), false, variantConfiguration.getAttributes());
                }
            });
        });
    };

    public void whenVariantMaterialized(Action<? super OutgoingConfigurationSpec> action) {
        variantMaterialization.forEach(action::execute);
    }

    public void whenSlotMaterialized(Action<? super OutgoingConfigurationSlotSpec> action) {
        slotMaterialization.forEach(action::execute);
    }

    private void variantMaterialized(Variant variant, Configuration configuration) {
        variantMaterialization.add(new OutgoingConfigurationSpec() {

            @Override
            public Variant getVariant() {
                return variant;
            }

            @Override
            public Configuration getConfiguration() {
                return configuration;
            }

        });
    }

    private void slotMaterialized(ArtifactSlot slot, boolean primary, AttributeContainer attributes) {
        slotMaterialization.add(new OutgoingConfigurationSlotSpec() {
            @Override
            public boolean isPrimary() {
                return primary;
            }

            @Override
            public ArtifactSlot getArtifactSlot() {
                return slot;
            }

            @Override
            public ArtifactAssembly getAssembly() {
                return resolver.require(slot);
            }

            @Override
            public void artifactAttributes(Action<? super AttributeContainer> action) {
                action.execute(attributes);
            }
        });
    }

}
