/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */

import { Duration } from "#time/Duration.js";
import { Instant, Millis, Seconds } from "#time/TimeUnit.js";
import { Entropy } from "#util/Entropy.js";

/**
 * An iterable of retry values based on a scheduling configuration.
 */
export class RetrySchedule {
    #entropy: Entropy;
    readonly config: RetrySchedule.Configuration;

    constructor(entropy: Entropy, config: RetrySchedule.Configuration) {
        this.#entropy = entropy;
        this.config = config;
    }

    /**
     * Yields intervals.
     *
     * Will yield indefinitely until canceled unless {@link config} specifies a timeout.
     */
    *[Symbol.iterator]() {
        const {
            timeout,
            maximumInterval,
            maximumCount,
            jitterFactor = 0,
            initialInterval = Seconds.one,
            backoffFactor = 2,
        } = this.config;

        let count = 0;
        let baseInterval = initialInterval;
        let timeSoFar = Instant;

        while ((timeout === undefined || timeSoFar < timeout) && (maximumCount === undefined || maximumCount > count)) {
            count++;
            const maxJitter = jitterFactor * baseInterval;
            const jitter = Millis.floor(Millis((maxJitter * this.#entropy.randomUint32) / Math.pow(2, 32)));
            let interval = Millis(baseInterval + jitter);

            if (timeout !== undefined && timeSoFar + interval > timeout) {
                interval = Millis(timeout - timeSoFar);
            }
            if (maximumInterval !== undefined && interval > maximumInterval) {
                interval = maximumInterval;
            }

            yield interval;
            timeSoFar = Millis(timeSoFar + interval);

            baseInterval = Millis(baseInterval * backoffFactor);
        }
    }
}

export namespace RetrySchedule {
    /**
     * Configuration parameters for retry schedule.
     */
    export interface Configuration {
        /**
         * Overall timeout.
         *
         * Leave undefined to allow unlimited retries.
         */
        readonly timeout?: Duration;

        /**
         * Maximum number of occurrences (including first).
         *
         * Set to zero to disable transmission completely; leave undefined to allow any number of transmissions.
         */
        readonly maximumCount?: number;

        /**
         * Interval between first request and final interval.
         *
         * Defaults to 1s.
         */
        readonly initialInterval?: Duration;

        /**
         * Multiplier for subsequent retries.
         *
         * Defaults to 2.
         */
        readonly backoffFactor?: number;

        /**
         * Maximum interval between retries (excluding jitter).
         *
         * Leave undefined for interval to allow interval to grow continuously.
         */
        readonly maximumInterval?: Duration;

        /**
         * Multiplier for retry jitter.
         *
         * Leave undefined to disable jitter.
         */
        readonly jitterFactor?: number;
    }

    /**
     * Create a full configuration with defaults.
     */
    export function Configuration(defaults: Configuration, config?: Configuration) {
        return {
            ...defaults,
            ...config,
        };
    }
}
