import { getWithLoc } from "../parser/converts/common.js";
import { getScopeFromNode, removeAllScopeAndVariableAndReference, removeIdentifierVariable, removeReference, removeScope, } from "../scope/index.js";
import { getKeys, traverseNodes, getNodes } from "../traverse.js";
import { UniqueIdGenerator } from "./unique.js";
import { fixLocations } from "./fix-locations.js";
/**
 * Get node range
 */
function getNodeRange(node, code) {
    const loc = "range" in node
        ? { start: node.range[0], end: node.range[1] }
        : getWithLoc(node);
    let start = loc.start;
    let end = loc.end;
    let openingParenCount = 0;
    let closingParenCount = 0;
    if (node.leadingComments) {
        const commentStart = getWithLoc(node.leadingComments[0]).start;
        if (commentStart < start) {
            start = commentStart;
            // Extract the number of parentheses before the node.
            let leadingEnd = loc.start;
            for (let index = node.leadingComments.length - 1; index >= 0; index--) {
                const comment = node.leadingComments[index];
                const loc = getWithLoc(comment);
                for (const c of code.slice(loc.end, leadingEnd).trim()) {
                    if (c === "(")
                        openingParenCount++;
                }
                leadingEnd = loc.start;
            }
        }
    }
    if (node.trailingComments) {
        const commentEnd = getWithLoc(node.trailingComments[node.trailingComments.length - 1]).end;
        if (end < commentEnd) {
            end = commentEnd;
            // Extract the number of parentheses after the node.
            let trailingStart = loc.end;
            for (const comment of node.trailingComments) {
                const loc = getWithLoc(comment);
                for (const c of code.slice(trailingStart, loc.start).trim()) {
                    if (c === ")")
                        closingParenCount++;
                }
                trailingStart = loc.end;
            }
        }
    }
    // Adjust the range so that the parentheses match up.
    if (openingParenCount < closingParenCount) {
        for (; openingParenCount < closingParenCount && start >= 0; start--) {
            const c = code[start].trim();
            if (c)
                continue;
            if (c !== "(")
                break;
            openingParenCount++;
        }
    }
    else if (openingParenCount > closingParenCount) {
        for (; openingParenCount > closingParenCount && end < code.length; end++) {
            const c = code[end].trim();
            if (c)
                continue;
            if (c !== ")")
                break;
            closingParenCount++;
        }
    }
    return [start, end];
}
/**
 * A class that handles script fragments.
 * The script fragment AST node remaps and connects to the original directive AST node.
 */
