/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import Parse from "./parse";
// import SqrlErr from './err'
/* END TYPES */
export default function compileToString(str, env) {
    const buffer = Parse(str, env);
    let res = "var tR='';" +
        (env.useWith ? 'with(' + env.varName + '||{}){' : '') +
        compileScope(buffer, env) +
        'if(cb){cb(null,tR)} return tR' +
        (env.useWith ? '}' : '');
    if (env.plugins) {
        for (let i = 0; i < env.plugins.length; i++) {
            const plugin = env.plugins[i];
            if (plugin.processFnString) {
                res = plugin.processFnString(res, env);
            }
        }
    }
    return res;
    // TODO: is `return cb()` necessary, or could we just do `cb()`
}
function filter(str, filters) {
    for (let i = 0; i < filters.length; i++) {
        const name = filters[i][0];
        const params = filters[i][1];
        const isFilterAsync = filters[i][2];
        // if (isFilterAsync && !env.async) {
        //   throw SqrlErr("Async filter '" + name + "' in non-async env")
        // }
        // Let the JS compiler do this, compile() will catch it
        str = (isFilterAsync ? 'await ' : '') + "c.l('F','" + name + "')(" + str;
        if (params) {
            str += ',' + params;
        }
        str += ')';
    }
    return str;
}
// TODO: Use type intersections for TemplateObject, etc.
// so I don't have to make properties mandatory
function compileHelper(env, res, descendants, params, isAsync, name) {
    let ret = '{exec:' +
        (isAsync ? 'async ' : '') +
        compileScopeIntoFunction(descendants, res, env) +
        ',params:[' +
        params +
        ']';
    if (name) {
        ret += ",name:'" + name + "'";
    }
    if (isAsync) {
        ret += ',async:true';
    }
    ret += '}';
    return ret;
}
function compileBlocks(blocks, env) {
    let ret = '[';
    for (let i = 0; i < blocks.length; i++) {
        const block = blocks[i];
        ret += compileHelper(env, block.res || '', block.d, block.p || '', block.a, block.n);
        if (i < blocks.length) {
            ret += ',';
        }
    }
    ret += ']';
    return ret;
}
export function compileScopeIntoFunction(buff, res, env) {
    return ('function(' + res + "){var tR='';" + compileScope(buff, env) + 'return tR}');
}
export function compileScope(buff, env) {
    let i = 0;
    const buffLength = buff.length;
    let returnStr = '';
    for (i; i < buffLength; i++) {
        const currentBlock = buff[i];
        if (typeof currentBlock === 'string') {
            const str = currentBlock;
            // we know string exists
            returnStr += "tR+='" + str + "';";
        }
        else {
            const type = currentBlock.t; // h, s, e, i
            let content = currentBlock.c || '';
            const filters = currentBlock.f;
            const name = currentBlock.n || '';
            const params = currentBlock.p || '';
            const res = currentBlock.res || '';
            const blocks = currentBlock.b;
            const isAsync = !!currentBlock.a; // !! is to booleanize it
            // if (isAsync && !env.async) {
            //   throw SqrlErr("Async block or helper '" + name + "' in non-async env")
            // }
            // Let compiler do this
            if (type === 'i') {
                if (env.defaultFilter) {
                    content = "c.l('F','" + env.defaultFilter + "')(" + content + ')';
                }
                let filtered = filter(content, filters);
                if (!currentBlock.raw && env.autoEscape) {
                    filtered = "c.l('F','e')(" + filtered + ')';
                }
                returnStr += 'tR+=' + filtered + ';';
                // reference
            }
            else if (type === 'h') {
                // helper
                if (env.storage.nativeHelpers.get(name)) {
                    returnStr += env.storage.nativeHelpers.get(name)(currentBlock, env);
                }
                else {
                    let helperReturn = (isAsync ? 'await ' : '') +
                        "c.l('H','" +
                        name +
                        "')(" +
                        compileHelper(env, res, currentBlock.d, params, isAsync);
                    if (blocks) {
                        helperReturn += ',' + compileBlocks(blocks, env);
                    }
                    else {
                        helperReturn += ',[]';
                    }
                    helperReturn += ',c)';
                    returnStr += 'tR+=' + filter(helperReturn, filters) + ';';
                }
            }
            else if (type === 's') {
                // self-closing helper
                returnStr +=
                    'tR+=' +
                        filter((isAsync ? 'await ' : '') +
                            "c.l('H','" +
                            name +
                            "')({params:[" +
                            params +
                            ']},[],c)', filters) +
                        ';';
            }
            else if (type === 'e') {
                // execute
                returnStr += content + '\n';
            }
        }
    }
    return returnStr;
}
