import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index';
import { _ } from '@joplin/lib/locale';
import { copyHtmlToClipboard } from './clipboardUtils';
import bridge from '../../../services/bridge';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng, svgDimensions, buildMenuItems, ContextMenuItem } from './contextMenuUtils';
const Menu = bridge().Menu;
import Resource, { resourceOcrStatusToString } from '@joplin/lib/models/Resource';
import BaseItem from '@joplin/lib/models/BaseItem';
import BaseModel, { ModelType } from '@joplin/lib/BaseModel';
import { NoteEntity, ResourceEntity, ResourceOcrDriverId, ResourceOcrStatus } from '@joplin/lib/services/database/types';
import { TinyMceEditorEvents } from '../NoteBody/TinyMCE/utils/types';
import { itemIsReadOnlySync, ItemSlice } from '@joplin/lib/models/utils/readOnly';
import Setting from '@joplin/lib/models/Setting';
import ItemChange from '@joplin/lib/models/ItemChange';
import shim, { MessageBoxType } from '@joplin/lib/shim';
import { openFileWithExternalEditor } from '@joplin/lib/services/ExternalEditWatcher/utils';
import CommandService from '@joplin/lib/services/CommandService';
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const fs = require('fs-extra');
const { writeFile } = require('fs-extra');
const { clipboard } = require('electron');
const { toSystemSlashes } = require('@joplin/lib/path-utils');

