"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FindReferencesProviderImpl = void 0;
const typescript_1 = __importDefault(require("typescript"));
const utils_1 = require("../../../utils");
const utils_2 = require("../utils");
const utils_3 = require("./utils");
class FindReferencesProviderImpl {
    constructor(lsAndTsDocResolver, componentReferencesProvider) {
        this.lsAndTsDocResolver = lsAndTsDocResolver;
        this.componentReferencesProvider = componentReferencesProvider;
    }
    async findReferences(document, position, context, cancellationToken) {
        if (this.isPositionForComponentCodeLens(position) ||
            this.isScriptStartOrEndTag(position, document)) {
            return this.componentReferencesProvider.findComponentReferences(document.uri);
        }
        const { lang, tsDoc, lsContainer } = await this.getLSAndTSDoc(document);
        if (cancellationToken?.isCancellationRequested) {
            return null;
        }
        const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position));
        const rawReferences = lang.findReferences(tsDoc.filePath, tsDoc.offsetAt(tsDoc.getGeneratedPosition(position)));
        if (!rawReferences) {
            return null;
        }
        const snapshots = new utils_3.SnapshotMap(this.lsAndTsDocResolver, lsContainer);
        snapshots.set(tsDoc.filePath, tsDoc);
        if (rawReferences.some((ref) => ref.definition.kind === typescript_1.default.ScriptElementKind.alias)) {
            const componentReferences = await this.checkIfHasAliasedComponentReference(offset, tsDoc, lang);
            if (componentReferences?.length) {
                return componentReferences;
            }
        }
        const references = rawReferences.flatMap((ref) => ref.references);
        references.push(...(await this.getStoreReferences(references, tsDoc, snapshots, lang, cancellationToken)));
        const locations = await Promise.all(references.map(async (ref) => this.mapReference(ref, context, snapshots, cancellationToken)));
        return (locations
            .filter(utils_1.isNotNullOrUndefined)
            // Possible $store references are added afterwards, sort for correct order
            .sort(sortLocationByFileAndRange));
    }
    isScriptStartOrEndTag(position, document) {
        if (!document.scriptInfo) {
            return false;
        }
        const { start, end } = document.scriptInfo.container;
        const offset = document.offsetAt(position);
        return ((offset >= start && offset <= start + '<script'.length) ||
            (offset >= end - '</script>'.length && offset <= end));
    }
    isPositionForComponentCodeLens(position) {
        return position.line === 0 && position.character === 0;
    }
    /**
     * If references of a $store are searched, also find references for the corresponding store
     * and vice versa.
     */
    async getStoreReferences(references, tsDoc, snapshots, lang, cancellationToken) {
        // If user started finding references at $store, find references for store, too
        let storeReferences = [];
        const storeReference = references.find((ref) => (0, utils_1.normalizePath)(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 = additionalReferences.flatMap((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 (cancellationToken?.isCancellationRequested) {
                return [];
            }
            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(...additionalReferences.flatMap((ref) => ref.references));
        }
        return [...storeReferences, ...$storeReferences];
    }
    async checkIfHasAliasedComponentReference(offset, tsDoc, lang) {
        const definitions = lang.getDefinitionAtPosition(tsDoc.filePath, offset);
        if (!definitions?.length) {
            return null;
        }
        const nonAliasDefinitions = definitions.filter((definition) => (0, utils_2.isGeneratedSvelteComponentName)(definition.name));
        const references = await Promise.all(nonAliasDefinitions.map((definition) => this.componentReferencesProvider.findComponentReferences((0, utils_1.pathToUrl)(definition.fileName))));
        const flattened = [];
        for (const ref of references) {
            if (ref) {
                const tmp = []; // perf optimization: we know each iteration has unique references
                for (const r of ref) {
                    const exists = flattened.some((f) => f.uri === r.uri &&
                        f.range.start.line === r.range.start.line &&
                        f.range.start.character === r.range.start.character);
                    if (!exists) {
                        tmp.push(r);
                    }
                }
                flattened.push(...tmp);
            }
        }
        return flattened;
    }
    async mapReference(ref, context, snapshots, cancellationToken) {
        if (!context.includeDeclaration && ref.isDefinition) {
            return null;
        }
        const snapshot = await snapshots.retrieve(ref.fileName);
        if (cancellationToken?.isCancellationRequested) {
            return null;
        }
        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