/* eslint-disable multiline-comment-style */

// =================================================================
// Command API types
// =================================================================

export interface Command {
	/**
	 * Name of command - must be globally unique
	 */
	name: string;

	/**
	 * Label to be displayed on menu items or keyboard shortcut editor for example.
	 * If it is missing, it's assumed it's a private command, to be called programmatically only.
	 * In that case the command will not appear in the shortcut editor or command panel, and logically
	 * should not be used as a menu item.
	 */
	label?: string;

	/**
	 * Icon to be used on toolbar buttons for example
	 */
	iconName?: string;

	/**
	 * Code to be ran when the command is executed. It may return a result.
	 */
	execute(...args: any[]): Promise<any | void>;

	/**
	 * Defines whether the command should be enabled or disabled, which in turns
	 * affects the enabled state of any associated button or menu item.
	 *
	 * The condition should be expressed as a "when-clause" (as in Visual Studio
	 * Code). It's a simple boolean expression that evaluates to `true` or
	 * `false`. It supports the following operators:
	 *
	 * Operator | Symbol | Example
	 * -- | -- | --
	 * Equality | == | "editorType == markdown"
	 * Inequality | != | "currentScreen != config"
	 * Or | \|\| | "noteIsTodo \|\| noteTodoCompleted"
	 * And | && | "oneNoteSelected && !inConflictFolder"
	 *
	 * Joplin, unlike VSCode, also supports parentheses, which allows creating
	 * more complex expressions such as `cond1 || (cond2 && cond3)`. Only one
	 * level of parentheses is possible (nested ones aren't supported).
	 *
	 * Currently the supported context variables aren't documented, but you can
	 * find the list below:
	 *
	 * - [Global When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts)
	 * - [Desktop app When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts)
	 *
	 * Note: Commands are enabled by default unless you use this property.
	 */
	enabledCondition?: string;
}

// =================================================================
// Interop API types
// =================================================================

export enum FileSystemItem {
	File = 'file',
	Directory = 'directory',
}

export enum ImportModuleOutputFormat {
	Markdown = 'md',
	Html = 'html',
}

/**
 * Used to implement a module to export data from Joplin. [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export) for an example.
 *
 * In general, all the event handlers you'll need to implement take a `context` object as a first argument. This object will contain the export or import path as well as various optional properties, such as which notes or notebooks need to be exported.
 *
 * To get a better sense of what it will contain it can be useful to print it using `console.info(context)`.
 */
export interface ExportModule {
	/**
	 * The format to be exported, eg "enex", "jex", "json", etc.
	 */
	format: string;

	/**
	 * The description that will appear in the UI, for example in the menu item.
	 */
	description: string;

	/**
	 * Whether the module will export a single file or multiple files in a directory. It affects the open dialog that will be presented to the user when using your exporter.
	 */
	target: FileSystemItem;

	/**
	 * Only applies to single file exporters or importers
	 * It tells whether the format can package multiple notes into one file.
	 * For example JEX or ENEX can, but HTML cannot.
	 */
	isNoteArchive: boolean;

	/**
	 * The extensions of the files exported by your module. For example, it is `["htm", "html"]` for the HTML module, and just `["jex"]` for the JEX module.
	 */
	fileExtensions?: string[];

	/**
	 * Called when the export process starts.
	 */
	onInit(context: ExportContext): Promise<void>;

	/**
	 * Called when an item needs to be processed. An "item" can be any Joplin object, such as a note, a folder, a notebook, etc.
	 */
	onProcessItem(context: ExportContext, itemType: number, item: any): Promise<void>;

	/**
	 * Called when a resource file needs to be exported.
	 */
	onProcessResource(context: ExportContext, resource: any, filePath: string): Promise<void>;

	/**
	 * Called when the export process is done.
	 */
	onClose(context: ExportContext): Promise<void>;
}

export interface ImportModule {
	/**
	 * The format to be exported, eg "enex", "jex", "json", etc.
	 */
	format: string;

	/**
	 * The description that will appear in the UI, for example in the menu item.
	 */
	description: string;

	/**
	 * Only applies to single file exporters or importers
	 * It tells whether the format can package multiple notes into one file.
	 * For example JEX or ENEX can, but HTML cannot.
	 */
	isNoteArchive: boolean;

	/**
	 * The type of sources that are supported by the module. Tells whether the module can import files or directories or both.
	 */
	sources: FileSystemItem[];

	/**
	 * Tells the file extensions of the exported files.
	 */
	fileExtensions?: string[];

	/**
	 * Tells the type of notes that will be generated, either HTML or Markdown (default).
	 */
	outputFormat?: ImportModuleOutputFormat;