export class ScriptLetContext {
    constructor(ctx) {
        this.restoreCallbacks = [];
        this.programRestoreCallbacks = [];
        this.closeScopeCallbacks = [];
        this.unique = new UniqueIdGenerator();
        this.currentScriptScopeKind = "render";
        this.script = ctx.sourceCode.scripts;
        this.ctx = ctx;
    }
    addExpression(expression, parent, typing, ...callbacks) {
        const range = getNodeRange(expression, this.ctx.code);
        return this.addExpressionFromRange(range, parent, typing, ...callbacks);
    }
    addExpressionFromRange(range, parent, typing, ...callbacks) {
        const part = this.ctx.code.slice(...range);
        const isTS = typing && this.ctx.isTypeScript();
        this.appendScript(`(${part})${isTS ? `as (${typing})` : ""};`, range[0] - 1, this.currentScriptScopeKind, "ExpressionStatement", (st, tokens, comments, result) => {
            const exprSt = st;
            const tsAs = isTS
                ? exprSt.expression
                : null;
            const node = tsAs?.expression || exprSt.expression;
            // Process for nodes
            for (const callback of callbacks) {
                callback(node, result);
            }
            tokens.shift(); // (
            tokens.pop(); // )
            tokens.pop(); // ;
            if (isTS) {
                for (const scope of extractTypeNodeScopes(tsAs.typeAnnotation, result)) {
                    removeScope(result.scopeManager, scope);
                }
                this.remapNodes([
                    {
                        offset: range[0] - 1,
                        range,
                        newNode: node,
                    },
                ], tokens, comments, result.visitorKeys);
            }
            node.parent = parent;
            // Disconnect the tree structure.
            exprSt.expression = null;
        });
        return callbacks;
    }
    addObjectShorthandProperty(identifier, parent, ...callbacks) {
        const range = getNodeRange(identifier, this.ctx.code);
        const part = this.ctx.code.slice(...range);
        this.appendScript(`({${part}});`, range[0] - 2, this.currentScriptScopeKind, "ExpressionStatement", (st, tokens, _comments, result) => {
            const exprSt = st;
            const objectExpression = exprSt.expression;
            const node = objectExpression.properties[0];
            // Process for nodes
            for (const callback of callbacks) {
                callback(node, result);
            }
            node.key.parent = parent;
            node.value.parent = parent;
            tokens.shift(); // (
            tokens.shift(); // {
            tokens.pop(); // }
            tokens.pop(); // )
            tokens.pop(); // ;
            // Disconnect the tree structure.
            exprSt.expression = null;
        });
    }
    addVariableDeclarator(declarator, parent, ...callbacks) {
        const range = declarator.type === "VariableDeclarator"
            ? // As of Svelte v5-next.65, VariableDeclarator nodes do not have location information.
                [
                    getNodeRange(declarator.id, this.ctx.code)[0],
                    getNodeRange(declarator.init, this.ctx.code)[1],
                ]
            : getNodeRange(declarator, this.ctx.code);
        const part = this.ctx.code.slice(...range);
        this.appendScript(`const ${part};`, range[0] - 6, this.currentScriptScopeKind, "VariableDeclaration", (st, tokens, _comments, result) => {
            const decl = st;
            const node = decl.declarations[0];
            // Process for nodes
            for (const callback of callbacks) {
                callback(node, result);
            }
            const scope = result.getScope(decl);
            for (const variable of scope.variables) {
                for (const def of variable.defs) {
                    if (def.parent === decl) {
                        def.parent = parent;
                    }
                }
            }
            node.parent = parent;
            tokens.shift(); // const
            tokens.pop(); // ;
            // Disconnect the tree structure.
            decl.declarations = [];
        });
        return callbacks;
    }
    addGenericTypeAliasDeclaration(param, callbackId, callbackDefault) {
        const ranges = getTypeParameterRanges(this.ctx.code, param);
        let scriptLet = `type ${this.ctx.code.slice(...ranges.idRange)}`;
        if (ranges.constraintRange) {
            scriptLet += ` =     ${this.ctx.code.slice(...ranges.constraintRange)};`;
            //           |extends|
        }
        else {
            scriptLet += " = unknown;";
        }
        this.appendScript(scriptLet, ranges.idRange[0] - 5, "generics", "TSTypeAliasDeclaration", (st, tokens, _comments, result) => {
            const decl = st;
            const id = decl.id;
            const typeAnnotation = decl.typeAnnotation;
            // Process for nodes
            callbackId(id, typeAnnotation);
            const scope = result.getScope(decl);
            for (const variable of scope.variables) {
                for (const def of variable.defs) {
                    if (def.node === decl) {
                        def.node = param;
                    }
                }
            }
            id.parent = param;
            typeAnnotation.parent = param;
            tokens.shift(); // type
            if (ranges.constraintRange) {
                const eqToken = tokens[1];
                eqToken.type = "Keyword";
                eqToken.value = "extends";
                eqToken.range[0] = eqToken.range[0] - 1;
                eqToken.range[1] = eqToken.range[0] + 7;
                tokens.pop(); // ;
            }
            else {
                tokens.pop(); // ;
                tokens.pop(); // unknown
                tokens.pop(); // =
            }
            // Disconnect the tree structure.
            delete decl.id;
            delete decl.typeAnnotation;
            delete decl.typeParameters;
        });
        if (ranges.defaultRange) {
            const eqDefaultType = this.ctx.code.slice(ranges.constraintRange?.[1] ?? ranges.idRange[1], ranges.defaultRange[1]);
            const id = this.generateUniqueId(eqDefaultType);
            const scriptLet = `type ${id}${eqDefaultType};`;
            this.appendScript(scriptLet, ranges.defaultRange[0] - 5 - id.length - 1, "generics", "TSTypeAliasDeclaration", (st, tokens, _comments, result) => {
                const decl = st;
                const typeAnnotation = decl.typeAnnotation;
                // Process for nodes
                callbackDefault(typeAnnotation);
                const scope = result.getScope(decl);
                removeIdentifierVariable(decl.id, scope);
                typeAnnotation.parent = param;
                tokens.shift(); // type
                tokens.shift(); // ${id}
                tokens.pop(); // ;
                // Disconnect the tree structure.
                delete decl.id;
                delete decl.typeAnnotation;
                delete decl.typeParameters;
            });
        }
    }
    nestIfBlock(expression, ifBlock, callback) {
        const range = getNodeRange(expression, this.ctx.code);
        const part = this.ctx.code.slice(...range);
        const restore = this.appendScript(`if(${part}){`, range[0] - 3, this.currentScriptScopeKind, "IfStatement", (st, tokens, _comments, result) => {
            const ifSt = st;
            const node = ifSt.test;
            const scope = result.getScope(ifSt.consequent);
            // Process for nodes
            callback(node, result);
            node.parent = ifBlock;
            // Process for scope
            result.registerNodeToScope(ifBlock, scope);
            tokens.shift(); // if
            tokens.shift(); // (
            tokens.pop(); // )
            tokens.pop(); // {
            tokens.pop(); // }
            // Disconnect the tree structure.
            ifSt.test = null;
            ifSt.consequent = null;
        });
        this.pushScope(restore, "}", this.currentScriptScopeKind);
    }
    nestEachBlock(expression, context, indexRange, eachBlock, callback) {
        const exprRange = getNodeRange(expression, this.ctx.code);
        const ctxRange = context && getNodeRange(context, this.ctx.code);
        let source = "Array.from(";
        const exprOffset = source.length;
        source += `${this.ctx.code.slice(...exprRange)}).forEach((`;
        const ctxOffset = source.length;
        if (ctxRange) {
            source += this.ctx.code.slice(...ctxRange);
        }
        else {
            source += "__$ctx__";
        }
        let idxOffset = null;
        if (indexRange) {
            source += ",";
            idxOffset = source.length;
            source += this.ctx.code.slice(indexRange.start, indexRange.end);
        }
        source += ")=>{";
        const restore = this.appendScript(source, exprRange[0] - exprOffset, this.currentScriptScopeKind, "ExpressionStatement", (st, tokens, comments, result) => {
            const expSt = st;
            const call = expSt.expression;
            const fn = call.arguments[0];
            const callArrayFrom = call.callee
                .object;
            const expr = callArrayFrom.arguments[0];
            const ctx = fn.params[0];
            const idx = (fn.params[1] ?? null);
            const scope = result.getScope(fn.body);
            // Process for nodes
            callback(expr, context ? ctx : null, idx);
            // Process for scope
            result.registerNodeToScope(eachBlock, scope);
            for (const v of scope.variables) {
                for (const def of v.defs) {
                    if (def.node === fn) {
                        def.node = eachBlock;
                    }
                }
            }
            if (!context) {
                // remove `__$ctx__` variable
                removeIdentifierVariable(ctx, scope);
            }
            // remove Array reference
            const arrayId = callArrayFrom.callee
                .object;
            const ref = scope.upper.references.find((r) => r.identifier === arrayId);
            if (ref) {
                removeReference(ref, scope.upper);
            }
            expr.parent = eachBlock;
            ctx.parent = eachBlock;
            if (idx) {
                idx.parent = eachBlock;
            }
            tokens.shift(); // Array
            tokens.shift(); // .
            tokens.shift(); // from
            tokens.shift(); // (
            tokens.pop(); // )
            tokens.pop(); // =>
            tokens.pop(); // {
            tokens.pop(); // }
            tokens.pop(); // )
            tokens.pop(); // ;
            const map = [
                {
                    offset: exprOffset,
                    range: exprRange,
                    newNode: expr,
                },
            ];
            if (ctxRange) {
                map.push({
                    offset: ctxOffset,
                    range: ctxRange,
                    newNode: ctx,
                });
            }
            if (indexRange) {
                map.push({
                    offset: idxOffset,
                    range: [indexRange.start, indexRange.end],
                    newNode: idx,
                });
            }
            this.remapNodes(map, tokens, comments, result.visitorKeys);
            // Disconnect the tree structure.
            expSt.expression = null;
        });
        this.pushScope(restore, "});", this.currentScriptScopeKind);
    }
    nestSnippetBlock(id, closeParentIndex, snippetBlock, 
    // If set to null, `currentScriptScopeKind` will be used.
    kind, callback) {
        const scopeKind = kind || this.currentScriptScopeKind;
        const idRange = getNodeRange(id, this.ctx.code);
        const part = this.ctx.code.slice(idRange[0], closeParentIndex + 1);
        const restore = this.appendScript(`function ${part}{`, idRange[0] - 9, scopeKind, "FunctionDeclaration", (st, tokens, _comments, result) => {
            const fnDecl = st;
            const idNode = fnDecl.id;
            const params = [...fnDecl.params];
            const scope = result.getScope(fnDecl);
            // Process for nodes
            callback(idNode, params);
            idNode.parent = snippetBlock;
            for (const param of params) {
                param.parent = snippetBlock;
            }
            // Process for scope
            result.registerNodeToScope(snippetBlock, scope);
            tokens.shift(); // function
            tokens.pop(); // {
            tokens.pop(); // }
            // Disconnect the tree structure.
            fnDecl.id = null;
            fnDecl.params = [];
        });
        this.pushScope(restore, "}", scopeKind);
    }
    nestBlock(block, params) {
        let resolvedParams;
        if (typeof params === "function") {
            if (this.ctx.isTypeScript()) {
                const generatedTypes = params({
                    generateUniqueId: (base) => this.generateUniqueId(base),
                });
                resolvedParams = [generatedTypes.param];
                if (generatedTypes.preparationScript) {
                    for (const preparationScript of generatedTypes.preparationScript) {
                        this.appendScriptWithoutOffset(preparationScript.script, this.currentScriptScopeKind, preparationScript.nodeType, (node, tokens, comments, result) => {
                            tokens.length = 0;
                            comments.length = 0;
                            removeAllScopeAndVariableAndReference(node, result);
                        });
                    }
                }
            }
            else {
                const generatedTypes = params(null);
                resolvedParams = [generatedTypes.param];
            }
        }
        else {
            resolvedParams = params;
        }
        if (!resolvedParams || resolvedParams.length === 0) {
            const restore = this.appendScript(`{`, block.range[0], this.currentScriptScopeKind, "BlockStatement", (st, tokens, _comments, result) => {
                const blockSt = st;
                // Process for scope
                const scope = result.getScope(blockSt);
                result.registerNodeToScope(block, scope);
                tokens.length = 0; // clear tokens
                // Disconnect the tree structure.
                blockSt.body = null;
            });
            this.pushScope(restore, "}", this.currentScriptScopeKind);
        }
        else {
            const sortedParams = [...resolvedParams]
                .map((d) => {
                return {
                    ...d,
                    range: getNodeRange(d.node, this.ctx.code),
                };
            })
                .sort((a, b) => {
                const [pA] = a.range;
                const [pB] = b.range;
                return pA - pB;
            });
            const maps = [];
            let source = "";
            for (let index = 0; index < sortedParams.length; index++) {
                const param = sortedParams[index];
                const range = param.range;
                if (source) {
                    source += ",";
                }
                const offset = source.length + 1; /* ( */
                source += this.ctx.code.slice(...range);
                maps.push({
                    index: maps.length,
                    offset,
                    range,
                });
                if (this.ctx.isTypeScript()) {
                    source += `: (${param.typing})`;
                }
            }
            const restore = this.appendScript(`(${source})=>{`, maps[0].range[0] - 1, this.currentScriptScopeKind, "ExpressionStatement", (st, tokens, comments, result) => {
                const exprSt = st;
                const fn = exprSt.expression;
                const scope = result.getScope(fn.body);
                // Process for nodes
                for (let index = 0; index < fn.params.length; index++) {
                    const p = fn.params[index];
                    sortedParams[index].callback(p, result);
                    if (this.ctx.isTypeScript()) {
                        const typeAnnotation = p.typeAnnotation;
                        delete p.typeAnnotation;
                        p.range[1] = typeAnnotation.range[0];
                        p.loc.end = {
                            line: typeAnnotation.loc.start.line,
                            column: typeAnnotation.loc.start.column,
                        };
                        removeAllScopeAndVariableAndReference(typeAnnotation, result);
                    }
                    p.parent = sortedParams[index].parent;
                }
                // Process for scope
                result.registerNodeToScope(block, scope);
                for (const v of scope.variables) {
                    for (const def of v.defs) {
                        if (def.node === fn) {
                            def.node = block;
                        }
                    }
                }
                tokens.shift(); // (
                tokens.pop(); // )
                tokens.pop(); // =>
                tokens.pop(); // {
                tokens.pop(); // }
                tokens.pop(); // ;
                this.remapNodes(maps.map((m) => {
                    return {
                        offset: m.offset,
                        range: m.range,
                        newNode: fn.params[m.index],
                    };
                }), tokens, comments, result.visitorKeys);
                // Disconnect the tree structure.
                exprSt.expression = null;
            });
            this.pushScope(restore, "};", this.currentScriptScopeKind);
        }
    }
    closeScope() {
        this.closeScopeCallbacks.pop()();
    }
    addProgramRestore(callback) {
        this.programRestoreCallbacks.push(callback);
    }
    appendScript(text, offset, kind, nodeType, callback) {
        const resultCallback = this.appendScriptWithoutOffset(text, kind, nodeType, (node, tokens, comments, result) => {
            fixLocations(node, tokens, comments, offset - resultCallback.start, result.visitorKeys, this.ctx);
            callback(node, tokens, comments, result);
        });
        return resultCallback;
    }
    appendScriptWithoutOffset(text, kind, nodeType, callback) {
        const { start: startOffset, end: endOffset } = this.script.addLet(text, kind);
        const restoreCallback = {
            start: startOffset,
            end: endOffset,
            nodeType,
            callback,
        };
        this.restoreCallbacks.push(restoreCallback);
        return restoreCallback;
    }
    pushScope(restoreCallback, closeToken, kind) {
        const upper = this.currentScriptScopeKind;
        this.currentScriptScopeKind = kind;
        this.closeScopeCallbacks.push(() => {
            this.script.addLet(closeToken, kind);
            this.currentScriptScopeKind = upper;
            restoreCallback.end = this.script.getCurrentVirtualCodeLength();
        });
    }
    /**
     * Restore AST nodes
     */
    restore(result) {
        const nodeToScope = getNodeToScope(result.scopeManager);
        const postprocessList = [];
        const callbackOption = {
            getScope,
            registerNodeToScope,
            scopeManager: result.scopeManager,
            visitorKeys: result.visitorKeys,
            addPostProcess: (cb) => postprocessList.push(cb),
        };
        this.restoreNodes(result, callbackOption);
        this.restoreProgram(result, callbackOption);
        postprocessList.forEach((p) => p());
        // Helpers
        /** Get scope */
        function getScope(node) {
            return getScopeFromNode(result.scopeManager, node);
        }
        /** Register node to scope */
        function registerNodeToScope(node, scope) {
            // If we replace the `scope.block` at this time,
            // the scope restore calculation will not work, so we will replace the `scope.block` later.
            postprocessList.push(() => {
                const beforeBlock = scope.block;
                scope.block = node;
                for (const variable of [
                    ...scope.variables,
                    ...(scope.upper?.variables ?? []),
                ]) {
                    for (const def of variable.defs) {
                        if (def.node === beforeBlock) {
                            def.node = node;
                        }
                    }
                }
            });
            const scopes = nodeToScope.get(node);
            if (scopes) {
                scopes.push(scope);
            }
            else {
                nodeToScope.set(node, [scope]);
            }
        }
    }
    /**
     * Restore AST nodes
     */
    restoreNodes(result, callbackOption) {
        let orderedRestoreCallback = this.restoreCallbacks.shift();
        if (!orderedRestoreCallback) {
            return;
        }
        const separateIndexes = this.script.separateIndexes;
        const tokens = result.ast.tokens;
        const processedTokens = [];
        const comments = result.ast.comments;
        const processedComments = [];
        let tok;
        while ((tok = tokens.shift())) {
            if (separateIndexes.includes(tok.range[0]) && tok.value === ";") {
                break;
            }
            if (orderedRestoreCallback.start <= tok.range[0]) {
                tokens.unshift(tok);
                break;
            }
            processedTokens.push(tok);
        }
        while ((tok = comments.shift())) {
            if (orderedRestoreCallback.start <= tok.range[0]) {
                comments.unshift(tok);
                break;
            }
            processedComments.push(tok);
        }
        const targetNodes = new Map();
        const removeStatements = [];
        traverseNodes(result.ast, {
            visitorKeys: result.visitorKeys,
            enterNode: (node) => {
                while (node.range && separateIndexes.includes(node.range[1] - 1)) {
                    node.range[1]--;
                    node.loc.end.column--;
                }
                if (node.loc.end.column < 0) {
                    node.loc.end = this.ctx.getLocFromIndex(node.range[1]);
                }
                if (node.parent === result.ast &&
                    separateIndexes[0] <= node.range[0]) {
                    removeStatements.push(node);
                }
                if (!orderedRestoreCallback) {
                    return;
                }
                if (orderedRestoreCallback.nodeType === node.type &&
                    orderedRestoreCallback.start <= node.range[0] &&
                    node.range[1] <= orderedRestoreCallback.end) {
                    targetNodes.set(node, orderedRestoreCallback);
                    orderedRestoreCallback = this.restoreCallbacks.shift();
                }
                //
            },
            leaveNode(node) {
                const restoreCallback = targetNodes.get(node);
                if (!restoreCallback) {
                    return;
                }
                const startIndex = {
                    token: tokens.findIndex((t) => restoreCallback.start <= t.range[0]),
                    comment: comments.findIndex((t) => restoreCallback.start <= t.range[0]),
                };
                if (startIndex.comment === -1) {
                    startIndex.comment = comments.length;
                }
                const endIndex = {
                    token: tokens.findIndex((t) => restoreCallback.end < t.range[1], startIndex.token),
                    comment: comments.findIndex((t) => restoreCallback.end < t.range[1], startIndex.comment),
                };
                if (endIndex.token === -1) {
                    endIndex.token = tokens.length;
                }
                if (endIndex.comment === -1) {
                    endIndex.comment = comments.length;
                }
                const targetTokens = tokens.splice(startIndex.token, endIndex.token - startIndex.token);
                const targetComments = comments.splice(startIndex.comment, endIndex.comment - startIndex.comment);
                restoreCallback.callback(node, targetTokens, targetComments, callbackOption);
                processedTokens.push(...targetTokens);
                processedComments.push(...targetComments);
            },
        });
        for (const st of removeStatements) {
            const index = result.ast.body.indexOf(st);
            result.ast.body.splice(index, 1);
        }
        result.ast.tokens = processedTokens;
        result.ast.comments = processedComments;
    }
    /**
     * Restore program node
     */
    restoreProgram(result, callbackOption) {
        for (const callback of this.programRestoreCallbacks) {
            callback(result.ast, result.ast.tokens, result.ast.comments, callbackOption);
        }
    }
    remapNodes(maps, tokens, comments, visitorKeys) {
        const targetMaps = [...maps];
        const first = targetMaps.shift();
        let tokenIndex = 0;
        for (; tokenIndex < tokens.length; tokenIndex++) {
            const token = tokens[tokenIndex];
            if (first.range[1] <= token.range[0]) {
                break;
            }
        }
        for (const map of targetMaps) {
            const startOffset = map.offset - first.offset + first.range[0];
            const endOffset = startOffset + (map.range[1] - map.range[0]);
            let removeCount = 0;
            let target = tokens[tokenIndex];
            while (target && target.range[1] <= startOffset) {
                removeCount++;
                target = tokens[tokenIndex + removeCount];
            }
            if (removeCount) {
                tokens.splice(tokenIndex, removeCount);
            }
            const bufferTokens = [];
            for (; tokenIndex < tokens.length; tokenIndex++) {
                const token = tokens[tokenIndex];
                if (endOffset <= token.range[0]) {
                    break;
                }
                bufferTokens.push(token);
            }
            fixLocations(map.newNode, bufferTokens, comments.filter((t) => startOffset <= t.range[0] && t.range[1] <= endOffset), map.range[0] - startOffset, visitorKeys, this.ctx);
        }
        tokens.splice(tokenIndex);
    }
    generateUniqueId(base) {
        return this.unique.generate(base, this.ctx.code, this.script.getCurrentVirtualCode());
    }
}
function getTypeParameterRanges(code, param) {
    const idRange = [
        param.range[0],
        param.constraint || param.default ? param.name.range[1] : param.range[1],
    ];
    let constraintRange = null;
    let defaultRange = null;
    if (param.constraint) {
        constraintRange = [
            param.constraint.range[0],
            param.default ? param.constraint.range[1] : param.range[1],
        ];
        const index = getTokenIndex(code, (code, index) => code.startsWith("extends", index), idRange[1], param.constraint.range[0]);
        if (index != null) {
            idRange[1] = index;
            constraintRange[0] = index + 7;
        }
    }
    if (param.default) {
        defaultRange = [param.default.range[0], param.range[1]];
        const index = getTokenIndex(code, (code, index) => code[index] === "=", constraintRange?.[1] ?? idRange[1], param.default.range[0]);
        if (index != null) {
            (constraintRange ?? idRange)[1] = index;
            defaultRange[0] = index + 1;
        }
    }
    return { idRange, constraintRange, defaultRange };
}
function getTokenIndex(code, targetToken, start, end) {
    let index = start;
    while (index < end) {
        if (targetToken(code, index)) {
            return index;
        }
        if (code.startsWith("//", index)) {
            const lfIndex = code.indexOf("\n", index);
            if (lfIndex >= 0) {
                index = lfIndex + 1;
                continue;
            }
            return null;
        }
        if (code.startsWith("/*", index)) {
            const endIndex = code.indexOf("*/", index);
            if (endIndex >= 0) {
                index = endIndex + 2;
                continue;
            }
            return null;
        }
        index++;
    }
    return null;
}
/** Get the node to scope map from given scope manager  */
function getNodeToScope(scopeManager) {
    if ("__nodeToScope" in scopeManager) {
        return scopeManager.__nodeToScope;
    }
    // transform scopeManager
    const nodeToScope = new WeakMap();
    for (const scope of scopeManager.scopes) {
        const scopes = nodeToScope.get(scope.block);
        if (scopes) {
            scopes.push(scope);
        }
        else {
            nodeToScope.set(scope.block, [scope]);
        }
    }
    scopeManager.acquire = function (node, inner) {
        /**
         * predicate
         */
        function predicate(testScope) {
            if (testScope.type === "function" && testScope.functionExpressionScope) {
                return false;
            }
            return true;
        }
        const scopes = nodeToScope.get(node);
        if (!scopes || scopes.length === 0) {
            return null;
        }
        // Heuristic selection from all scopes.
        // If you would like to get all scopes, please use ScopeManager#acquireAll.
        if (scopes.length === 1) {
            return scopes[0];
        }
        if (inner) {
            for (let i = scopes.length - 1; i >= 0; --i) {
                const scope = scopes[i];
                if (predicate(scope)) {
                    return scope;
                }
            }
        }
        else {
            for (let i = 0, iz = scopes.length; i < iz; ++i) {
                const scope = scopes[i];
                if (predicate(scope)) {
                    return scope;
                }
            }
        }
        return null;
    };
    return nodeToScope;
}
/** Extract the type scope of the given node. */
function extractTypeNodeScopes(node, result) {
    const scopes = new Set();
    for (const scope of iterateTypeNodeScopes(node)) {
        scopes.add(scope);
    }
    return scopes;
    /** Iterate the type scope of the given node. */
    function* iterateTypeNodeScopes(node) {
        if (node.type === "TSParenthesizedType") {
            // Skip TSParenthesizedType.
            yield* iterateTypeNodeScopes(node.typeAnnotation);
        }
        else if (node.type === "TSConditionalType") {
            yield result.getScope(node);
            // `falseType` of `TSConditionalType` is sibling scope.
            const falseType = node.falseType;
            yield* iterateTypeNodeScopes(falseType);
        }
        else if (node.type === "TSFunctionType" ||
            node.type === "TSMappedType" ||
            node.type === "TSConstructorType") {
            yield result.getScope(node);
        }
        else {
            const typeNode = node;
            for (const key of getKeys(typeNode, result.visitorKeys)) {
                for (const child of getNodes(typeNode, key)) {
                    yield* iterateTypeNodeScopes(child);
                }
            }
        }
    }
}
