import { SlashCommand } from '../../../slash-commands/SlashCommand.js';
import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js';
import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js';
import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js';
import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
import { isTrueBoolean } from '../../../utils.js';
import { QuickReplyApi } from '../api/QuickReplyApi.js';
import { QuickReply } from './QuickReply.js';
import { QuickReplySet } from './QuickReplySet.js';

export class SlashCommandHandler {
    /** @type {QuickReplyApi} */ api;




    constructor(/** @type {QuickReplyApi} */api) {
        this.api = api;
    }




    init() {
        function getExecutionIcons(/** @type {QuickReply} */ qr) {
            let icons = '';
            if (qr.preventAutoExecute) icons += '🚫';
            if (qr.isHidden) icons += '👁️';
            if (qr.executeOnStartup) icons += '🚀';
            if (qr.executeOnUser) icons += enumIcons.user;
            if (qr.executeOnAi) icons += enumIcons.assistant;
            if (qr.executeOnChatChange) icons += '💬';
            if (qr.executeOnNewChat) icons += '🆕';
            if (qr.executeOnGroupMemberDraft) icons += enumIcons.group;
            if (qr.executeBeforeGeneration) icons += '✈️';
            return icons;
        }

        const localEnumProviders = {
            /** All quick reply sets, optionally filtering out sets that wer already used in the "set" named argument */
            qrSets: (executor) => QuickReplySet.list.filter(qrSet => qrSet.name != String(executor.namedArgumentList.find(x => x.name == 'set')?.value))
                .map(qrSet => new SlashCommandEnumValue(qrSet.name, null, enumTypes.enum, 'S')),

            /** All QRs inside a set, utilizing the "set" named argument */
            qrEntries: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => {
                const icons = getExecutionIcons(qr);
                const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim();
                return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr);
            }) ?? [],

