import { hljs } from '../../lib.js';
import { t } from '../i18n.js';
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
import { SlashCommandClosure } from './SlashCommandClosure.js';
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
import { SlashCommandScope } from './SlashCommandScope.js';

/**
 * @typedef {NamedArgumentsCapture & {
 * _scope:SlashCommandScope,
 * _parserFlags:import('./SlashCommandParser.js').ParserFlags,
 * _abortController:SlashCommandAbortController,
 * _debugController:SlashCommandDebugController,
 * _hasUnnamedArgument:boolean,
 * }} NamedArguments
 */

/**
 * Alternative object for local JSDocs, where you don't need existing pipe, scope, etc. arguments
 * @typedef {{[id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined}} NamedArgumentsCapture
 */

/**
 * @typedef {string|SlashCommandClosure|(string|SlashCommandClosure)[]} UnnamedArguments
*/



export class SlashCommand {
    /**
     * Creates a SlashCommand from a properties object.
     * @param {Object} props
     * @param {string} [props.name]
     * @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback]
     * @param {string} [props.helpString]
     * @param {boolean} [props.splitUnnamedArgument]
     * @param {Number} [props.splitUnnamedArgumentCount]
     * @param {boolean} [props.rawQuotes] If set to true, does not remove wrapping quotes from the unnamed argument.
     * @param {string[]} [props.aliases]
     * @param {string} [props.returns]
     * @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
     * @param {SlashCommandArgument[]} [props.unnamedArgumentList]
     */
    static fromProps(props) {
        const instance = Object.assign(new this(), props);
        return instance;
    }




    /**@type {string}*/ name;
    /**@type {(namedArguments:NamedArguments, unnamedArguments:UnnamedArguments)=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback;
    /**@type {string}*/ helpString;
    /**@type {boolean}*/ splitUnnamedArgument = false;
    /**@type {Number}*/ splitUnnamedArgumentCount;
    /** @type {boolean} */ rawQuotes = false;
    /**@type {string[]}*/ aliases = [];
    /**@type {string}*/ returns;
    /**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
    /**@type {SlashCommandArgument[]}*/ unnamedArgumentList = [];

    /**@type {Object.<string, HTMLElement>}*/ helpCache = {};
    /**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {};

    /**@type {boolean}*/ isExtension = false;
    /**@type {boolean}*/ isThirdParty = false;
    /**@type {string}*/ source;

    renderHelpItem(key = null) {
        key = key ?? this.name;
        if (!this.helpCache[key]) {
            const typeIcon = '[/]';
            const li = document.createElement('li'); {
                li.classList.add('item');
                const type = document.createElement('span'); {
                    type.classList.add('type');
                    type.classList.add('monospace');
                    type.textContent = typeIcon;
                    li.append(type);
                }
                const specs = document.createElement('span'); {
                    specs.classList.add('specs');
                    const name = document.createElement('span'); {
                        name.classList.add('name');
                        name.classList.add('monospace');
                        name.textContent = '/';
                        key.split('').forEach(char=>{
                            const span = document.createElement('span'); {
                                span.textContent = char;
                                name.append(span);
                            }
                        });
                        specs.append(name);
                    }
                    const body = document.createElement('span'); {
                        body.classList.add('body');
                        const args = document.createElement('span'); {
                            args.classList.add('arguments');
                            for (const arg of this.namedArgumentList) {
                                const argItem = document.createElement('span'); {
                                    argItem.classList.add('argument');
                                    argItem.classList.add('namedArgument');
                                    if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
                                    if (arg.acceptsMultiple) argItem.classList.add('multiple');
                                    const name = document.createElement('span'); {
                                        name.classList.add('argument-name');
                                        name.textContent = arg.name;
                                        argItem.append(name);
                                    }
                                    if (arg.enumList.length > 0) {
                                        const enums = document.createElement('span'); {
                                            enums.classList.add('argument-enums');
                                            for (const e of arg.enumList) {
                                                const enumItem = document.createElement('span'); {
                                                    enumItem.classList.add('argument-enum');
                                                    enumItem.textContent = e.value;
                                                    enums.append(enumItem);
                                                }
                                            }
                                            argItem.append(enums);
                                        }
                                    } else {
                                        const types = document.createElement('span'); {
                                            types.classList.add('argument-types');
                                            for (const t of arg.typeList) {
                                                const type = document.createElement('span'); {
                                                    type.classList.add('argument-type');
                                                    type.textContent = t;
                                                    types.append(type);
                                                }
                                            }
                                            argItem.append(types);
                                        }
                                    }
                                    args.append(argItem);
                                }
                            }
                            for (const arg of this.unnamedArgumentList) {
                                const argItem = document.createElement('span'); {
                                    argItem.classList.add('argument');
                                    argItem.classList.add('unnamedArgument');
                                    if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
                                    if (arg.acceptsMultiple) argItem.classList.add('multiple');
                                    if (arg.enumList.length > 0) {
                                        const enums = document.createElement('span'); {
                                            enums.classList.add('argument-enums');
                                            for (const e of arg.enumList) {
                                                const enumItem = document.createElement('span'); {
                                                    enumItem.classList.add('argument-enum');
                                                    enumItem.textContent = e.value;
                                                    enums.append(enumItem);
                                                }
                                            }
                                            argItem.append(enums);
                                        }
                                    } else {
                                        const types = document.createElement('span'); {
                                            types.classList.add('argument-types');
                                            for (const t of arg.typeList) {
                                                const type = document.createElement('span'); {
                                                    type.classList.add('argument-type');
                                                    type.textContent = t;
                                                    types.append(type);
                                                }
                                            }
                                            argItem.append(types);
                                        }
                                    }
                                    args.append(argItem);
                                }
                            }
                            body.append(args);
                        }
                        const returns = document.createElement('span'); {
                            returns.classList.add('returns');
                            returns.textContent = this.returns ?? 'void';
                            body.append(returns);
                        }
                        specs.append(body);
                    }
                    li.append(specs);
                }
                const stopgap = document.createElement('span'); {
                    stopgap.classList.add('stopgap');
                    stopgap.textContent = '';
                    li.append(stopgap);
                }
                const help = document.createElement('span'); {
                    help.classList.add('help');
                    const content = document.createElement('span'); {
                        content.classList.add('helpContent');
                        content.innerHTML = this.helpString;
                        const text = content.textContent;
                        content.innerHTML = '';
                        content.textContent = text;
                        help.append(content);
                    }
                    li.append(help);
                }
                if (this.aliases.length > 0) {
                    const aliases = document.createElement('span'); {
                        aliases.classList.add('aliases');
                        aliases.append(' (alias: ');
                        for (const aliasName of this.aliases) {
                            const alias = document.createElement('span'); {
                                alias.classList.add('monospace');
                                alias.textContent = `/${aliasName}`;
                                aliases.append(alias);
                            }
                        }
                        aliases.append(')');
                        // li.append(aliases);
                    }
                }
            }
            this.helpCache[key] = li;
        }
        return /**@type {HTMLElement}*/(this.helpCache[key].cloneNode(true));
    }

