package org.implab.gradle.variants.artifacts;

import java.util.function.Function;

import org.gradle.api.Action;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Task;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskProvider;
import groovy.lang.Closure;
import org.implab.gradle.common.core.lang.Closures;

/**
 * DSL model describing how a slot artifact is assembled.
 *
 * <p>Selection rules declared here may refer to internal build topology such as roles, layers or units.
 * Those selectors influence slot assembly only and do not become part of published artifact identity.
 *
 * <p>Regardless of the number of declared inputs, a slot is expected to materialize to a single published
 * artifact.
 */
public interface ArtifactAssemblySpec {
    /**
     * Contributes direct input material to the slot assembly.
     *
     * <p>The resulting slot still represents one published artifact.
     *
     * @param artifact direct input notation understood by the implementation
     */
    void from(Object artifact);

    /**
     * Registers a task that directly produces the published slot artifact.
     *
     * <p>Use this method when the slot is produced as one file or directory by an
     * existing task, for example generated package metadata. Unlike {@link #from(Object)}
     * and topology-aware selectors, this does not copy inputs into a managed assembly
     * directory. The mapped task output becomes the published artifact itself.
     *
     * <p>This mode is mutually exclusive with contribution-based assembly methods
     * such as {@link #from(Object)}, {@link #fromVariant(Action)}, {@link #fromRole(String, Action)},
     * and {@link #fromLayer(String, Action)} for the same slot.
     *
     * @param <T> task type
     * @param task task provider producing the artifact
     * @param artifact maps the producing task to its output file or directory provider
     */
    <T extends Task> void producedBy(
            TaskProvider<T> task,
            Function<? super T, ? extends Provider<? extends FileSystemLocation>> artifact);

    default <T extends Task> void producedBy(TaskProvider<T> task, Closure<?> closure) {
        producedBy(task, taskInstance -> producedArtifact(closure, taskInstance));
    }

    @SuppressWarnings("unchecked")
    private static Provider<? extends FileSystemLocation> producedArtifact(Closure<?> closure, Task task) {
        var c = (Closure<?>) closure.clone();
        c.setResolveStrategy(Closure.DELEGATE_FIRST);
        c.setDelegate(task);

        var artifact = c.call(task);
        if (artifact instanceof Provider<?>) {
            return (Provider<? extends FileSystemLocation>) artifact;
        }

        throw new InvalidUserDataException("Produced artifact mapper for task '" + task.getName()
                + "' must return Provider<? extends FileSystemLocation>");
    }

    /**
     * Selects outputs from the whole variant scope.
     *
     * @param action output selection rule
     */
    void fromVariant(Action<? super OutputSelectionSpec> action);

    default void fromVariant(Closure<?> closure) {
        fromVariant(Closures.action(closure));
    }

    /**
     * Selects outputs from a role inside the current variant.
     *
     * @param roleName role name used only for assembly-time selection
     * @param action output selection rule
     */
    void fromRole(String roleName, Action<? super OutputSelectionSpec> action);

    default void fromRole(String roleName, Closure<?> closure) {
        fromRole(roleName, Closures.action(closure));
    }

    /**
     * Selects outputs from a layer inside the current variant.
     *
     * @param layerName layer name used only for assembly-time selection
     * @param action output selection rule
     */
    void fromLayer(String layerName, Action<? super OutputSelectionSpec> action);

    default void fromLayer(String layerName, Closure<?> closure) {
        fromLayer(layerName, Closures.action(closure));
    }
}
