package org.implab.gradle.common.core.lang;

import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
 * Replayable append-only notification queue.
 *
 * <p>
 * The queue stores values in insertion order and supports replaying all values
 * that were already added when a consumer is registered. The same consumer is
 * also retained and invoked for every value added later.
 *
 * <p>
 * Values are committed before dispatch. If a consumer throws while a value is
 * being added, the value remains recorded in the queue and will be visible to
 * later consumers through {@link #forEach(Consumer)} and {@link #values()}.
 *
 * <p>
 * Reentrant replay is intentionally not supported. Consumers must not call
 * {@link #add(Object)} or {@link #forEach(Consumer)} on the same queue while
 * they are being invoked by that queue. Such nested calls fail fast with
 * {@link IllegalStateException}. This keeps ordering predictable and avoids
 * recursive event-loop semantics.
 *
 * @param <T> value type
 */
@NonNullByDefault
public class ReplayableQueue<T> {
    private final List<Consumer<? super T>> consumers = new LinkedList<>();
    private final List<T> values = new LinkedList<>();
    private boolean replaying = false;

    /**
     * Adds a value and dispatches it to all registered consumers.
     *
     * <p>
     * The value is recorded before consumers are invoked. If dispatch fails, the
     * value still belongs to the queue.
     *
     * @param value value to add
     */
    public void add(T value) {
        safeInvoke(value, v -> {
            values.add(v);
            consumers.forEach(consumer -> consumer.accept(v));
        });
    }

    /**
     * Returns an immutable snapshot of values recorded so far.
     *
     * @return current values in insertion order
     */
    public List<T> values() {
        return List.copyOf(values);
    }

    /**
     * Replays all recorded values to the consumer and registers it for future
     * values.
     *
     * <p>
     * The consumer is registered only after replaying existing values succeeds.
     *
     * @param consumer consumer to replay and retain
     */
    public void forEach(Consumer<? super T> consumer) {
        safeInvoke(values, v -> {
            v.forEach(consumer);
            consumers.add(consumer);
        });
    }

    private <X> void safeInvoke(X value, Consumer<? super X> consumer) {
        if (replaying)
            throw new IllegalStateException("Reentrant replay is not supported: replay is in progress");
        try {
            replaying = true;
            consumer.accept(value);
        } finally {
            replaying = false;
        }
    }

}
