package org.implab.gradle.variants.sources;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.gradle.api.Action;
import org.implab.gradle.common.core.lang.Closures;
import groovy.lang.Closure;

@NonNullByDefault
public interface VariantSourcesExtension {

    /**
     * Selects how selector rules behave when they target an already materialized
     * {@link GenericSourceSet}.
     *
     * <p>This policy is single-valued:
     * <ul>
     * <li>it must be selected before the first selector rule is registered via
     * {@link #variant(String, Action)}, {@link #layer(String, Action)} or
     * {@link #unit(String, String, Action)};</li>
     * <li>once selected, it cannot be changed later;</li>
     * <li>the policy controls both diagnostics and late-application semantics.</li>
     * </ul>
     *
     * <p>If not selected explicitly, the default is
     * {@link LateConfigurationPolicySpec#failOnLateConfiguration()}.
     */
    void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action);

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

    /**
     * Selects how compile-unit name collisions are handled when the finalized
     * source context is created.
     *
     * <p>This policy is single-valued:
     * <ul>
     * <li>it must be selected before the finalized
     * {@link VariantSourcesContext} becomes observable through
     * {@link #whenFinalized(Action)};</li>
     * <li>once the context is being created, the policy is fixed and cannot be
     * changed later;</li>
     * <li>the policy governs validation of compile-unit names produced by the
     * source-set materializer.</li>
     * </ul>
     *
     * <p>If not selected explicitly, the default is
     * {@link NamingPolicySpec#failOnNameCollision()}.
     */
    void namingPolicy(Action<? super NamingPolicySpec> action);

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

    /**
     * Registers a selector rule for all compile units of the given layer.
     *
     * <p>Registering the first selector rule fixes the selected
     * {@link #lateConfigurationPolicy(Action)} for the remaining extension
     * lifecycle.
     */
    void layer(String layerName, Action<? super GenericSourceSet> action);

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

    /**
     * Registers a selector rule for all compile units of the given variant.
     *
     * <p>Registering the first selector rule fixes the selected
     * {@link #lateConfigurationPolicy(Action)} for the remaining extension
     * lifecycle.
     */
    void variant(String variantName, Action<? super GenericSourceSet> action);

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

    /**
     * Registers a selector rule for one exact compile unit.
     *
     * <p>Registering the first selector rule fixes the selected
     * {@link #lateConfigurationPolicy(Action)} for the remaining extension
     * lifecycle.
     */
    void unit(String variantName, String layerName, Action<? super GenericSourceSet> action);

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

    /**
     * Invoked when finalized variants-derived source context becomes available.
     *
     * Replayable:
     * <ul>
     * <li>if called before variants finalization, action is queued
     * <li>if called after variants finalization, action is invoked immediately
     * </ul>
     *
     * <p>By the time this callback becomes observable, compile-unit naming
     * policy has already been fixed and symbolic source-set names for finalized
     * compile units are determined.
     */
    void whenFinalized(Action<? super VariantSourcesContext> action);

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


    /**
     * Imperative selector for the late-configuration mode.
     *
     * <p>Exactly one mode is expected to be chosen for the extension lifecycle.
     */
    interface LateConfigurationPolicySpec {
        /**
         * Rejects selector registration if it targets any already materialized
         * source set.
         */
        void failOnLateConfiguration();

        /**
         * Allows late selector registration, but emits a warning when it targets an
         * already materialized source set.
         *
         * <p>For such targets, selector precedence is not re-established
         * retroactively. The action is applied as a late imperative step, after the
         * state already produced at the materialization moment.
         */
        void warnOnLateConfiguration();

        /**
         * Allows late selector registration without a warning when it targets an
         * already materialized source set.
         *
         * <p>For such targets, selector precedence is not re-established
         * retroactively. The action is applied as a late imperative step, after the
         * state already produced at the materialization moment.
         */
        void allowLateConfiguration();
    }

    interface NamingPolicySpec {
        /**
         * Rejects finalized compile-unit models that project the same source-set
         * name for different compile units.
         */
        void failOnNameCollision();

        /**
         * Resolves name collisions deterministically for the finalized
         * compile-unit model.
         *
         * <p>Conflicting compile units are ordered canonically by
         * {@code (variant.name, layer.name)}. The first unit keeps the base
         * projected name, and each next unit receives a numeric suffix
         * ({@code 2}, {@code 3}, ...).
         */
        void resolveNameCollision();
    }
}
