import AsyncActionQueue from '../AsyncActionQueue';
const EventEmitter = require('events');

class UndoQueue {

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	private inner_: any[] = [];
	private size_ = 20;

	public pop() {
		return this.inner_.pop();
	}

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	public push(e: any) {
		this.inner_.push(e);
		while (this.length > this.size_) {
			this.inner_.splice(0, 1);
		}
	}

	public get length(): number {
		return this.inner_.length;
	}

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	public at(index: number): any {
		return this.inner_[index];
	}

}

export default class UndoRedoService {

	private pushAsyncQueue: AsyncActionQueue = new AsyncActionQueue(700);
	private undoStates: UndoQueue = new UndoQueue();
	private redoStates: UndoQueue = new UndoQueue();
	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	private eventEmitter: any = new EventEmitter();
	private isUndoing = false;

	public constructor() {
		this.push = this.push.bind(this);
	}

	// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
	public on(eventName: string, callback: Function) {
		return this.eventEmitter.on(eventName, callback);
	}

	// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
	public off(eventName: string, callback: Function) {
		return this.eventEmitter.removeListener(eventName, callback);
	}

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	public push(state: any) {
		this.undoStates.push(state);
		this.redoStates = new UndoQueue();
		this.eventEmitter.emit('stackChange');
	}

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	public schedulePush(state: any) {
		this.pushAsyncQueue.push(async () => {
			this.push(state);
		});
	}

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	public async undo(redoState: any) {
		if (this.isUndoing) return;
		if (!this.canUndo) throw new Error('Nothing to undo');
		this.isUndoing = true;
		await this.pushAsyncQueue.processAllNow();
		const state = this.undoStates.pop();
		this.redoStates.push(redoState);
		this.eventEmitter.emit('stackChange');
		this.isUndoing = false;
		return state;
	}

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	public async redo(undoState: any) {
		if (this.isUndoing) return;
		if (!this.canRedo) throw new Error('Nothing to redo');
		this.isUndoing = true;
		await this.pushAsyncQueue.processAllNow();
		const state = this.redoStates.pop();
		this.undoStates.push(undoState);
		this.eventEmitter.emit('stackChange');
		this.isUndoing = false;
		return state;
	}

	public async reset() {
		this.undoStates = new UndoQueue();
		this.redoStates = new UndoQueue();
		this.isUndoing = false;
		const output = this.pushAsyncQueue.reset();
		this.eventEmitter.emit('stackChange');
		return output;
	}

	public get canUndo(): boolean {
		return !!this.undoStates.length;
	}

	public get canRedo(): boolean {
		return !!this.redoStates.length;
	}

}
