"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FindReferencesProviderImpl = void 0;
const utils_1 = require("../../../utils");
const utils_2 = require("../utils");
const utils_3 = require("./utils");
class FindReferencesProviderImpl {
    constructor(lsAndTsDocResolver) {
        this.lsAndTsDocResolver = lsAndTsDocResolver;
    }
    async findReferences(document, position, context) {
        const { lang, tsDoc } = await this.getLSAndTSDoc(document);
        const rawReferences = lang.findReferences(tsDoc.filePath, tsDoc.offsetAt(tsDoc.getGeneratedPosition(position)));
        if (!rawReferences) {
            return null;
        }
        const snapshots = new utils_3.SnapshotMap(this.lsAndTsDocResolver);
        snapshots.set(tsDoc.filePath, tsDoc);
        const references = (0, utils_1.flatten)(rawReferences.map((ref) => ref.references));
        references.push(...(await this.getStoreReferences(references, tsDoc, snapshots, lang)));
        const locations = await Promise.all(references.map(async (ref) => this.mapReference(ref, context, snapshots)));
        return (locations
            .filter(utils_1.isNotNullOrUndefined)
            // Possible $store references are added afterwards, sort for correct order
            .sort(sortLocationByFileAndRange));
    }
    /**
     * If references of a $store are searched, also find references for the corresponding store
     * and vice versa.
     */
    async getStoreReferences(references, tsDoc, snapshots, lang) {
        // If user started finding references at $store, find references for store, too
        let storeReferences = [];
        const storeReference = references.find((ref) => ref.fileName === tsDoc.filePath &&
            (0, utils_3.isTextSpanInGeneratedCode)(tsDoc.getFullText(), ref.textSpan) &&
            (0, utils_3.is$storeVariableIn$storeDeclaration)(tsDoc.getFullText(), ref.textSpan.start));
        if (storeReference) {
            const additionalReferences = lang.findReferences(tsDoc.filePath, (0, utils_3.getStoreOffsetOf$storeDeclaration)(tsDoc.getFullText(), storeReference.textSpan.start)) || [];
            storeReferences = (0, utils_1.flatten)(additionalReferences.map((ref) => ref.references));
        }
        // If user started finding references at store, find references for $store, too
        // If user started finding references at $store, find references for $store in other files
        const $storeReferences = [];
        for (const ref of [...references, ...storeReferences]) {
            const snapshot = await snapshots.retrieve(ref.fileName);
            if (!((0, utils_3.isTextSpanInGeneratedCode)(snapshot.getFullText(), ref.textSpan) &&
                (0, utils_3.isStoreVariableIn$storeDeclaration)(snapshot.getFullText(), ref.textSpan.start))) {
                continue;
            }
            if (storeReference?.fileName === ref.fileName) {
                // $store in X -> usages of store -> store in X -> we would add duplicate $store references
                continue;
            }
            const additionalReferences = lang.findReferences(snapshot.filePath, (0, utils_3.get$storeOffsetOf$storeDeclaration)(snapshot.getFullText(), ref.textSpan.start)) || [];
            $storeReferences.push(...(0, utils_1.flatten)(additionalReferences.map((ref) => ref.references)));
        }
        return [...storeReferences, ...$storeReferences];
    }
    async mapReference(ref, context, snapshots) {
        if (!context.includeDeclaration && ref.isDefinition) {
            return null;
        }
        const snapshot = await snapshots.retrieve(ref.fileName);
        if ((0, utils_3.isTextSpanInGeneratedCode)(snapshot.getFullText(), ref.textSpan)) {
            return null;
        }
        // TODO we should deduplicate if we support finding references from multiple language service
        const location = (0, utils_2.convertToLocationForReferenceOrDefinition)(snapshot, ref.textSpan);
        // Some references are in generated code but not wrapped with explicit ignore comments.
        // These show up as zero-length ranges, so filter them out.
        if (!(0, utils_2.hasNonZeroRange)(location)) {
            return null;
        }
        return location;
    }
    async getLSAndTSDoc(document) {
        return this.lsAndTsDocResolver.getLSAndTSDoc(document);
    }
}
exports.FindReferencesProviderImpl = FindReferencesProviderImpl;
function sortLocationByFileAndRange(l1, l2) {
    const localeCompare = l1.uri.localeCompare(l2.uri);
    return localeCompare === 0
        ? (l1.range.start.line - l2.range.start.line) * 10000 +
            (l1.range.start.character - l2.range.start.character)
        : localeCompare;
}
//# sourceMappingURL=FindReferencesProvider.js.map