const { id, ids, createNTestFolders, sortedIds, createNTestNotes, TestApp } = require('@joplin/lib/testing/test-utils.js');
const BaseModel = require('@joplin/lib/BaseModel').default;
const uuid = require('@joplin/lib/uuid').default;
const Note = require('@joplin/lib/models/Note').default;
const Folder = require('@joplin/lib/models/Folder').default;

const { ALL_NOTES_FILTER_ID } = require('@joplin/lib/reserved-ids.js');

let testApp = null;

const goBackWard = (state) => {
	if (!state.backwardHistoryNotes.length)	return;
	testApp.dispatch({ type: 'HISTORY_BACKWARD' });
};

const goForward = (state) => {
	if (!state.forwardHistoryNotes.length) return;
	testApp.dispatch({ type: 'HISTORY_FORWARD' });
};

const goToNote = (testApp, note) => {
	testApp.dispatch({ type: 'NOTE_SELECT', id: note.id });
};

describe('feature_NoteHistory', () => {
	beforeEach(async () => {
		testApp = new TestApp();
		await testApp.start(['--no-welcome']);
	});

	afterEach(async () => {
		if (testApp) await testApp.destroy();
		testApp = null;
	});

	it('should save history when navigating through notes', (async () => {
		// setup
		const folders = await createNTestFolders(2);
		await testApp.wait();
		const notes0 = await createNTestNotes(5, folders[0]);
		// let notes1 = await createNTestNotes(5, folders[1]);
		await testApp.wait();

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });
		await testApp.wait();

		testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[3].id });
		await testApp.wait();
		testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[2].id });
		await testApp.wait();
		testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[1].id });
		await testApp.wait();
		testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[0].id });
		await testApp.wait();

		let state = testApp.store().getState();

		goBackWard(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[1].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		goBackWard(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[2].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		goForward(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[1].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[4].id });
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[4].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);
	}));


	it('should save history when navigating through notebooks', (async () => {
		const folders = await createNTestFolders(2);
		await testApp.wait();
		const notes0 = await createNTestNotes(5, folders[0]);
		const notes1 = await createNTestNotes(5, folders[1]);
		await testApp.wait();

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });
		await testApp.wait();

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[1]) });
		await testApp.wait();

		let state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes1[4].id]);
		expect(state.selectedFolderId).toEqual(folders[1].id);

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[4].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		goBackWard(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes1[4].id]);
		expect(state.selectedFolderId).toEqual(folders[1].id);

		goForward(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[4].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);
	}));


	it('should save history when searching for a note', (async () => {
		const folders = await createNTestFolders(2);
		await testApp.wait();
		const notes0 = await createNTestNotes(5, folders[0]);
		const notes1 = await createNTestNotes(5, folders[1]);
		await testApp.wait();

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });
		await testApp.wait();

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[1]) });
		await testApp.wait();

		let state = testApp.store().getState();

		expect(state.selectedNoteIds).toEqual([notes1[4].id]); // notes1[4]
		expect(state.selectedFolderId).toEqual(folders[1].id);

		const searchId = uuid.create();
		testApp.dispatch({
			type: 'SEARCH_UPDATE',
			search: {
				id: searchId,
				title: notes0[0].title,
				query_pattern: notes0[0].title,
				query_folder_id: null,
				type: BaseModel.TYPE_SEARCH,
			},
		});
		await testApp.wait();

		testApp.dispatch({
			type: 'SEARCH_SELECT',
			id: searchId,
		});
		await testApp.wait();

		state = testApp.store().getState();
		expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4], notes1[4]]));
		expect(ids(state.forwardHistoryNotes)).toEqual([]);
	}));

	it('should ensure no adjacent duplicates', (async () => {
		const folders = await createNTestFolders(2);
		const notes0 = await createNTestNotes(3, folders[0]);
		await testApp.wait();

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });

		testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[0].id });
		await testApp.wait();

		goToNote(testApp, notes0[1]);
		await testApp.wait();
		goToNote(testApp, notes0[2]);
		await testApp.wait();
		goToNote(testApp, notes0[1]);
		await testApp.wait();
		goToNote(testApp, notes0[2]);
		await testApp.wait();

		let state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[2].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		testApp.dispatch({ type: 'NOTE_DELETE', id: notes0[1].id });
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[2].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		goBackWard(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[0].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);
	}));

	it('should ensure history is not corrupted when notes get deleted.', (async () => {
		const folders = await createNTestFolders(2);
		await testApp.wait();
		const notes0 = await createNTestNotes(5, folders[0]);
		await testApp.wait();

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });
		await testApp.wait();

		testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[0].id });
		await testApp.wait();

		goToNote(testApp, notes0[1]);
		await testApp.wait();

		goToNote(testApp, notes0[2]);
		await testApp.wait();

		testApp.dispatch({ type: 'NOTE_DELETE', id: notes0[1].id });
		await testApp.wait();

		let state = testApp.store().getState();
		goBackWard(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[0].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);
	}));

	it('should ensure history is not corrupted when notes get created.', (async () => {
		const folders = await createNTestFolders(2);
		await testApp.wait();
		const notes0 = await createNTestNotes(5, folders[0]);
		await testApp.wait();

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });
		await testApp.wait();

		goToNote(testApp, notes0[0]);
		await testApp.wait();

		goToNote(testApp, notes0[1]);
		await testApp.wait();

		const newNote = await Note.save({
			parent_id: folders[0].id,
			is_todo: 0,
			body: 'test',
		});
		await testApp.wait();

		goToNote(testApp, newNote);
		await testApp.wait();

		let state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([newNote.id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		goToNote(testApp, notes0[2]);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[2].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		goBackWard(state);
		await testApp.wait();
		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([newNote.id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		goBackWard(state);
		await testApp.wait();
		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[1].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		goForward(state);
		await testApp.wait();
		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([newNote.id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);

		goForward(state);
		await testApp.wait();
		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[2].id]);
		expect(state.selectedFolderId).toEqual(folders[0].id);
	}));

	it('should ensure history works when traversing all notes', (async () => {
		const folders = await createNTestFolders(2);
		await testApp.wait();
		const notes0 = await createNTestNotes(5, folders[0]);
		const notes1 = await createNTestNotes(5, folders[1]);
		await testApp.wait();

		testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });
		await testApp.wait();

		goToNote(testApp, notes0[0]);
		await testApp.wait();

		testApp.dispatch({ type: 'SMART_FILTER_SELECT', id: ALL_NOTES_FILTER_ID });
		await testApp.wait();

		let state = testApp.store().getState();
		expect(sortedIds(state.notes)).toEqual(sortedIds(notes0.concat(notes1)));
		expect(state.selectedNoteIds).toEqual(ids([notes0[0]]));

		goToNote(testApp, notes0[2]);
		await testApp.wait();

		goToNote(testApp, notes0[4]);
		await testApp.wait();

		goToNote(testApp, notes1[2]);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes1[2].id]);

		goBackWard(state);
		await testApp.wait();
		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[4].id]);

		goBackWard(state);
		await testApp.wait();
		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[2].id]);

		goBackWard(state);
		await testApp.wait();
		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[0].id]);

		goForward(state);
		await testApp.wait();
		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[2].id]);

		goForward(state);
		await testApp.wait();
		state = testApp.store().getState();
		expect(state.selectedNoteIds).toEqual([notes0[4].id]);
	}));

	it('should ensure history works when traversing through conflict notes', (async () => {
		const folders = await createNTestFolders(1);
		await testApp.wait();
		const notes0 = await createNTestNotes(5, folders[0]);
		await testApp.wait();

		// create two conflict notes with parent_id folder 1
		const note1 = await Note.save({ title: 'note 1', parent_id: folders[0].id, is_conflict: 1 });
		await testApp.wait();
		const note2 = await Note.save({ title: 'note 2', parent_id: folders[0].id, is_conflict: 1 });
		await testApp.wait();

		// Testing history between conflict notes
		testApp.dispatch({ type: 'FOLDER_SELECT', id: Folder.conflictFolderId() });
		await testApp.wait();

		goToNote(testApp, note1);
		await testApp.wait();

		goToNote(testApp, note2);
		await testApp.wait();

		let state = testApp.store().getState();
		expect(state.selectedFolderId).toBe(Folder.conflictFolderId());
		expect(state.selectedNoteIds[0]).toBe(note2.id);

		goBackWard(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedFolderId).toBe(Folder.conflictFolderId());
		expect(state.selectedNoteIds[0]).toBe(note1.id);

		goForward(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedFolderId).toBe(Folder.conflictFolderId());
		expect(state.selectedNoteIds[0]).toBe(note2.id);

		// Testing history between conflict and non conflict notes.
		testApp.dispatch({ type: 'FOLDER_SELECT', id: folders[0].id });
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedFolderId).toBe(folders[0].id);
		expect(state.selectedNoteIds[0]).toBe(notes0[4].id);

		goBackWard(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedFolderId).toBe(Folder.conflictFolderId());
		expect(state.selectedNoteIds[0]).toBe(note2.id);

		goForward(state);
		await testApp.wait();

		state = testApp.store().getState();
		expect(state.selectedFolderId).toBe(folders[0].id);
		expect(state.selectedNoteIds[0]).toBe(notes0[4].id);
	}));

});