import { MAIN_HEADER_TABS } from './../constants';
import { useNDVStore } from '@/stores/ndv';
import { BulkCommand, Undoable } from '@/models/history';
import { useHistoryStore } from '@/stores/history';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins';
import { Command } from '@/models/history';
import { debounceHelper } from '@/mixins/debounce';
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
import Vue from 'vue';
import { getNodeViewTab } from '@/utils';

const UNDO_REDO_DEBOUNCE_INTERVAL = 100;

export const historyHelper = mixins(debounceHelper, deviceSupportHelpers).extend({
	computed: {
		...mapStores(useNDVStore, useHistoryStore, useUIStore, useWorkflowsStore),
		isNDVOpen(): boolean {
			return this.ndvStore.activeNodeName !== null;
		},
	},
	mounted() {
		document.addEventListener('keydown', this.handleKeyDown);
	},
	destroyed() {
		document.removeEventListener('keydown', this.handleKeyDown);
	},
	methods: {
		handleKeyDown(event: KeyboardEvent) {
			const currentNodeViewTab = getNodeViewTab(this.$route);

			if (event.repeat || currentNodeViewTab !== MAIN_HEADER_TABS.WORKFLOW) return;
			if (this.isCtrlKeyPressed(event) && event.key.toLowerCase() === 'z') {
				event.preventDefault();
				if (!this.isNDVOpen) {
					if (event.shiftKey) {
						this.callDebounced('redo', {
							debounceTime: UNDO_REDO_DEBOUNCE_INTERVAL,
							trailing: true,
						});
					} else {
						this.callDebounced('undo', {
							debounceTime: UNDO_REDO_DEBOUNCE_INTERVAL,
							trailing: true,
						});
					}
				} else if (!event.shiftKey) {
					this.trackUndoAttempt(event);
				}
			}
		},
		async undo() {
			const command = this.historyStore.popUndoableToUndo();
			if (!command) {
				return;
			}
			if (command instanceof BulkCommand) {
				this.historyStore.bulkInProgress = true;
				const commands = command.commands;
				const reverseCommands: Command[] = [];
				for (let i = commands.length - 1; i >= 0; i--) {
					await commands[i].revert();
					reverseCommands.push(commands[i].getReverseCommand());
				}
				this.historyStore.pushUndoableToRedo(new BulkCommand(reverseCommands));
				await Vue.nextTick();
				this.historyStore.bulkInProgress = false;
			}
			if (command instanceof Command) {
				await command.revert();
				this.historyStore.pushUndoableToRedo(command.getReverseCommand());
				this.uiStore.stateIsDirty = true;
			}
			this.trackCommand(command, 'undo');
		},
		async redo() {
			const command = this.historyStore.popUndoableToRedo();
			if (!command) {
				return;
			}
			if (command instanceof BulkCommand) {
				this.historyStore.bulkInProgress = true;
				const commands = command.commands;
				const reverseCommands = [];
				for (let i = commands.length - 1; i >= 0; i--) {
					await commands[i].revert();
					reverseCommands.push(commands[i].getReverseCommand());
				}
				this.historyStore.pushBulkCommandToUndo(new BulkCommand(reverseCommands), false);
				await Vue.nextTick();
				this.historyStore.bulkInProgress = false;
			}
			if (command instanceof Command) {
				await command.revert();
				this.historyStore.pushCommandToUndo(command.getReverseCommand(), false);
				this.uiStore.stateIsDirty = true;
			}
			this.trackCommand(command, 'redo');
		},
		trackCommand(command: Undoable, type: 'undo' | 'redo'): void {
			if (command instanceof Command) {
				this.$telemetry.track(`User hit ${type}`, { commands_length: 1, commands: [command.name] });
			} else if (command instanceof BulkCommand) {
				this.$telemetry.track(`User hit ${type}`, {
					commands_length: command.commands.length,
					commands: command.commands.map((c) => c.name),
				});
			}
		},
		trackUndoAttempt(event: KeyboardEvent) {
			if (this.isNDVOpen && !event.shiftKey) {
				const activeNode = this.ndvStore.activeNode;
				if (activeNode) {
					this.$telemetry.track('User hit undo in NDV', { node_type: activeNode.type });
				}
			}
		},
	},
});
