import * as lsp from 'vscode-languageserver';
import * as lspcalls from './lsp-protocol.calls.proposed.js';
import { uriToPath, toLocation, toSymbolKind, pathToUri } from './protocol-translation.js';
import { Range } from './utils/typeConverters.js';
export async function computeCallers(tspClient, args) {
    const nullResult = { calls: [] };
    const contextDefinition = await getDefinition(tspClient, args);
    if (!contextDefinition) {
        return nullResult;
    }
    const contextSymbol = await findEnclosingSymbol(tspClient, contextDefinition);
    if (!contextSymbol) {
        return nullResult;
    }
    const callerReferences = await findNonDefinitionReferences(tspClient, contextDefinition);
    const calls = [];
    for (const callerReference of callerReferences) {
        const symbol = await findEnclosingSymbol(tspClient, callerReference);
        if (!symbol) {
            continue;
        }
        const location = toLocation(callerReference, undefined);
        calls.push({
            location,
            symbol,
        });
    }
    return { calls, symbol: contextSymbol };
}
export async function computeCallees(tspClient, args, documentProvider) {
    const nullResult = { calls: [] };
    const contextDefinition = await getDefinition(tspClient, args);
    if (!contextDefinition) {
        return nullResult;
    }
    const contextSymbol = await findEnclosingSymbol(tspClient, contextDefinition);
    if (!contextSymbol) {
        return nullResult;
    }
    const outgoingCallReferences = await findOutgoingCalls(tspClient, contextSymbol, documentProvider);
    const calls = [];
    for (const reference of outgoingCallReferences) {
        const definitionReferences = await findDefinitionReferences(tspClient, reference);
        const definitionReference = definitionReferences[0];
        if (!definitionReference) {
            continue;
        }
        const definitionSymbol = await findEnclosingSymbol(tspClient, definitionReference);
        if (!definitionSymbol) {
            continue;
        }
        const location = toLocation(reference, undefined);
        calls.push({
            location,
            symbol: definitionSymbol,
        });
    }
    return { calls, symbol: contextSymbol };
}
async function findOutgoingCalls(tspClient, contextSymbol, documentProvider) {
    /**
     * The TSP does not provide call references.
     * As long as we are not able to access the AST in a tsserver plugin and return the information necessary as metadata to the reponse,
     * we need to test possible calls.
     */
    const computeCallCandidates = (document, range) => {
        const symbolText = document.getText(range);
        const regex = /\W([$_a-zA-Z0-9\u{00C0}-\u{E007F}]+)(<.*>)?\(/gmu; // Example: matches `candidate` in " candidate()", "Foo.candidate<T>()", etc.
        let match = regex.exec(symbolText);
        const candidates = [];
        while (match) {
            const identifier = match[1];
            if (identifier) {
                const start = match.index + match[0].indexOf(identifier);
                const end = start + identifier.length;
                candidates.push({ identifier, start, end });
            }
            match = regex.exec(symbolText);
        }
        const offset = document.offsetAt(range.start);
        const candidateRanges = candidates.map(c => lsp.Range.create(document.positionAt(offset + c.start), document.positionAt(offset + c.end)));
        return candidateRanges;
    };
    /**
     * This function tests a candidate and returns a locaion for a valid call.
     */
    const validateCall = async (file, candidateRange) => {
        const tspPosition = { line: candidateRange.start.line + 1, offset: candidateRange.start.character + 1 };
        const references = await findNonDefinitionReferences(tspClient, { file, start: tspPosition, end: tspPosition });
        for (const reference of references) {
            const tspPosition = { line: candidateRange.start.line + 1, offset: candidateRange.start.character + 1 };
            if (tspPosition.line === reference.start.line) {
                return reference;
            }
        }
    };
    const calls = [];
    const file = uriToPath(contextSymbol.location.uri);
    const document = documentProvider(file);
    if (!document) {
        return calls;
    }
    const candidateRanges = computeCallCandidates(document, contextSymbol.location.range);
    for (const candidateRange of candidateRanges) {
        const call = await validateCall(file, candidateRange);
        if (call) {
            calls.push(call);
        }
    }
    return calls;
}
async function getDefinition(tspClient, args) {
    const file = uriToPath(args.textDocument.uri);
    if (!file) {
        return undefined;
    }
    const definitionResult = await tspClient.request("definition" /* CommandTypes.Definition */, {
        file,
        line: args.position.line + 1,
        offset: args.position.character + 1,
    });
    return definitionResult.body ? definitionResult.body[0] : undefined;
}
async function findEnclosingSymbol(tspClient, args) {
    const file = args.file;
    const response = await tspClient.request("navtree" /* CommandTypes.NavTree */, { file });
    const tree = response.body;
    if (!tree || !tree.childItems) {
        return undefined;
    }
    const pos = lsp.Position.create(args.start.line - 1, args.start.offset - 1);
    const symbol = findEnclosingSymbolInTree(tree, lsp.Range.create(pos, pos));
    if (!symbol) {
        return undefined;
    }
    const uri = pathToUri(file, undefined);
    return lspcalls.DefinitionSymbol.create(uri, symbol);
}
function findEnclosingSymbolInTree(parent, range) {
    const inSpan = (span) => !!Range.intersection(Range.fromTextSpan(span), range);
    const inTree = (tree) => tree.spans.some(span => inSpan(span));
    let candidate = inTree(parent) ? parent : undefined;
    outer: while (candidate) {
        const children = candidate.childItems || [];
        for (const child of children) {
            if (inTree(child)) {
                candidate = child;
                continue outer;
            }
        }
        break;
    }
    if (!candidate) {
        return undefined;
    }
    const span = candidate.spans.find(span => inSpan(span));
    const spanRange = Range.fromTextSpan(span);
    let selectionRange = spanRange;
    if (candidate.nameSpan) {
        const nameRange = Range.fromTextSpan(candidate.nameSpan);
        if (Range.intersection(spanRange, nameRange)) {
            selectionRange = nameRange;
        }
    }
    return {
        name: candidate.text,
        kind: toSymbolKind(candidate.kind),
        range: spanRange,
        selectionRange: selectionRange,
    };
}
async function findDefinitionReferences(tspClient, args) {
    return (await findReferences(tspClient, args)).filter(ref => ref.isDefinition);
}
async function findNonDefinitionReferences(tspClient, args) {
    return (await findReferences(tspClient, args)).filter(ref => !ref.isDefinition);
}
async function findReferences(tspClient, args) {
    const file = args.file;
    const result = await tspClient.request("references" /* CommandTypes.References */, {
        file,
        line: args.start.line,
        offset: args.start.offset,
    });
    if (!result.body) {
        return [];
    }
    return result.body.refs;
}
//# sourceMappingURL=calls.js.map