function handleCopyToClipboard(options: ContextMenuOptions) {
	if (options.textToCopy) {
		clipboard.writeText(options.textToCopy);
	} else if (options.htmlToCopy) {
		copyHtmlToClipboard(options.htmlToCopy);
	}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
async function saveFileData(data: any, filename: string) {
	const newFilePath = await bridge().showSaveDialog({ defaultPath: filename });
	if (!newFilePath) return;
	await writeFile(newFilePath, data);
}

// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
export async function openItemById(itemId: string, dispatch: Function, hash = '') {

	const item = await BaseItem.loadItemById(itemId);

	if (!item) throw new Error(`No item with ID ${itemId}`);

	if (item.type_ === BaseModel.TYPE_RESOURCE) {
		const resource = item as ResourceEntity;
		const localState = await Resource.localState(resource);
		if (localState.fetch_status !== Resource.FETCH_STATUS_DONE || !!resource.encryption_blob_encrypted) {
			if (localState.fetch_status === Resource.FETCH_STATUS_ERROR) {
				bridge().showErrorMessageBox(`${_('There was an error downloading this attachment:')}\n\n${localState.fetch_error}`);
			} else {
				bridge().showErrorMessageBox(_('This attachment is not downloaded or not decrypted yet'));
			}
			return;
		}

		try {
			if (itemIsReadOnlySync(ModelType.Resource, ItemChange.SOURCE_UNSPECIFIED, resource as ItemSlice, Setting.value('sync.userId'), BaseItem.syncShareCache)) {
				await ResourceEditWatcher.instance().openAsReadOnly(resource.id);
			} else {
				await ResourceEditWatcher.instance().openAndWatch(resource.id);
			}
		} catch (error) {
			console.error(error);
			bridge().showErrorMessageBox(error.message);
		}
	} else if (item.type_ === BaseModel.TYPE_NOTE) {
		const note = item as NoteEntity;

		dispatch({
			type: 'FOLDER_AND_NOTE_SELECT',
			folderId: note.parent_id,
			noteId: note.id,
			hash,
		});
	} else {
		throw new Error(`Unsupported item type: ${item.type_}`);
	}
}

// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
export function menuItems(dispatch: Function): ContextMenuItems {
	const makeSeparator = (): ContextMenuItem => {
		return {
			isActive: () => { return true; },
			label: '',
			onAction: () => {},
			isSeparator: true,
		};
	};

	return {
		open: {
			label: _('Open...'),
			onAction: async (options: ContextMenuOptions) => {
				if (options.resourceId) {
					await openItemById(options.resourceId, dispatch);
				} else if (options.linkToOpen) {
					await CommandService.instance().execute('openItem', options.linkToOpen);
				} else {
					await shim.showErrorDialog('No link found');
				}
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => (
				(!options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource || itemType === ContextMenuItemType.NoteLink))
				|| (!!options.linkToOpen && itemType === ContextMenuItemType.Link)
			),
		},
		openNoteInNewWindow: {
			label: _('Open in new window'),
			onAction: async (options: ContextMenuOptions) => {
				await CommandService.instance().execute('openNoteInNewWindow', options.resourceId);
			},
			isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.NoteLink,
		},
		saveAs: {
			label: _('Save as...'),
			onAction: async (options: ContextMenuOptions) => {
				const { resourcePath, resource } = await resourceInfo(options);
				const filePath = await bridge().showSaveDialog({
					defaultPath: resource.filename ? resource.filename : resource.title,
				});
				if (!filePath) return;
				await fs.copy(resourcePath, filePath);
			},
			// We handle images received as text separately as it can be saved in multiple formats
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource),
		},
		saveAsSvg: {
			label: _('Save as %s', 'SVG'),
			onAction: async (options: ContextMenuOptions) => {
				await saveFileData(options.textToCopy, options.filename);
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy && itemType === ContextMenuItemType.Image && options.mime?.startsWith('image/svg'),
		},
		saveAsPng: {
			label: _('Save as %s', 'PNG'),
			onAction: async (options: ContextMenuOptions) => {
				// First convert it to png then save
				if (options.mime !== 'image/svg+xml') {
					throw new Error(`Unsupported image type: ${options.mime}`);
				}
				if (!options.filename) {
					throw new Error('Filename is needed to save as png');
				}
				// double dimensions to make sure it's always big enough even on hdpi screens
				const [width, height] = svgDimensions(document, options.textToCopy).map((x: number) => x * 2 || undefined);
				const dataUri = textToDataUri(options.textToCopy, options.mime);
				const png = await svgUriToPng(document, dataUri, width, height);
				const filename = options.filename.replace('.svg', '.png');
				await saveFileData(png, filename);
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy && itemType === ContextMenuItemType.Image && options.mime?.startsWith('image/svg'),
		},
		separator1: makeSeparator(),
		revealInFolder: {
			label: _('Reveal file in folder'),
			onAction: async (options: ContextMenuOptions) => {
				const { resourcePath } = await resourceInfo(options);
				bridge().showItemInFolder(resourcePath);
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource),
		},
		separator2: makeSeparator(),
		recognizeHandwrittenImage: {
			label: _('Recognize handwritten image'),
			onAction: async (options: ContextMenuOptions) => {
				const syncTargetId = Setting.value('sync.target');
				if (!SyncTargetRegistry.isJoplinServerOrCloud(syncTargetId)) {
					await shim.showMessageBox(_('This feature is only available on Joplin Cloud and Joplin Server.'), { type: MessageBoxType.Error });
					return;
				}

				if (!Setting.value('ocr.handwrittenTextDriverEnabled')) {
					await shim.showMessageBox(_('This feature is disabled by default, you need to manually enable it by turning on the option to \'Enable handwritten transcription\'.'), { type: MessageBoxType.Error });
					return;
				}

				const { resource } = await resourceInfo(options);

				if (!['image/png', 'image/jpg', 'image/jpeg', 'image/bmp'].includes(resource.mime)) {
					await shim.showMessageBox(_('This image type is not supported by the recognition system.'), { type: MessageBoxType.Error });
					return;
				}

				await Resource.save({
					id: resource.id,
					ocr_status: ResourceOcrStatus.Todo,
					ocr_driver_id: ResourceOcrDriverId.HandwrittenText,
					ocr_details: '',
					ocr_error: '',
					ocr_text: '',
				});
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => {
				return itemType === ContextMenuItemType.Resource || (itemType === ContextMenuItemType.Image && options.resourceId);
			},
		},
		copyOcrText: {
			label: _('View OCR text'),
			onAction: async (options: ContextMenuOptions) => {
				const { resource } = await resourceInfo(options);

				if (resource.ocr_status === ResourceOcrStatus.Done) {
					const tempFilePath = `${Setting.value('tempDir')}/${resource.id}_ocr.txt`;
					await shim.fsDriver().writeFile(tempFilePath, resource.ocr_text, 'utf8');
					await openFileWithExternalEditor(tempFilePath, bridge());
				} else {
					bridge().showInfoMessageBox(_('This attachment does not have OCR data (Status: %s)', resourceOcrStatusToString(resource.ocr_status)));
				}
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => {
				return itemType === ContextMenuItemType.Resource || (itemType === ContextMenuItemType.Image && options.resourceId);
			},
		},
		createAccessibleDocument: {
			label: _('Create accessible document'),
			onAction: async (options: ContextMenuOptions) => {
				const { resource } = await resourceInfo(options);
				await CommandService.instance().execute('createAccessibleDocument', resource.id);
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => {
				return itemType === ContextMenuItemType.Resource || (itemType === ContextMenuItemType.Image && options.resourceId);
			},
		},
		separator3: makeSeparator(),
		copyPathToClipboard: {
			label: _('Copy path to clipboard'),
			onAction: async (options: ContextMenuOptions) => {
				let path = '';
				if (options.textToCopy && options.mime) {
					path = textToDataUri(options.textToCopy, options.mime);
				} else {
					const { resourcePath } = await resourceInfo(options);
					if (resourcePath) path = toSystemSlashes(resourcePath);
				}
				if (!path) return;
				clipboard.writeText(path);
			},
			isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
		},
		copyImage: {
			label: _('Copy image'),
			onAction: async (options: ContextMenuOptions) => {
				const { resourcePath } = await resourceInfo(options);
				const image = bridge().createImageFromPath(resourcePath);
				clipboard.writeImage(image);
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && itemType === ContextMenuItemType.Image,
		},
		copyLinkUrl: {
			label: _('Copy Link Address'),
			onAction: async (options: ContextMenuOptions) => {
				clipboard.writeText(options.linkToCopy !== null ? options.linkToCopy : options.textToCopy);
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => itemType === ContextMenuItemType.Link || !!options.linkToCopy,
		},
		separator4: makeSeparator(),
		cut: {
			label: _('Cut'),
			onAction: async (options: ContextMenuOptions) => {
				handleCopyToClipboard(options);
				options.insertContent('');
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => itemType !== ContextMenuItemType.Image && (!options.isReadOnly && (!!options.textToCopy || !!options.htmlToCopy)),
		},
		copy: {
			label: _('Copy'),
			onAction: async (options: ContextMenuOptions) => {
				handleCopyToClipboard(options);
			},
			isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => itemType !== ContextMenuItemType.Image && (!!options.textToCopy || !!options.htmlToCopy),
		},
		paste: {
			label: _('Paste'),
			onAction: async (_options: ContextMenuOptions) => {
				bridge().activeWindow().webContents.paste();
			},
			isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.isReadOnly && clipboard.availableFormats().length > 0,
		},
		pasteAsText: {
			label: _('Paste as text'),
			onAction: async (options: ContextMenuOptions) => {
				options.fireEditorEvent(TinyMceEditorEvents.PasteAsText);
			},
			isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.isReadOnly && (!!clipboard.readText() || !!clipboard.readHTML()),
		},
	};
}

// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
export default async function contextMenu(options: ContextMenuOptions, dispatch: Function) {
	const menu = new Menu();

	if (!('readyOnly' in options)) options.isReadOnly = true;

	const items = await buildMenuItems(menuItems(dispatch), options);

	for (const item of items) {
		menu.append(item);
	}

	return menu;
}