	/**
	 * Called when the import process starts. There is only one event handler within which you should import the complete data.
	 */
	onExec(context: ImportContext): Promise<void>;
}

export interface ExportOptions {
	format?: string;
	path?: string;
	sourceFolderIds?: string[];
	sourceNoteIds?: string[];
	// modulePath?: string;
	target?: FileSystemItem;
}

export interface ExportContext {
	destPath: string;
	options: ExportOptions;

	/**
	 * You can attach your own custom data using this propery - it will then be passed to each event handler, allowing you to keep state from one event to the next.
	 */
	userData?: any;
}

export interface ImportContext {
	sourcePath: string;
	options: any;
	warnings: string[];
}

// =================================================================
// Misc types
// =================================================================

export interface Script {
	onStart?(event: any): Promise<void>;
}

export interface Disposable {
	// dispose():void;
}

export enum ModelType {
	Note = 1,
	Folder = 2,
	Setting = 3,
	Resource = 4,
	Tag = 5,
	NoteTag = 6,
	Search = 7,
	Alarm = 8,
	MasterKey = 9,
	ItemChange = 10,
	NoteResource = 11,
	ResourceLocalState = 12,
	Revision = 13,
	Migration = 14,
	SmartFilter = 15,
	Command = 16,
}

export interface VersionInfo {
	version: string;
	profileVersion: number;
	syncVersion: number;
}

// =================================================================
// Menu types
// =================================================================

export interface CreateMenuItemOptions {
	accelerator: string;
}

export enum MenuItemLocation {
	File = 'file',
	Edit = 'edit',
	View = 'view',
	Note = 'note',
	Tools = 'tools',
	Help = 'help',

	/**
	 * @deprecated Do not use - same as NoteListContextMenu
	 */
	Context = 'context',

	// If adding an item here, don't forget to update isContextMenuItemLocation()

	/**
	 * When a command is called from the note list context menu, the
	 * command will receive the following arguments:
	 *
	 * - `noteIds:string[]`: IDs of the notes that were right-clicked on.
	 */
	NoteListContextMenu = 'noteListContextMenu',

	EditorContextMenu = 'editorContextMenu',

	/**
	 * When a command is called from a folder context menu, the
	 * command will receive the following arguments:
	 *
	 * - `folderId:string`: ID of the folder that was right-clicked on
	 */
	FolderContextMenu = 'folderContextMenu',

	/**
	 * When a command is called from a tag context menu, the
	 * command will receive the following arguments:
	 *
	 * - `tagId:string`: ID of the tag that was right-clicked on
	 */
	TagContextMenu = 'tagContextMenu',
}

export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
	return [
		MenuItemLocation.Context,
		MenuItemLocation.NoteListContextMenu,
		MenuItemLocation.EditorContextMenu,
		MenuItemLocation.FolderContextMenu,
		MenuItemLocation.TagContextMenu,
	].includes(location);
}

export interface MenuItem {
	/**
	 * Command that should be associated with the menu item. All menu item should
	 * have a command associated with them unless they are a sub-menu.
	 */
	commandName?: string;

	/**
	 * Arguments that should be passed to the command. They will be as rest
	 * parameters.
	 */
	commandArgs?: any[];

	/**
	 * Set to "separator" to create a divider line
	 */
	type?: ('normal' | 'separator' | 'submenu' | 'checkbox' | 'radio');

	/**
	 * Accelerator associated with the menu item
	 */
	accelerator?: string;

	/**
	 * Menu items that should appear below this menu item. Allows creating a menu tree.
	 */
	submenu?: MenuItem[];

	/**
	 * Menu item label. If not specified, the command label will be used instead.
	 */
	label?: string;
}

// =================================================================
// View API types
// =================================================================

export interface ButtonSpec {
	id: ButtonId;
	title?: string;
	onClick?(): void;
}

export type ButtonId = string;

export enum ToolbarButtonLocation {
	/**
	 * This toolbar in the top right corner of the application. It applies to the note as a whole, including its metadata.
	 */
	NoteToolbar = 'noteToolbar',

	/**
	 * This toolbar is right above the text editor. It applies to the note body only.
	 */
	EditorToolbar = 'editorToolbar',
}

export type ViewHandle = string;

export interface EditorCommand {
	name: string;
	value?: any;
}

export interface DialogResult {
	id: ButtonId;
	formData?: any;
}

export interface Size {
	width?: number;
	height?: number;
}

export interface Rectangle {
	x?: number;
	y?: number;
	width?: number;
	height?: number;
}