            /** All QRs inside a set, utilizing the "set" named argument, returns the QR's ID */
            qrIds: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => {
                const icons = getExecutionIcons(qr);
                const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim();
                return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr, null, ()=>qr.id.toString(), true);
            }) ?? [],

            /** All QRs as a set.name string, to be able to execute, for example via the /run command */
            qrExecutables: () => {
                const globalSetList = this.api.settings.config.setList;
                const chatSetList = this.api.settings.chatConfig?.setList;

                const globalQrs = globalSetList.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat();
                const chatQrs = chatSetList?.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat() ?? [];
                const otherQrs = QuickReplySet.list.filter(set => !globalSetList.some(link => link.set.name === set.name && !chatSetList?.some(link => link.set.name === set.name)))
                    .map(set => set.qrList.map(qr => ({ set, qr }))).flat();

                return [
                    ...globalQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[global] ${x.qr.title || x.qr.message}`, enumTypes.name, enumIcons.qr)),
                    ...chatQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[chat] ${x.qr.title || x.qr.message}`, enumTypes.enum, enumIcons.qr)),
                    ...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)),
                ];
            },
        };

        window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr',
            callback: (_, value) => this.executeQuickReplyByIndex(Number(value)),
            unnamedArgumentList: [
                new SlashCommandArgument(
                    'number', [ARGUMENT_TYPE.NUMBER], true,
                ),
            ],
            helpString: 'Activates the specified Quick Reply',
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qrset',
            callback: () => {
                toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.');
                return '';
            },
            helpString: '<strong>DEPRECATED</strong> – The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.',
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set',
            callback: (args, value) => {
                this.toggleGlobalSet(value, args);
                return '';
            },
            namedArgumentList: [
                new SlashCommandNamedArgument(
                    'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
                ),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: 'Toggle global QR set',
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-on',
            callback: (args, value) => {
                this.addGlobalSet(value, args);
                return '';
            },
            namedArgumentList: [
                new SlashCommandNamedArgument(
                    'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
                ),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: 'Activate global QR set',
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-off',
            callback: (_, value) => {
                this.removeGlobalSet(value);
                return '';
            },
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: 'Deactivate global QR set',
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set',
            callback: (args, value) => {
                this.toggleChatSet(value, args);
                return '';
            },
            namedArgumentList: [
                new SlashCommandNamedArgument(
                    'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
                ),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: 'Toggle chat QR set',
        }));

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-on',
            callback: (args, value) => {
                this.addChatSet(value, args);
                return '';
            },
            namedArgumentList: [
                new SlashCommandNamedArgument(
                    'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
                ),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: 'Activate chat QR set',
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-off',
            callback: (_, value) => {
                this.removeChatSet(value);
                return '';
            },
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: 'Deactivate chat QR set',
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-list',
            callback: (_, value) => JSON.stringify(this.listSets(value ?? 'all')),
            returns: 'list of QR sets',
            namedArgumentList: [],
            unnamedArgumentList: [
                new SlashCommandArgument(
                    'set type', [ARGUMENT_TYPE.STRING], false, false, 'all', ['all', 'global', 'chat'],
                ),
            ],
            helpString: 'Gets a list of the names of all quick reply sets.',
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-list',
            callback: (_, value) => {
                return JSON.stringify(this.listQuickReplies(value));
            },
            returns: 'list of QRs',
            namedArgumentList: [],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: 'Gets a list of the names of all quick replies in this quick reply set.',
        }));

        const qrArgs = [
            SlashCommandNamedArgument.fromProps({
                name: 'set',
                description: 'name of the QR set, e.g., set=PresetName1',
                typeList: [ARGUMENT_TYPE.STRING],
                isRequired: true,
                enumProvider: localEnumProviders.qrSets,
            }),
            SlashCommandNamedArgument.fromProps({
                name: 'label',
                description: 'text on the button, e.g., label=MyButton',
                typeList: [ARGUMENT_TYPE.STRING],
                isRequired: false,
                enumProvider: localEnumProviders.qrEntries,
            }),
            SlashCommandNamedArgument.fromProps({
                name: 'icon',
                description: 'icon to show on the button, e.g., icon=fa-pencil',
                typeList: [ARGUMENT_TYPE.STRING],
                isRequired: false,
            }),
            SlashCommandNamedArgument.fromProps({
                name: 'showlabel',
                description: 'whether to show the label even when an icon is assigned, e.g., icon=fa-pencil showlabel=true',
                typeList: [ARGUMENT_TYPE.BOOLEAN],
                isRequired: false,
            }),
            new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
            new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
            new SlashCommandNamedArgument('user', 'auto execute on user message, e.g., user=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
            new SlashCommandNamedArgument('bot', 'auto execute on AI message, e.g., bot=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
            new SlashCommandNamedArgument('load', 'auto execute on chat load, e.g., load=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
            new SlashCommandNamedArgument('new', 'auto execute on new chat, e.g., new=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
            new SlashCommandNamedArgument('group', 'auto execute on group member selection, e.g., group=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
            new SlashCommandNamedArgument('generation', 'auto execute before message generation, e.g., generation=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
            new SlashCommandNamedArgument('title', 'title / tooltip to be shown on button, e.g., title="My Fancy Button"', [ARGUMENT_TYPE.STRING], false),
        ];
        const qrUpdateArgs = [
            new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false),
            SlashCommandNamedArgument.fromProps({
                name: 'id',
                description: 'numeric ID of the QR, e.g., id=42',
                typeList: [ARGUMENT_TYPE.NUMBER],
                isRequired: false,
                enumProvider: localEnumProviders.qrIds,
            }),
        ];

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create',
            callback: (args, message) => {
                this.createQuickReply(args, message);
                return '';
            },
            namedArgumentList: qrArgs,
            unnamedArgumentList: [
                new SlashCommandArgument(
                    'command', [ARGUMENT_TYPE.STRING], true,
                ),
            ],
            helpString: `
                <div>Creates a new Quick Reply.</div>
                <div>
                    <strong>Example:</strong>
                    <ul>
                        <li>
                            <pre><code>/qr-create set=MyPreset label=MyButton /echo 123</code></pre>
                        </li>
                    </ul>
                </div>
            `,
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-get',
            callback: (args, _) => {
                return this.getQuickReply(args);
            },
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({
                    name: 'set',
                    description: 'name of the QR set, e.g., set=PresetName1',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
                SlashCommandNamedArgument.fromProps({
                    name: 'label',
                    description: 'text on the button, e.g., label=MyButton',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: false,
                    enumProvider: localEnumProviders.qrEntries,
                }),
                SlashCommandNamedArgument.fromProps({
                    name: 'id',
                    description: 'numeric ID of the QR, e.g., id=42',
                    typeList: [ARGUMENT_TYPE.NUMBER],
                    isRequired: false,
                    enumProvider: localEnumProviders.qrIds,
                }),
            ],
            returns: 'a dictionary with all the QR\'s properties',
            helpString: `
                <div>Get a Quick Reply's properties.</div>
                <div>
                    <strong>Examples:</strong>
                    <ul>
                        <li>
                            <pre><code>/qr-get set=MyPreset label=MyButton | /echo</code></pre>
                            <pre><code>/qr-get set=MyPreset id=42 | /echo</code></pre>
                        </li>
                    </ul>
                </div>
            `,
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update',
            callback: (args, message) => {
                this.updateQuickReply(args, message);
                return '';
            },
            returns: 'updated quick reply',
            namedArgumentList: [...qrUpdateArgs, ...qrArgs.map(it=>{
                if (it.name == 'label') {
                    const clone = SlashCommandNamedArgument.fromProps(it);
                    clone.isRequired = false;
                    return clone;
                }
                return it;
            })],
            unnamedArgumentList: [
                new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
            ],
            helpString: `
                <div>
                    Updates Quick Reply.
                </div>
                <div>
                    <strong>Example:</strong>
                    <ul>
                        <li>
                            <pre><code>/qr-update set=MyPreset label=MyButton newlabel=MyRenamedButton /echo 123</code></pre>
                        </li>
                    </ul>
                </div>
            `,
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-delete',
            callback: (args, name) => {
                this.deleteQuickReply(args, name);
                return '';
            },
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({
                    name: 'set',
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
                SlashCommandNamedArgument.fromProps({
                    name: 'label',
                    description: 'Quick Reply label',
                    typeList: [ARGUMENT_TYPE.STRING],
                    enumProvider: localEnumProviders.qrEntries,
                }),
                SlashCommandNamedArgument.fromProps({
                    name: 'id',
                    description: 'numeric ID of the QR, e.g., id=42',
                    typeList: [ARGUMENT_TYPE.NUMBER],
                    enumProvider: localEnumProviders.qrIds,
                }),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'label',
                    typeList: [ARGUMENT_TYPE.STRING],
                    enumProvider: localEnumProviders.qrEntries,
                }),
            ],
            helpString: 'Deletes a Quick Reply from the specified set. (Label must be provided via named or unnamed argument)',
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextadd',
            callback: (args, name) => {
                this.createContextItem(args, name);
                return '';
            },
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({
                    name: 'set',
                    description: 'Name of QR set to add the context menu to',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
                SlashCommandNamedArgument.fromProps({
                    name: 'label',
                    description: 'Label of Quick Reply to add the context menu to',
                    typeList: [ARGUMENT_TYPE.STRING],
                    enumProvider: localEnumProviders.qrEntries,
                }),
                SlashCommandNamedArgument.fromProps({
                    name: 'id',
                    description: 'Numeric ID of Quick Reply to add the context menu to, e.g. id=42',
                    typeList: [ARGUMENT_TYPE.NUMBER],
                    enumProvider: localEnumProviders.qrIds,
                }),
                new SlashCommandNamedArgument(
                    'chain',
                    'If true, button QR is sent together with (before) the clicked QR from the context menu',
                    [ARGUMENT_TYPE.BOOLEAN],
                    false,
                    false,
                    'false',
                ),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'Name of QR set to add as a context menu',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: `
                <div>
                    Add a context menu preset to a QR.
                </div>
                <div>
                    If <code>id</code> and <code>label</code> are both provided, <code>id</code> will be used.
                </div>
                <div>
                    <strong>Example:</strong>
                    <ul>
                        <li>
                            <pre><code>/qr-contextadd set=MyQRSetWithTheButton label=MyButton chain=true MyQRSetWithContextItems</code></pre>
                        </li>
                    </ul>
                </div>
            `,
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextdel',
            callback: (args, name) => {
                this.deleteContextItem(args, name);
                return '';
            },
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({
                    name: 'set',
                    description: 'Name of QR set to remove the context menu from',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
                SlashCommandNamedArgument.fromProps({
                    name: 'label',
                    description: 'Label of Quick Reply to remove the context menu from',
                    typeList: [ARGUMENT_TYPE.STRING],
                    enumProvider: localEnumProviders.qrEntries,
                }),
                SlashCommandNamedArgument.fromProps({
                    name: 'id',
                    description: 'Numeric ID of Quick Reply to remove the context menu from, e.g. id=42',
                    typeList: [ARGUMENT_TYPE.NUMBER],
                    enumProvider: localEnumProviders.qrIds,
                }),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'Name of QR set to remove',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: `
                <div>
                    Remove context menu preset from a QR.
                </div>
                <div>
                    If <code>id</code> and <code>label</code> are both provided, <code>id</code> will be used.
                </div>
                <div>
                    <strong>Example:</strong>
                    <ul>
                        <li>
                            <pre><code>/qr-contextdel set=MyPreset label=MyButton MyOtherPreset</code></pre>
                        </li>
                    </ul>
                </div>
            `,
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextclear',
            callback: (args, label) => {
                this.clearContextMenu(args, label);
                return '';
            },
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({
                    name: 'set',
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
                SlashCommandNamedArgument.fromProps({
                    name: 'id',
                    description: 'numeric ID of the QR, e.g., id=42',
                    typeList: [ARGUMENT_TYPE.NUMBER],
                    enumProvider: localEnumProviders.qrIds,
                }),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'Quick Reply label',
                    typeList: [ARGUMENT_TYPE.STRING],
                    enumProvider: localEnumProviders.qrEntries,
                }),
            ],
            helpString: `
                <div>
                    Remove all context menu presets from a QR.
                </div>
                <div>
                    If <code>id</code> and a label are both provided, <code>id</code> will be used.
                </div>
                <div>
                    <strong>Example:</strong>
                    <ul>
                        <li>
                            <pre><code>/qr-contextclear set=MyPreset MyButton</code></pre>
                        </li>
                    </ul>
                </div>
            `,
        }));

        const presetArgs = [
            new SlashCommandNamedArgument('nosend', 'disable send / insert in user input (invalid for slash commands)', [ARGUMENT_TYPE.BOOLEAN], false),
            new SlashCommandNamedArgument('before', 'place QR before user input', [ARGUMENT_TYPE.BOOLEAN], false),
            new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false),
        ];
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create',
            callback: async (args, name) => {
                await this.createSet(name, args);
                return '';
            },
            aliases: ['qr-presetadd'],
            namedArgumentList: presetArgs,
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                    forceEnum: false,
                }),
            ],
            helpString: `
                <div>
                    Create a new preset (overrides existing ones).
                </div>
                <div>
                    <strong>Example:</strong>
                    <ul>
                        <li>
                            <pre><code>/qr-set-add MyNewPreset</code></pre>
                        </li>
                    </ul>
                </div>
            `,
        }));

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update',
            callback: async (args, name) => {
                await this.updateSet(name, args);
                return '';
            },
            aliases: ['qr-presetupdate'],
            namedArgumentList: presetArgs,
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: `
                <div>
                    Update an existing preset.
                </div>
                <div>
                    <strong>Example:</strong>
                    <pre><code>/qr-set-update enabled=false MyPreset</code></pre>
                </div>
            `,
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete',
            callback: async (_, name) => {
                await this.deleteSet(name);
                return '';
            },
            aliases: ['qr-presetdelete'],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({
                    description: 'QR set name',
                    typeList: [ARGUMENT_TYPE.STRING],
                    isRequired: true,
                    enumProvider: localEnumProviders.qrSets,
                }),
            ],
            helpString: `
                <div>
                    Delete an existing preset.
                </div>
                <div>
                    <strong>Example:</strong>
                    <pre><code>/qr-set-delete MyPreset</code></pre>
                </div>
            `,
        }));

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-arg',
            callback: ({ _scope }, [key, value]) => {
                _scope.setMacro(`arg::${key}`, value, key.includes('*'));
                return '';
            },
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({ description: 'argument name',
                    typeList: ARGUMENT_TYPE.STRING,
                    isRequired: true,
                }),
                SlashCommandArgument.fromProps({ description: 'argument value',
                    typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY],
                    isRequired: true,
                }),
            ],
            splitUnnamedArgument: true,
            splitUnnamedArgumentCount: 2,
            helpString: `
                <div>
                    Set a fallback value for a Quick Reply argument.
                </div>
                <div>
                    <strong>Example:</strong>
                    <pre><code>/qr-arg x foo |\n/echo {{arg::x}}</code></pre>
                </div>
            `,
        }));

        SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'import',
            /**
             *
             * @param {{_scope:SlashCommandScope, _abortController:SlashCommandAbortController, _debugController:SlashCommandDebugController, from:string}} args
             * @param {string} value
             */
            callback: (args, value) => {
                if (!args.from) throw new Error('/import requires from= to be set.');
                if (!value) throw new Error('/import requires the unnamed argument to be set.');
                let qr = [...this.api.listGlobalSets(), ...this.api.listChatSets()]
                    .map(it=>this.api.getSetByName(it)?.qrList ?? [])
                    .flat()
                    .find(it=>it.label == args.from)
                ;
                if (!qr) {
                    let [setName, ...qrNameParts] = args.from.split('.');
                    let qrName = qrNameParts.join('.');
                    let qrs = QuickReplySet.get(setName);
                    if (qrs) {
                        qr = qrs.qrList.find(it=>it.label == qrName);
                    }
                }
                if (qr) {
                    const parser = new SlashCommandParser();
                    const closure = parser.parse(qr.message, true, [], args._abortController, args._debugController);
                    if (args._debugController) {
                        closure.source = args.from;
                    }
                    const testCandidates = (executor)=>{
                        return (
                            executor.namedArgumentList.find(arg=>arg.name == 'key')
                            && executor.unnamedArgumentList.length > 0
                            && executor.unnamedArgumentList[0].value instanceof SlashCommandClosure
                        ) || (
                            !executor.namedArgumentList.find(arg=>arg.name == 'key')
                            && executor.unnamedArgumentList.length > 1
                            && executor.unnamedArgumentList[1].value instanceof SlashCommandClosure
                        );
                    };
                    const candidates = closure.executorList
                        .filter(executor=>['let', 'var'].includes(executor.command.name))
                        .filter(testCandidates)
                        .map(executor=>({
                            key: executor.namedArgumentList.find(arg=>arg.name == 'key')?.value ?? executor.unnamedArgumentList[0].value,
                            value: executor.unnamedArgumentList[executor.namedArgumentList.find(arg=>arg.name == 'key') ? 0 : 1].value,
                        }))
                    ;
                    for (let i = 0; i < value.length; i++) {
                        const srcName = value[i];
                        let dstName = srcName;
                        if (i + 2 < value.length && value[i + 1] == 'as') {
                            dstName = value[i + 2];
                            i += 2;
                        }
                        const pick = candidates.find(it=>it.key == srcName);
                        if (!pick) throw new Error(`No scoped closure named "${srcName}" found in "${args.from}"`);
                        if (args._scope.existsVariableInScope(dstName)) {
                            args._scope.setVariable(dstName, pick.value);
                        } else {
                            args._scope.letVariable(dstName, pick.value);
                        }
                    }
                } else {
                    throw new Error(`No Quick Reply found for "${name}".`);
                }
                return '';
            },
            namedArgumentList: [
                SlashCommandNamedArgument.fromProps({ name: 'from',
                    description: 'Quick Reply to import from (QRSet.QRLabel)',
                    typeList: ARGUMENT_TYPE.STRING,
                    isRequired: true,
                }),
            ],
            unnamedArgumentList: [
                SlashCommandArgument.fromProps({ description: 'what to import (x or x as y)',
                    acceptsMultiple: true,
                    typeList: ARGUMENT_TYPE.STRING,
                    isRequired: true,
                }),
            ],
            splitUnnamedArgument: true,
            helpString: `
                <div>
                    Import one or more closures from another Quick Reply.
                </div>
                <div>
                    Only imports closures that are directly assigned a scoped variable via <code>/let</code> or <code>/var</code>.
                </div>
                <div>
                    <strong>Examples:</strong>
                    <ul>
                        <li><pre><code>/import from=LibraryQrSet.FooBar foo |\n/:foo</code></pre></li>
                        <li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo\n\tbar\n|\n/:foo |\n/:bar</code></pre></li>
                        <li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo as x\n\tbar as y\n|\n/:x |\n/:y</code></pre></li>
                    </ul>
                </div>
            `,
        }));
    }




    getSetByName(name) {
        const set = this.api.getSetByName(name);
        if (!set) {
            toastr.error(`No Quick Reply Set with the name "${name}" could be found.`);
        }
        return set;
    }

    getQrByLabel(setName, label) {
        const qr = this.api.getQrByLabel(setName, label);
        if (!qr) {
            toastr.error(`No Quick Reply with the label "${label}" could be found in the set "${setName}"`);
        }
        return qr;
    }




    async executeQuickReplyByIndex(idx) {
        try {
            return await this.api.executeQuickReplyByIndex(idx);
        } catch (ex) {
            toastr.error(ex.message);
        }
    }


    toggleGlobalSet(name, args = {}) {
        try {
            this.api.toggleGlobalSet(name, isTrueBoolean(args.visible ?? 'true'));
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    addGlobalSet(name, args = {}) {
        try {
            this.api.addGlobalSet(name, isTrueBoolean(args.visible ?? 'true'));
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    removeGlobalSet(name) {
        try {
            this.api.removeGlobalSet(name);
        } catch (ex) {
            toastr.error(ex.message);
        }
    }


    toggleChatSet(name, args = {}) {
        try {
            this.api.toggleChatSet(name, isTrueBoolean(args.visible ?? 'true'));
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    addChatSet(name, args = {}) {
        try {
            this.api.addChatSet(name, isTrueBoolean(args.visible ?? 'true'));
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    removeChatSet(name) {
        try {
            this.api.removeChatSet(name);
        } catch (ex) {
            toastr.error(ex.message);
        }
    }


    createQuickReply(args, message) {
        try {
            this.api.createQuickReply(
                args.set ?? '',
                args.label ?? '',
                {
                    icon: args.icon,
                    showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel),
                    message: message ?? '',
                    title: args.title,
                    isHidden: isTrueBoolean(args.hidden),
                    executeOnStartup: isTrueBoolean(args.startup),
                    executeOnUser: isTrueBoolean(args.user),
                    executeOnAi: isTrueBoolean(args.bot),
                    executeOnChatChange: isTrueBoolean(args.load),
                    executeOnNewChat: isTrueBoolean(args.new),
                    executeOnGroupMemberDraft: isTrueBoolean(args.group),
                    executeBeforeGeneration: isTrueBoolean(args.generation),
                    automationId: args.automationId ?? '',
                },
            );
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    getQuickReply(args) {
        if (!args.id && !args.label) {
            toastr.error('Please provide a valid id or label.');
            return '';
        }
        try {
            return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label));
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    updateQuickReply(args, message) {
        try {
            this.api.updateQuickReply(
                args.set ?? '',
                args.id !== undefined ? Number(args.id) : (args.label ?? ''),
                {
                    icon: args.icon,
                    showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel),
                    newLabel: args.newlabel,
                    message: (message ?? '').trim().length > 0 ? message : undefined,
                    title: args.title,
                    isHidden: args.hidden === undefined ? undefined : isTrueBoolean(args.hidden),
                    executeOnStartup: args.startup === undefined ? undefined : isTrueBoolean(args.startup),
                    executeOnUser: args.user === undefined ? undefined : isTrueBoolean(args.user),
                    executeOnAi: args.bot === undefined ? undefined : isTrueBoolean(args.bot),
                    executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load),
                    executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group),
                    executeOnNewChat: args.new === undefined ? undefined : isTrueBoolean(args.new),
                    executeBeforeGeneration: args.generation === undefined ? undefined : isTrueBoolean(args.generation),
                    automationId: args.automationId ?? '',
                },
            );
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    deleteQuickReply(args, label) {
        try {
            this.api.deleteQuickReply(args.set, args.id !== undefined ? Number(args.id) : (args.label ?? label));
        } catch (ex) {
            toastr.error(ex.message);
        }
    }

    createContextItem(args, name) {
        try {
            this.api.createContextItem(
                args.set,
                args.id !== undefined ? Number(args.id) : args.label,
                name,
                isTrueBoolean(args.chain),
            );
        }  catch (ex) {
            toastr.error(ex.message);
        }
    }
    deleteContextItem(args, name) {
        try {
            this.api.deleteContextItem(args.set, args.id !== undefined ? Number(args.id) : args.label, name);
        }  catch (ex) {
            toastr.error(ex.message);
        }
    }
    clearContextMenu(args, label) {
        try {
            this.api.clearContextMenu(args.set, args.id !== undefined ? Number(args.id) : args.label ?? label);
        } catch (ex) {
            toastr.error(ex.message);
        }
    }


    async createSet(name, args) {
        try {
            await this.api.createSet(
                args.name ?? name ?? '',
                {
                    disableSend: isTrueBoolean(args.nosend),
                    placeBeforeInput: isTrueBoolean(args.before),
                    injectInput: isTrueBoolean(args.inject),
                },
            );
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    async updateSet(name, args) {
        try {
            await this.api.updateSet(
                args.name ?? name ?? '',
                {
                    disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined,
                    placeBeforeInput: args.before !== undefined ? isTrueBoolean(args.before) : undefined,
                    injectInput: args.inject !== undefined ? isTrueBoolean(args.inject) : undefined,
                },
            );
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    async deleteSet(name) {
        try {
            await this.api.deleteSet(name ?? '');
        } catch (ex) {
            toastr.error(ex.message);
        }
    }

    listSets(source) {
        try {
            switch (source) {
                case 'global':
                    return this.api.listGlobalSets();
                case 'chat':
                    return this.api.listChatSets();
                default:
                    return this.api.listSets();
            }
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
    listQuickReplies(name) {
        try {
            return this.api.listQuickReplies(name);
        } catch (ex) {
            toastr.error(ex.message);
        }
    }
}
