import { convertToNext } from '../../check/arbitrary/definition/Converters.js';
import { NextArbitrary } from '../../check/arbitrary/definition/NextArbitrary.js';
import { NextValue } from '../../check/arbitrary/definition/NextValue.js';
import { CommandsIterable } from '../../check/model/commands/CommandsIterable.js';
import { CommandWrapper } from '../../check/model/commands/CommandWrapper.js';
import { ReplayPath } from '../../check/model/ReplayPath.js';
import { makeLazy } from '../../stream/LazyIterableIterator.js';
import { Stream } from '../../stream/Stream.js';
import { oneof } from '../oneof.js';
import { restrictedIntegerArbitraryBuilder } from './builders/RestrictedIntegerArbitraryBuilder.js';
export class CommandsArbitrary extends NextArbitrary {
    constructor(commandArbs, maxGeneratedCommands, maxCommands, sourceReplayPath, disableReplayLog) {
        super();
        this.sourceReplayPath = sourceReplayPath;
        this.disableReplayLog = disableReplayLog;
        this.oneCommandArb = convertToNext(oneof(...commandArbs).map((c) => new CommandWrapper(c)));
        this.lengthArb = convertToNext(restrictedIntegerArbitraryBuilder(0, maxGeneratedCommands, maxCommands));
        this.replayPath = [];
        this.replayPathPosition = 0;
    }
    metadataForReplay() {
        return this.disableReplayLog ? '' : `replayPath=${JSON.stringify(ReplayPath.stringify(this.replayPath))}`;
    }
    buildNextValueFor(items, shrunkOnce) {
        const commands = items.map((item) => item.value_);
        const context = { shrunkOnce, items };
        return new NextValue(new CommandsIterable(commands, () => this.metadataForReplay()), context);
    }
    generate(mrng) {
        const size = this.lengthArb.generate(mrng, undefined);
        const sizeValue = size.value;
        const items = Array(sizeValue);
        for (let idx = 0; idx !== sizeValue; ++idx) {
            const item = this.oneCommandArb.generate(mrng, undefined);
            items[idx] = item;
        }
        this.replayPathPosition = 0;
        return this.buildNextValueFor(items, false);
    }
    canShrinkWithoutContext(value) {
        return false;
    }
    filterOnExecution(itemsRaw) {
        const items = [];
        for (const c of itemsRaw) {
            if (c.value_.hasRan) {
                this.replayPath.push(true);
                items.push(c);
            }
            else
                this.replayPath.push(false);
        }
        return items;
    }
    filterOnReplay(itemsRaw) {
        return itemsRaw.filter((c, idx) => {
            const state = this.replayPath[this.replayPathPosition + idx];
            if (state === undefined)
                throw new Error(`Too short replayPath`);
            if (!state && c.value_.hasRan)
                throw new Error(`Mismatch between replayPath and real execution`);
            return state;
        });
    }
    filterForShrinkImpl(itemsRaw) {
        if (this.replayPathPosition === 0) {
            this.replayPath = this.sourceReplayPath !== null ? ReplayPath.parse(this.sourceReplayPath) : [];
        }
        const items = this.replayPathPosition < this.replayPath.length
            ? this.filterOnReplay(itemsRaw)
            : this.filterOnExecution(itemsRaw);
        this.replayPathPosition += itemsRaw.length;
        return items;
    }
    shrink(_value, context) {
        if (context === undefined) {
            return Stream.nil();
        }
        const safeContext = context;
        const shrunkOnce = safeContext.shrunkOnce;
        const itemsRaw = safeContext.items;
        const items = this.filterForShrinkImpl(itemsRaw);
        if (items.length === 0) {
            return Stream.nil();
        }
        const rootShrink = shrunkOnce
            ? Stream.nil()
            : new Stream([[]][Symbol.iterator]());
        const nextShrinks = [];
        for (let numToKeep = 0; numToKeep !== items.length; ++numToKeep) {
            nextShrinks.push(makeLazy(() => {
                const fixedStart = items.slice(0, numToKeep);
                return this.lengthArb
                    .shrink(items.length - 1 - numToKeep, undefined)
                    .map((l) => fixedStart.concat(items.slice(items.length - (l.value + 1))));
            }));
        }
        for (let itemAt = 0; itemAt !== items.length; ++itemAt) {
            nextShrinks.push(makeLazy(() => this.oneCommandArb
                .shrink(items[itemAt].value_, items[itemAt].context)
                .map((v) => items.slice(0, itemAt).concat([v], items.slice(itemAt + 1)))));
        }
        return rootShrink.join(...nextShrinks).map((shrinkables) => {
            return this.buildNextValueFor(shrinkables.map((c) => new NextValue(c.value_.clone(), c.context)), true);
        });
    }
}