// =================================================================
// Settings types
// =================================================================

export enum SettingItemType {
	Int = 1,
	String = 2,
	Bool = 3,
	Array = 4,
	Object = 5,
	Button = 6,
}

export enum SettingItemSubType {
	FilePathAndArgs = 'file_path_and_args',
	FilePath = 'file_path', // Not supported on mobile!
	DirectoryPath = 'directory_path', // Not supported on mobile!
}

export enum AppType {
	Desktop = 'desktop',
	Mobile = 'mobile',
	Cli = 'cli',
}

export enum SettingStorage {
	Database = 1,
	File = 2,
}

// Redefine a simplified interface to mask internal details
// and to remove function calls as they would have to be async.
export interface SettingItem {
	value: any;
	type: SettingItemType;

	/**
	 * Currently only used to display a file or directory selector. Always set
	 * `type` to `SettingItemType.String` when using this property.
	 */
	subType?: SettingItemSubType;

	label: string;
	description?: string;

	/**
	 * A public setting will appear in the Configuration screen and will be
	 * modifiable by the user. A private setting however will not appear there,
	 * and can only be changed programmatically. You may use this to store some
	 * values that you do not want to directly expose.
	 */
	public: boolean;

	/**
	 * You would usually set this to a section you would have created
	 * specifically for the plugin.
	 */
	section?: string;

	/**
	 * To create a setting with multiple options, set this property to `true`.
	 * That setting will render as a dropdown list in the configuration screen.
	 */
	isEnum?: boolean;

	/**
	 * This property is required when `isEnum` is `true`. In which case, it
	 * should contain a map of value => label.
	 */
	options?: Record<any, any>;

	/**
	 * Reserved property. Not used at the moment.
	 */
	appTypes?: AppType[];

	/**
	 * Set this to `true` to store secure data, such as passwords. Any such
	 * setting will be stored in the system keychain if one is available.
	 */
	secure?: boolean;

	/**
	 * An advanced setting will be moved under the "Advanced" button in the
	 * config screen.
	 */
	advanced?: boolean;

	/**
	 * Set the min, max and step values if you want to restrict an int setting
	 * to a particular range.
	 */
	minimum?: number;
	maximum?: number;
	step?: number;

	/**
	 * Either store the setting in the database or in settings.json. Defaults to database.
	 */
	storage?: SettingStorage;
}

export interface SettingSection {
	label: string;
	iconName?: string;
	description?: string;
	name?: string;
}

// =================================================================
// Data API types
// =================================================================

/**
 * An array of at least one element and at most three elements.
 *
 * - **[0]**: Resource name (eg. "notes", "folders", "tags", etc.)
 * - **[1]**: (Optional) Resource ID.
 * - **[2]**: (Optional) Resource link.
 */
export type Path = string[];

// =================================================================
// Content Script types
// =================================================================

export type PostMessageHandler = (message: any)=> Promise<any>;

/**
 * When a content script is initialised, it receives a `context` object.
 */
export interface ContentScriptContext {
	/**
	 * The plugin ID that registered this content script
	 */
	pluginId: string;

	/**
	 * The content script ID, which may be necessary to post messages
	 */
	contentScriptId: string;

	/**
	 * Can be used by CodeMirror content scripts to post a message to the plugin
	 */
	postMessage: PostMessageHandler;
}