    renderHelpDetails(key = null) {
        key = key ?? this.name;
        if (!this.helpDetailsCache[key]) {
            const frag = document.createDocumentFragment();
            const cmd = this;
            const namedArguments = cmd.namedArgumentList ?? [];
            const unnamedArguments = cmd.unnamedArgumentList ?? [];
            const returnType = cmd.returns ?? 'void';
            const helpString = cmd.helpString ?? 'NO DETAILS';
            const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key);
            const specs = document.createElement('div'); {
                specs.classList.add('specs');
                const head = document.createElement('div'); {
                    head.classList.add('head');
                    const name = document.createElement('div'); {
                        name.classList.add('name');
                        name.classList.add('monospace');
                        name.title = t`Command name`;
                        name.textContent = `/${key}`;
                        head.append(name);
                    }
                    const src = document.createElement('div'); {
                        src.classList.add('source');
                        src.classList.add('fa-solid');
                        if (this.isExtension) {
                            src.classList.add('isExtension');
                            src.classList.add('fa-cubes');
                            if (this.isThirdParty) src.classList.add('isThirdParty');
                            else src.classList.add('isCore');
                        } else {
                            src.classList.add('isCore');
                            src.classList.add('fa-star-of-life');
                        }
                        src.title = [
                            this.isExtension ? 'Extension' : 'Core',
                            this.isThirdParty ? 'Third Party' : (this.isExtension ? 'Core' : null),
                            this.source,
                        ].filter(it=>it).join('\n');
                        head.append(src);
                    }
                    if (this.rawQuotes) {
                        const rawQuotes = document.createElement('div'); {
                            rawQuotes.classList.add('rawQuotes');
                            rawQuotes.classList.add('fa-solid');
                            rawQuotes.classList.add('fa-quote-left');
                            rawQuotes.title = t`Does not alter quoted literal unnamed arguments. Pass raw=false argument to override.`;
                            head.append(rawQuotes);
                        }
                    }
                    specs.append(head);
                }
                const body = document.createElement('div'); {
                    body.classList.add('body');
                    const args = document.createElement('ul'); {
                        args.classList.add('arguments');
                        for (const arg of namedArguments) {
                            const listItem = document.createElement('li'); {
                                listItem.classList.add('argumentItem');
                                const argSpec = document.createElement('div'); {
                                    argSpec.classList.add('argumentSpec');
                                    const argItem = document.createElement('div'); {
                                        argItem.classList.add('argument');
                                        argItem.classList.add('namedArgument');
                                        argItem.title = arg.isRequired ? t`Named argument` : t`Optional named argument`;
                                        if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
                                        if (arg.acceptsMultiple) argItem.classList.add('multiple');
                                        const name = document.createElement('span'); {
                                            name.classList.add('argument-name');
                                            name.title = t`${argItem.title} - Name`;
                                            name.textContent = arg.name;
                                            argItem.append(name);
                                        }
                                        if (arg.enumList.length > 0) {
                                            const enums = document.createElement('span'); {
                                                enums.classList.add('argument-enums');
                                                enums.title = t`${argItem.title} - Accepted values`;
                                                for (const e of arg.enumList) {
                                                    const enumItem = document.createElement('span'); {
                                                        enumItem.classList.add('argument-enum');
                                                        enumItem.textContent = e.value;
                                                        enums.append(enumItem);
                                                    }
                                                }
                                                argItem.append(enums);
                                            }
                                        } else {
                                            const types = document.createElement('span'); {
                                                types.classList.add('argument-types');
                                                types.title = t`${argItem.title} - Accepted types`;
                                                for (const t of arg.typeList) {
                                                    const type = document.createElement('span'); {
                                                        type.classList.add('argument-type');
                                                        type.textContent = t;
                                                        types.append(type);
                                                    }
                                                }
                                                argItem.append(types);
                                            }
                                        }
                                        argSpec.append(argItem);
                                    }
                                    if (arg.defaultValue !== null) {
                                        const argDefault = document.createElement('div'); {
                                            argDefault.classList.add('argument-default');
                                            argDefault.title = t`Default value`;
                                            argDefault.textContent = arg.defaultValue.toString();
                                            argSpec.append(argDefault);
                                        }
                                    }
                                    listItem.append(argSpec);
                                }
                                const desc = document.createElement('div'); {
                                    desc.classList.add('argument-description');
                                    desc.innerHTML = arg.description;
                                    listItem.append(desc);
                                }
                                args.append(listItem);
                            }
                        }
                        for (const arg of unnamedArguments) {
                            const listItem = document.createElement('li'); {
                                listItem.classList.add('argumentItem');
                                const argSpec = document.createElement('div'); {
                                    argSpec.classList.add('argumentSpec');
                                    const argItem = document.createElement('div'); {
                                        argItem.classList.add('argument');
                                        argItem.classList.add('unnamedArgument');
                                        argItem.title = arg.isRequired ? t`Unnamed argument` : t`Optional unnamed argument`;
                                        if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
                                        if (arg.acceptsMultiple) argItem.classList.add('multiple');
                                        if (arg.enumList.length > 0) {
                                            const enums = document.createElement('span'); {
                                                enums.classList.add('argument-enums');
                                                enums.title = t`${argItem.title} - Accepted values`;
                                                for (const e of arg.enumList) {
                                                    const enumItem = document.createElement('span'); {
                                                        enumItem.classList.add('argument-enum');
                                                        enumItem.textContent = e.value;
                                                        enums.append(enumItem);
                                                    }
                                                }
                                                argItem.append(enums);
                                            }
                                        } else {
                                            const types = document.createElement('span'); {
                                                types.classList.add('argument-types');
                                                types.title = t`${argItem.title} - Accepted types`;
                                                for (const t of arg.typeList) {
                                                    const type = document.createElement('span'); {
                                                        type.classList.add('argument-type');
                                                        type.textContent = t;
                                                        types.append(type);
                                                    }
                                                }
                                                argItem.append(types);
                                            }
                                        }
                                        argSpec.append(argItem);
                                    }
                                    if (arg.defaultValue !== null) {
                                        const argDefault = document.createElement('div'); {
                                            argDefault.classList.add('argument-default');
                                            argDefault.title = t`Default value`;
                                            argDefault.textContent = arg.defaultValue.toString();
                                            argSpec.append(argDefault);
                                        }
                                    }
                                    listItem.append(argSpec);
                                }
                                const desc = document.createElement('div'); {
                                    desc.classList.add('argument-description');
                                    desc.innerHTML = arg.description;
                                    listItem.append(desc);
                                }
                                args.append(listItem);
                            }
                        }
                        body.append(args);
                    }
                    const returns = document.createElement('span'); {
                        returns.classList.add('returns');
                        returns.title = [null, undefined, 'void'].includes(returnType) ? t`Command does not return anything` : t`Return value`;
                        returns.textContent = returnType ?? 'void';
                        body.append(returns);
                    }
                    specs.append(body);
                }
                frag.append(specs);
            }
            const help = document.createElement('span'); {
                help.classList.add('help');
                help.innerHTML = helpString;
                for (const code of help.querySelectorAll('pre > code')) {
                    code.classList.add('language-stscript');
                    hljs.highlightElement(/**@type {HTMLElement}*/(code));
                }
                frag.append(help);
            }
            if (aliasList.length > 0) {
                const aliases = document.createElement('span'); {
                    aliases.classList.add('aliases');
                    for (const aliasName of aliasList) {
                        const alias = document.createElement('span'); {
                            alias.classList.add('alias');
                            alias.textContent = `/${aliasName}`;
                            aliases.append(alias);
                        }
                    }
                    frag.append(aliases);
                }
            }
            this.helpDetailsCache[key] = frag;
        }
        const frag = document.createDocumentFragment();
        frag.append(this.helpDetailsCache[key].cloneNode(true));
        return frag;
    }
}
