export class CommandTransactionError extends Error {
    cause;
    rollbackErrors;
    constructor(cause, rollbackErrors) {
        super("Command transaction failed and rollback reported errors.");
        this.name = "CommandTransactionError";
        this.cause = cause;
        this.rollbackErrors = rollbackErrors;
    }
}
class CompositeCommand {
    commands;
    name;
    constructor(commands) {
        this.commands = commands;
        this.name = commands.map((command) => command.name).join(", ");
    }
    async execute(context) {
        await executeCommandsAtomically(this.commands, context);
    }
    async undo(context) {
        for (const command of [...this.commands].reverse()) {
            await command.undo(context);
        }
    }
}
export class CommandHistory {
    undoStack = [];
    redoStack = [];
    get canUndo() {
        return this.undoStack.length > 0;
    }
    get canRedo() {
        return this.redoStack.length > 0;
    }
    get undoDepth() {
        return this.undoStack.length;
    }
    get redoDepth() {
        return this.redoStack.length;
    }
    async execute(command, context) {
        try {
            await command.execute(context);
        }
        catch (error) {
            await Promise.resolve(command.undo(context)).catch(() => undefined);
            throw error;
        }
        const previous = this.undoStack[this.undoStack.length - 1];
        if (previous?.canMerge?.(command) && previous.merge) {
            this.undoStack[this.undoStack.length - 1] = previous.merge(command);
        }
        else {
            this.undoStack.push(command);
        }
        this.redoStack.length = 0;
    }
    async executeTransaction(commands, context) {
        if (commands.length === 0) {
            return;
        }
        await executeCommandsAtomically(commands, context);
        this.undoStack.push(new CompositeCommand([...commands]));
        this.redoStack.length = 0;
    }
    async undo(context) {
        const command = this.undoStack.pop();
        if (!command) {
            return;
        }
        try {
            await command.undo(context);
        }
        catch (error) {
            this.undoStack.push(command);
            throw error;
        }
        this.redoStack.push(command);
    }
    async redo(context) {
        const command = this.redoStack.pop();
        if (!command) {
            return;
        }
        try {
            await command.execute(context);
        }
        catch (error) {
            this.redoStack.push(command);
            throw error;
        }
        this.undoStack.push(command);
    }
    clear() {
        this.undoStack.length = 0;
        this.redoStack.length = 0;
    }
}
async function executeCommandsAtomically(commands, context) {
    const executed = [];
    try {
        for (const command of commands) {
            await command.execute(context);
            executed.push(command);
        }
    }
    catch (error) {
        const rollbackErrors = [];
        for (const command of executed.reverse()) {
            try {
                await command.undo(context);
            }
            catch (rollbackError) {
                rollbackErrors.push(rollbackError);
            }
        }
        if (rollbackErrors.length > 0) {
            throw new CommandTransactionError(error, rollbackErrors);
        }
        throw error;
    }
}
//# sourceMappingURL=CommandHistory.js.map