export enum ContentScriptType {
	/**
	 * Registers a new Markdown-It plugin, which should follow the template
	 * below.
	 *
	 * ```javascript
	 * module.exports = {
	 *     default: function(context) {
	 *         return {
	 *             plugin: function(markdownIt, options) {
	 *                 // ...
	 *             },
	 *             assets: {
	 *                 // ...
	 *             },
	 *         }
	 *     }
	 * }
	 * ```
	 * See [the
	 * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
	 * for a simple Markdown-it plugin example.
	 *
	 * ## Exported members
	 *
	 * - The `context` argument is currently unused but could be used later on
	 *   to provide access to your own plugin so that the content script and
	 *   plugin can communicate.
	 *
	 * - The **required** `plugin` key is the actual Markdown-It plugin - check
	 *   the [official doc](https://github.com/markdown-it/markdown-it) for more
	 *   information. The `options` parameter is of type
	 *   [RuleOptions](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml.ts),
	 *   which contains a number of options, mostly useful for Joplin's internal
	 *   code.
	 *
	 * - Using the **optional** `assets` key you may specify assets such as JS
	 *   or CSS that should be loaded in the rendered HTML document. Check for
	 *   example the Joplin [Mermaid
	 *   plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
	 *   to see how the data should be structured.
	 *
	 * ## Posting messages from the content script to your plugin
	 *
	 * The application provides the following function to allow executing
	 * commands from the rendered HTML code:
	 *
	 * ```javascript
	 * const response = await webviewApi.postMessage(contentScriptId, message);
	 * ```
	 *
	 * - `contentScriptId` is the ID you've defined when you registered the
	 *   content script. You can retrieve it from the
	 *   {@link ContentScriptContext | context}.
	 * - `message` can be any basic JavaScript type (number, string, plain
	 *   object), but it cannot be a function or class instance.
	 *
	 * When you post a message, the plugin can send back a `response` thus
	 * allowing two-way communication:
	 *
	 * ```javascript
	 * await joplin.contentScripts.onMessage(contentScriptId, (message) => {
	 *     // Process message
	 *     return response; // Can be any object, string or number
	 * });
	 * ```
	 *
	 * See {@link JoplinContentScripts.onMessage} for more details, as well as
	 * the [postMessage
	 * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).
	 *
	 * ## Registering an existing Markdown-it plugin
	 *
	 * To include a regular Markdown-It plugin, that doesn't make use of any
	 * Joplin-specific features, you would simply create a file such as this:
	 *
	 * ```javascript
	 * module.exports = {
	 *     default: function(context) {
	 *         return {
	 *             plugin: require('markdown-it-toc-done-right');
	 *         }
	 *     }
	 * }
	 * ```
	 */
	MarkdownItPlugin = 'markdownItPlugin',

	/**
	 * Registers a new CodeMirror plugin, which should follow the template
	 * below.
	 *
	 * ```javascript
	 * module.exports = {
	 *     default: function(context) {
	 *         return {
	 *             plugin: function(CodeMirror) {
	 *                 // ...
	 *             },
	 *             codeMirrorResources: [],
	 *             codeMirrorOptions: {
	 *                                  // ...
	 *                       },
	 *             assets: {
	 *                 // ...
	 *             },
	 *         }
	 *     }
	 * }
	 * ```
	 *
	 * - The `context` argument is currently unused but could be used later on
	 *   to provide access to your own plugin so that the content script and
	 *   plugin can communicate.
	 *
	 * - The `plugin` key is your CodeMirror plugin. This is where you can
	 *   register new commands with CodeMirror or interact with the CodeMirror
	 *   instance as needed.
	 *
	 * - The `codeMirrorResources` key is an array of CodeMirror resources that
	 *   will be loaded and attached to the CodeMirror module. These are made up
	 *   of addons, keymaps, and modes. For example, for a plugin that want's to
	 *   enable clojure highlighting in code blocks. `codeMirrorResources` would
	 *   be set to `['mode/clojure/clojure']`.
	 *
	 * - The `codeMirrorOptions` key contains all the
	 *   [CodeMirror](https://codemirror.net/doc/manual.html#config) options
	 *   that will be set or changed by this plugin. New options can alse be
	 *   declared via
	 *   [`CodeMirror.defineOption`](https://codemirror.net/doc/manual.html#defineOption),
	 *   and then have their value set here. For example, a plugin that enables
	 *   line numbers would set `codeMirrorOptions` to `{'lineNumbers': true}`.
	 *
	 * - Using the **optional** `assets` key you may specify **only** CSS assets
	 *   that should be loaded in the rendered HTML document. Check for example
	 *   the Joplin [Mermaid
	 *   plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
	 *   to see how the data should be structured.
	 *
	 * One of the `plugin`, `codeMirrorResources`, or `codeMirrorOptions` keys
	 * must be provided for the plugin to be valid. Having multiple or all
	 * provided is also okay.
	 *
	 * See also the [demo
	 * plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
	 * for an example of all these keys being used in one plugin.
	 *
	 * ## Posting messages from the content script to your plugin
	 *
	 * In order to post messages to the plugin, you can use the postMessage
	 * function passed to the {@link ContentScriptContext | context}.
	 *
	 * ```javascript
	 * const response = await context.postMessage('messageFromCodeMirrorContentScript');
	 * ```
	 *
	 * When you post a message, the plugin can send back a `response` thus
	 * allowing two-way communication:
	 *
	 * ```javascript
	 * await joplin.contentScripts.onMessage(contentScriptId, (message) => {
	 *     // Process message
	 *     return response; // Can be any object, string or number
	 * });
	 * ```
	 *
	 * See {@link JoplinContentScripts.onMessage} for more details, as well as
	 * the [postMessage
	 * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).
	 *
	 */
	CodeMirrorPlugin = 'codeMirrorPlugin',
}
