import { SlashCommandAbortController } from './SlashCommandAbortController.js';
import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
import { SlashCommandClosure } from './SlashCommandClosure.js';
import { PARSER_FLAG } from './SlashCommandParser.js';
import { SlashCommandScope } from './SlashCommandScope.js';




/**
 * @typedef {{
 * _pipe:string|SlashCommandClosure,
 * _scope:SlashCommandScope,
 * _parserFlags:{[id:PARSER_FLAG]:boolean},
 * _abortController:SlashCommandAbortController,
 * [id:string]:string|SlashCommandClosure,
 * }} NamedArguments
 */

/**
 * @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, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|void|Promise<string|SlashCommandClosure|void>} [props.callback]
     * @param {string} [props.helpString]
     * @param {boolean} [props.splitUnnamedArgument]
     * @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:{_pipe:string|SlashCommandClosure, _scope:SlashCommandScope, _abortController:SlashCommandAbortController, [id:string]:string|SlashCommandClosure}, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback;
    /**@type {string}*/ helpString;
    /**@type {boolean}*/ splitUnnamedArgument = false;
    /**@type {string[]}*/ aliases = [];
    /**@type {string}*/ returns;
    /**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
    /**@type {SlashCommandArgument[]}*/ unnamedArgumentList = [];

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

    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 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 name = document.createElement('div'); {
                    name.classList.add('name');
                    name.classList.add('monospace');
                    name.title = 'command name';
                    name.textContent = `/${key}`;
                    specs.append(name);
                }
                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 ? '' : '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 = `${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 = `${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 = `${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 = '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 argItem = document.createElement('div'); {
                                    argItem.classList.add('argument');
                                    argItem.classList.add('unnamedArgument');
                                    argItem.title = `${arg.isRequired ? '' : '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 = `${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 = `${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);
                                        }
                                    }
                                    listItem.append(argItem);
                                }
                                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) ? 'command does not return anything' : '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(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;
    }
}
