"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decorateCallHierarchy = decorateCallHierarchy;
const utils_1 = require("../utils");
const svelte2tsx_1 = require("svelte2tsx");
const ENSURE_COMPONENT_HELPER = '__sveltets_2_ensureComponent';
function decorateCallHierarchy(ls, snapshotManager, typescript) {
    // don't need to patch prepare. It's always a ts/js file
    // const prepareCallHierarchy = ls.prepareCallHierarchy;
    const provideCallHierarchyIncomingCalls = ls.provideCallHierarchyIncomingCalls;
    const provideCallHierarchyOutgoingCalls = ls.provideCallHierarchyOutgoingCalls;
    ls.provideCallHierarchyIncomingCalls = (fileName, position) => {
        const program = ls.getProgram();
        // probably won't happen
        if (!program) {
            return provideCallHierarchyIncomingCalls(fileName, position);
        }
        const snapshot = snapshotManager.get(fileName);
        const componentExportOffset = isComponentModulePosition(fileName, position) && snapshot
            ? (0, utils_1.offsetOfGeneratedComponentExport)(snapshot)
            : -1;
        const redirectedPosition = componentExportOffset >= 0 ? componentExportOffset : position;
        const tsResult = provideCallHierarchyIncomingCalls(fileName, redirectedPosition);
        return tsResult
            .map((item) => {
            if (!(0, utils_1.isSvelteFilePath)(item.from.file)) {
                return item;
            }
            const snapshot = snapshotManager.get(item.from.file);
            const from = convertSvelteCallHierarchyItem(item.from, program);
            if (!from || !snapshot) {
                return null;
            }
            const fromSpans = item.fromSpans
                .map((span) => snapshot.getOriginalTextSpan(span))
                .filter(utils_1.isNotNullOrUndefined);
            return {
                from,
                fromSpans: fromSpans
            };
        })
            .concat(getInComingCallsForComponent(ls, program, fileName, redirectedPosition) ?? [])
            .filter(utils_1.isNotNullOrUndefined);
    };
    ls.provideCallHierarchyOutgoingCalls = (fileName, position) => {
        const program = ls.getProgram();
        // probably won't happen
        if (!program) {
            return provideCallHierarchyOutgoingCalls(fileName, position);
        }
        const sourceFile = program?.getSourceFile(fileName);
        const renderFunctionOffset = isComponentModulePosition(fileName, position) && sourceFile
            ? sourceFile.statements
                .find((statement) => typescript.isFunctionDeclaration(statement) &&
                statement.name?.getText() === svelte2tsx_1.internalHelpers.renderName)
                ?.name?.getStart()
            : -1;
        const offset = renderFunctionOffset != null && renderFunctionOffset >= 0
            ? renderFunctionOffset
            : position;
        const snapshot = snapshotManager.get(fileName);
        return provideCallHierarchyOutgoingCalls(fileName, offset)
            .concat(program && sourceFile && isComponentModulePosition(fileName, position)
            ? (getOutgoingCallsForComponent(program, sourceFile) ?? [])
            : [])
            .map((item) => {
            const to = convertSvelteCallHierarchyItem(item.to, program);
            if (!to ||
                item.to.name.startsWith('__sveltets') ||
                item.to.containerName === 'svelteHTML') {
                return null;
            }
            const fromSpans = snapshot
                ? item.fromSpans
                    .map((span) => snapshot.getOriginalTextSpan(span))
                    .filter(utils_1.isNotNullOrUndefined)
                : item.fromSpans;
            if (!fromSpans.length) {
                return null;
            }
            return {
                to,
                fromSpans
            };
        })
            .filter(utils_1.isNotNullOrUndefined);
    };
    function isComponentModulePosition(fileName, position) {
        return (0, utils_1.isSvelteFilePath)(fileName) && position === 0;
    }
    function convertSvelteCallHierarchyItem(item, program) {
        if (!(0, utils_1.isSvelteFilePath)(item.file)) {
            return item;
        }
        const snapshot = snapshotManager.get(item.file);
        if (!snapshot) {
            return null;
        }
        const redirectedCallHierarchyItem = redirectSvelteCallHierarchyItem(snapshot, program, item);
        if (redirectedCallHierarchyItem) {
            return redirectedCallHierarchyItem;
        }
        const selectionSpan = snapshot.getOriginalTextSpan(item.selectionSpan);
        if (!selectionSpan) {
            return null;
        }
        const span = snapshot.getOriginalTextSpan(item.span);
        if (!span) {
            return null;
        }
        return {
            ...item,
            span,
            selectionSpan
        };
    }
    function redirectSvelteCallHierarchyItem(snapshot, program, item) {
        const sourceFile = program.getSourceFile(item.file);
        if (!sourceFile) {
            return null;
        }
        if ((0, utils_1.isGeneratedSvelteComponentName)(item.name)) {
            return toComponentCallHierarchyItem(snapshot, item.file);
        }
        if (item.name === svelte2tsx_1.internalHelpers.renderName) {
            const end = item.selectionSpan.start + item.selectionSpan.length;
            const renderFunction = sourceFile.statements.find((statement) => statement.getStart() <= item.selectionSpan.start && statement.getEnd() >= end);
            if (!renderFunction || !sourceFile.statements.includes(renderFunction)) {
                return null;
            }
            return toComponentCallHierarchyItem(snapshot, item.file);
        }
        return null;
    }
    function toComponentCallHierarchyItem(snapshot, file) {
        const fileSpan = { start: 0, length: snapshot.getOriginalText().length };
        return {
            kind: typescript.ScriptElementKind.moduleElement,
            file: file,
            name: '',
            selectionSpan: { start: 0, length: 0 },
            span: fileSpan
        };
    }
    function getInComingCallsForComponent(ls, program, fileName, position) {
        if (!(0, utils_1.isSvelteFilePath)(fileName)) {
            return null;
        }
        return (ls
            .findReferences(fileName, position)
            ?.map((ref) => componentRefToIncomingCall(ref, program))
            .filter(utils_1.isNotNullOrUndefined) ?? null);
    }
    function componentRefToIncomingCall(ref, program) {
        const snapshot = (0, utils_1.isSvelteFilePath)(ref.definition.fileName) &&
            snapshotManager.get(ref.definition.fileName);
        const sourceFile = program.getSourceFile(ref.definition.fileName);
        if (!snapshot || !sourceFile) {
            return null;
        }
        const startTags = ref.references
            .map((ref) => {
            const generatedTextSpan = snapshot.getGeneratedTextSpan(ref.textSpan);
            const node = generatedTextSpan &&
                (0, utils_1.findNodeAtSpan)(sourceFile, generatedTextSpan, isComponentStartTag);
            if (node) {
                return ref;
            }
            return null;
        })
            .filter(utils_1.isNotNullOrUndefined);
        if (!startTags.length) {
            return null;
        }
        return {
            from: toComponentCallHierarchyItem(snapshot, ref.definition.fileName),
            fromSpans: startTags.map((tag) => tag.textSpan)
        };
    }
    function isComponentStartTag(node) {
        return (!!node &&
            node.parent &&
            typescript.isCallExpression(node.parent) &&
            typescript.isIdentifier(node.parent.expression) &&
            node.parent.expression.text === ENSURE_COMPONENT_HELPER &&
            typescript.isIdentifier(node) &&
            node === node.parent.arguments[0]);
    }
    function getOutgoingCallsForComponent(program, sourceFile) {
        const groups = new Map();
        const startTags = (0, utils_1.gatherDescendants)(sourceFile, isComponentStartTag);
        const typeChecker = program.getTypeChecker();
        for (const startTag of startTags) {
            const type = typeChecker.getTypeAtLocation(startTag);
            const symbol = type.aliasSymbol ?? type.symbol;
            const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
            if (!declaration || !typescript.isClassDeclaration(declaration)) {
                continue;
            }
            let group = groups.get(declaration);
            if (!group) {
                group = [];
                groups.set(declaration, group);
            }
            group.push({ start: startTag.getStart(), length: startTag.getWidth() });
        }
        return (Array.from(groups).map(([declaration, group]) => {
            const file = declaration.getSourceFile().fileName;
            const name = declaration.name?.getText() ?? file.slice(file.lastIndexOf('.'));
            const span = { start: declaration.getStart(), length: declaration.getWidth() };
            const selectionSpan = declaration.name
                ? { start: declaration.name.getStart(), length: declaration.name.getWidth() }
                : span;
            return {
                to: {
                    file,
                    kind: typescript.ScriptElementKind.classElement,
                    name,
                    selectionSpan,
                    span
                },
                fromSpans: group
            };
        }) ?? null);
    }
}
//# sourceMappingURL=call-hierarchy.js.map