import { ImportExportResult } from './types';

import InteropService_Importer_Base from './InteropService_Importer_Base';
import BaseItem from '../../models/BaseItem';
import BaseModel from '../../BaseModel';
import Resource from '../../models/Resource';
import Folder from '../../models/Folder';
import NoteTag from '../../models/NoteTag';
import Note from '../../models/Note';
import Tag from '../../models/Tag';
const { sprintf } = require('sprintf-js');
import shim from '../../shim';
const { fileExtension } = require('../../path-utils');
import uuid from '../../uuid';

export default class InteropService_Importer_Raw extends InteropService_Importer_Base {
	public async exec(result: ImportExportResult) {
		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
		const itemIdMap: any = {};
		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
		const createdResources: any = {};
		const noteTagsToCreate = [];
		const destinationFolderId = this.options_.destinationFolderId;

		const replaceLinkedItemIds = async (noteBody: string) => {
			let output = noteBody;
			const itemIds = Note.linkedItemIds(noteBody);

			for (let i = 0; i < itemIds.length; i++) {
				const id = itemIds[i];
				if (!itemIdMap[id]) itemIdMap[id] = uuid.create();
				output = output.replace(new RegExp(id, 'gi'), itemIdMap[id]);
			}

			return output;
		};

		const stats = await shim.fsDriver().readDirStats(this.sourcePath_);

		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
		const folderExists = function(stats: any[], folderId: string) {
			folderId = folderId.toLowerCase();
			for (let i = 0; i < stats.length; i++) {
				const stat = stats[i];
				const statId = BaseItem.pathToId(stat.path);
				if (statId.toLowerCase() === folderId) return true;
			}
			return false;
		};

		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
		let defaultFolder_: any = null;
		const defaultFolder = async () => {
			if (defaultFolder_) return defaultFolder_;
			const folderTitle = await Folder.findUniqueItemTitle(this.options_.defaultFolderTitle ? this.options_.defaultFolderTitle : 'Imported', '');
			// eslint-disable-next-line require-atomic-updates
			defaultFolder_ = await Folder.save({ title: folderTitle });
			return defaultFolder_;
		};

		const setFolderToImportTo = async (itemParentId: string) => {
			// Logic is a bit complex here:
			// - If a destination folder was specified, move the note to it.
			// - Otherwise, if the associated folder exists, use this.
			// - If it doesn't exist, use the default folder. This is the case for example when importing JEX archives that contain only one or more notes, but no folder.
			const itemParentExists = folderExists(stats, itemParentId);

			if (!itemIdMap[itemParentId]) {
				if (destinationFolderId) {
					itemIdMap[itemParentId] = destinationFolderId;
				} else if (!itemParentExists) {
					const parentFolder = await defaultFolder();
					// eslint-disable-next-line require-atomic-updates
					itemIdMap[itemParentId] = parentFolder.id;
				} else {
					itemIdMap[itemParentId] = uuid.create();
				}
			}
		};

		for (let i = 0; i < stats.length; i++) {
			const stat = stats[i];

			try {
				if (stat.isDirectory()) continue;
				if (fileExtension(stat.path).toLowerCase() !== 'md') continue;

				const content = await shim.fsDriver().readFile(`${this.sourcePath_}/${stat.path}`);
				const item = await BaseItem.unserialize(content);
				const itemType = item.type_;
				const ItemClass = BaseItem.itemClass(item);

				delete item.type_;

				if (itemType === BaseModel.TYPE_NOTE) {
					await setFolderToImportTo(item.parent_id);

					if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
					item.id = itemIdMap[item.id];
					item.parent_id = itemIdMap[item.parent_id];
					item.body = await replaceLinkedItemIds(item.body);
				} else if (itemType === BaseModel.TYPE_FOLDER) {
					if (destinationFolderId) continue;

					if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
					item.id = itemIdMap[item.id];

					if (item.parent_id) {
						await setFolderToImportTo(item.parent_id);
						item.parent_id = itemIdMap[item.parent_id];
					}

					item.title = await Folder.findUniqueItemTitle(item.title, item.parent_id);
				} else if (itemType === BaseModel.TYPE_RESOURCE) {
					const sourceId = item.id;
					if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
					item.id = itemIdMap[item.id];
					createdResources[item.id] = item;

					const sourceResourcePath = `${this.sourcePath_}/resources/${Resource.filename({ ...item, id: sourceId })}`;
					const destPath = Resource.fullPath(item);

					if (await shim.fsDriver().exists(sourceResourcePath)) {
						await shim.fsDriver().copy(sourceResourcePath, destPath);
					} else {
						result.warnings.push(sprintf('Could not find resource file: %s', sourceResourcePath));
					}
				} else if (itemType === BaseModel.TYPE_TAG) {
					const tag = await Tag.loadByTitle(item.title);
					if (tag) {
						itemIdMap[item.id] = tag.id;
						continue;
					}

					const tagId = uuid.create();
					itemIdMap[item.id] = tagId;
					item.id = tagId;
				} else if (itemType === BaseModel.TYPE_NOTE_TAG) {
					noteTagsToCreate.push(item);
					continue;
				}

				await ItemClass.save(item, { isNew: true, autoTimestamp: false });
			} catch (error) {
				error.message = `Could not import: ${stat.path}: ${error.message}`;
				throw error;
			}
		}

		for (let i = 0; i < noteTagsToCreate.length; i++) {
			const noteTag = noteTagsToCreate[i];
			const newNoteId = itemIdMap[noteTag.note_id];
			const newTagId = itemIdMap[noteTag.tag_id];

			if (!newNoteId) {
				result.warnings.push(sprintf('Non-existent note %s referenced in tag %s', noteTag.note_id, noteTag.tag_id));
				continue;
			}

			if (!newTagId) {
				result.warnings.push(sprintf('Non-existent tag %s for note %s', noteTag.tag_id, noteTag.note_id));
				continue;
			}

			noteTag.id = uuid.create();
			noteTag.note_id = newNoteId;
			noteTag.tag_id = newTagId;

			await NoteTag.save(noteTag, { isNew: true });
		}

		return result;
	}
}
