import path from 'node:path';
import { isAssetSpecifier } from './languages.js';
const DEFAULT_MAX_PROJECTS = 64;
const DEFAULT_MAX_AUXILIARY_BYTES = 64 * 1024 * 1024;
const MAX_DIAGNOSTICS = 100;
/** Stable producer/consumer discriminator for preferred-mode checker fallback. */
export const TYPESCRIPT_CHECKER_UNAVAILABLE_REASON = 'checker-unavailable';
/** Stable reason for compiler-AST calls that could not join parser call facts. */
export const RESOLVER_JOIN_MISS_REASON = 'resolver-join-miss';
let typescriptPromise;
async function loadTypeScript() {
    if (!typescriptPromise) {
        typescriptPromise = import('typescript')
            .then(module => ('default' in module ? module.default : module))
            .catch((error) => {
            typescriptPromise = undefined;
            throw error;
        });
    }
    return typescriptPromise;
}
function abortIfNeeded(signal) {
    if (signal?.aborted)
        throw signal.reason instanceof Error ? signal.reason : new Error('Operation aborted.');
}
function normalizedFileName(fileName) {
    return path.resolve(fileName);
}
function diagnosticMessage(ts, diagnostic) {
    const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
    const start = diagnostic.start;
    const location = diagnostic.file && start != null ? diagnostic.file.getLineAndCharacterOfPosition(start) : undefined;
    return {
        message,
        severity: diagnostic.category === ts.DiagnosticCategory.Error ? 'error' : 'warning',
        line: location ? location.line + 1 : undefined,
        column: location ? location.character + 1 : undefined,
        code: diagnostic.code,
        source: 'typescript',
    };
}
function addResolverDiagnostic(file, diagnostic) {
    const provenance = file.provenance;
    if (!provenance)
        return;
    const diagnostics = provenance.resolver.diagnostics ?? [];
    if (diagnostics.length < MAX_DIAGNOSTICS)
        diagnostics.push(diagnostic);
    provenance.resolver.diagnostics = diagnostics;
    provenance.resolver.diagnosticsTruncated = diagnostics.length >= MAX_DIAGNOSTICS;
}
function setResolverState(file, tier, coverage) {
    if (!file.provenance)
        return;
    file.provenance.resolver.tier = tier;
    file.provenance.resolver.coverage = coverage;
}
function isTypeScriptFamily(file) {
    return file.language === 'typescript' || file.language === 'javascript' || file.language === 'vue';
}
function vueCompilerSource(ts, file) {
    const raw = ts.sys.readFile(file.path);
    if (raw == null)
        return { fileName: `${file.path}.ts` };
    const virtual = raw
        .split('')
        .map(character => (character === '\n' || character === '\r' ? character : ' '));
    let scriptKind = ts.ScriptKind.TS;
    const scriptPattern = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
    let match;
    while ((match = scriptPattern.exec(raw)) !== null) {
        const openingLength = match[0].indexOf('>') + 1;
        const start = match.index + openingLength;
        const end = start + match[2].length;
        const attributes = match[1];
        if (/\blang\s*=\s*['"]tsx['"]/.test(attributes))
            scriptKind = ts.ScriptKind.TSX;
        else if (/\blang\s*=\s*['"]jsx['"]/.test(attributes))
            scriptKind = ts.ScriptKind.JSX;
        for (let offset = start; offset < end; offset++)
            virtual[offset] = raw[offset] ?? ' ';
    }
    const suffix = scriptKind === ts.ScriptKind.TSX ? 'tsx' : scriptKind === ts.ScriptKind.JSX ? 'jsx' : 'ts';
    return { fileName: `${file.path}.${suffix}`, content: virtual.join(''), scriptKind };
}
function compilerSources(ts, files) {
    return new Map(files.map(file => [
        normalizedFileName(file.path),
        file.language === 'vue' ? vueCompilerSource(ts, file) : { fileName: file.path },
    ]));
}
function groupProjects(ts, files) {
    const groups = new Map();
    const includedFilesByConfig = new Map();
    for (const file of files) {
        const directory = path.dirname(file.path);
        const candidate = ts.findConfigFile(directory, ts.sys.fileExists, 'tsconfig.json') ??
            ts.findConfigFile(directory, ts.sys.fileExists, 'jsconfig.json');
        let configPath = candidate;
        if (candidate && file.language !== 'vue') {
            const normalizedConfig = normalizedFileName(candidate);
            if (!includedFilesByConfig.has(normalizedConfig)) {
                const read = ts.readConfigFile(candidate, ts.sys.readFile);
                if (read.error) {
                    // Keep a malformed nearest config attached to the project so
                    // preferred mode can report it and required mode can reject it.
                    includedFilesByConfig.set(normalizedConfig, undefined);
                }
                else {
                    const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(candidate), undefined, candidate);
                    includedFilesByConfig.set(normalizedConfig, new Set(parsed.fileNames.map(fileName => normalizedFileName(fileName))));
                }
            }
            const included = includedFilesByConfig.get(normalizedConfig);
            if (included && !included.has(normalizedFileName(file.path)))
                configPath = undefined;
        }
        const key = configPath ? normalizedFileName(configPath) : '<default>';
        const group = groups.get(key) ?? { configPath, files: [] };
        group.files.push(file);
        groups.set(key, group);
    }
    return [...groups.values()];
}
function projectOptions(ts, group) {
    if (!group.configPath) {
        return {
            options: {
                allowJs: true,
                checkJs: false,
                // A repository without a config has no declared Node ESM extension
                // policy. Bundler resolution is the conservative TypeScript default
                // for analyzing the extensionless imports accepted by the source
                // parsers; an actual tsconfig/jsconfig always overrides this.
                module: ts.ModuleKind.ESNext,
                moduleResolution: ts.ModuleResolutionKind.Bundler,
                target: ts.ScriptTarget.Latest,
                jsx: ts.JsxEmit.Preserve,
                noEmit: true,
            },
            diagnostics: [],
        };
    }
    const read = ts.readConfigFile(group.configPath, ts.sys.readFile);
    if (read.error)
        return { options: {}, diagnostics: [read.error] };
    const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(group.configPath), undefined, group.configPath);
    return {
        options: {
            ...parsed.options,
            allowJs: parsed.options.allowJs ?? true,
            noEmit: true,
        },
        diagnostics: [...parsed.errors],
    };
}
function declarationName(ts, declaration) {
    const named = declaration;
    if (named.name) {
        const name = named.name;
        if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
            return name.text;
    }
    if (ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration)) {
        const parent = declaration.parent;
        if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name))
            return parent.name.text;
        if (ts.isPropertyDeclaration(parent) && parent.name)
            return parent.name.getText(parent.getSourceFile());
        if (ts.isExportAssignment(parent) && !parent.isExportEquals)
            return 'default';
    }
    if (ts.isExportAssignment(declaration))
        return 'default';
    return undefined;
}
function modeledDeclarationIdentity(ts, declaration, nameHint) {
    if (ts.isConstructorDeclaration(declaration)) {
        return { name: 'constructor', types: new Set(['function']) };
    }
    if (ts.isConstructSignatureDeclaration(declaration)) {
        return { name: 'new', types: new Set(['function']) };
    }
    if (ts.isCallSignatureDeclaration(declaration)) {
        return { name: 'call', types: new Set(['function']) };
    }
    if (ts.isFunctionDeclaration(declaration) ||
        ts.isMethodDeclaration(declaration) ||
        ts.isMethodSignature(declaration) ||
        ts.isGetAccessorDeclaration(declaration) ||
        ts.isSetAccessorDeclaration(declaration) ||
        ts.isArrowFunction(declaration) ||
        ts.isFunctionExpression(declaration)) {
        const name = nameHint ?? declarationName(ts, declaration);
        return name ? { name, types: new Set(['function']) } : undefined;
    }
    if (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration)) {
        const name = nameHint ?? declarationName(ts, declaration);
        return name ? { name, types: new Set(['class']) } : undefined;
    }
    if (ts.isInterfaceDeclaration(declaration)) {
        return { name: declaration.name.text, types: new Set(['interface']) };
    }
    if (ts.isTypeAliasDeclaration(declaration)) {
        return { name: declaration.name.text, types: new Set(['type-alias']) };
    }
    if (ts.isEnumDeclaration(declaration)) {
        return { name: declaration.name.text, types: new Set(['enum']) };
    }
    if (ts.isVariableDeclaration(declaration) || ts.isPropertyDeclaration(declaration)) {
        const name = nameHint ?? declarationName(ts, declaration);
        return name ? { name, types: new Set(['function', 'variable']) } : undefined;
    }
    if (ts.isExportAssignment(declaration) &&
        !declaration.isExportEquals &&
        (ts.isArrowFunction(declaration.expression) || ts.isFunctionExpression(declaration.expression))) {
        return { name: 'default', types: new Set(['function']) };
    }
    // Parameters, binding elements, and arbitrary properties are not graph
    // declarations. Mapping one to its containing function/class would turn a
    // checker refusal into a trusted self-loop.
    return undefined;
}
function symbolContainsOffset(symbol, sourceFile, offset) {
    const span = symbol.span;
    if (!span?.exact || span.startColumn == null || span.endColumn == null)
        return false;
    const location = sourceFile.getLineAndCharacterOfPosition(offset);
    const line = location.line + 1;
    const column = location.character + 1;
    const atOrAfterStart = line > span.startLine || (line === span.startLine && column >= span.startColumn);
    const beforeEnd = line < span.endLine || (line === span.endLine && column < span.endColumn);
    return atOrAfterStart && beforeEnd;
}
function symbolTarget(ts, file, declaration, nameHint) {
    const identity = modeledDeclarationIdentity(ts, declaration, nameHint);
    if (!identity)
        return undefined;
    const sourceFile = declaration.getSourceFile();
    const start = declaration.getStart(sourceFile);
    const line = sourceFile.getLineAndCharacterOfPosition(start).line + 1;
    const containing = file.symbols
        .filter(symbol => identity.types.has(symbol.type) &&
        symbol.name === identity.name &&
        symbolContainsOffset(symbol, sourceFile, start))
        .sort((left, right) => {
        const leftSize = (left.span?.endByte ?? Number.MAX_SAFE_INTEGER) - (left.span?.startByte ?? 0);
        const rightSize = (right.span?.endByte ?? Number.MAX_SAFE_INTEGER) - (right.span?.startByte ?? 0);
        return leftSize - rightSize;
    });
    const bySpan = containing[0];
    const fallback = file.symbols.find(symbol => symbol.line === line && identity.types.has(symbol.type) && symbol.name === identity.name);
    const parsed = bySpan ?? fallback;
    if (!parsed)
        return undefined;
    return {
        path: file.path,
        type: parsed.type,
        name: parsed.name,
        line: parsed.line,
        containerName: parsed.containerName,
        localId: parsed.localId,
    };
}
function unwrapAlias(ts, checker, symbol) {
    if ((symbol.flags & ts.SymbolFlags.Alias) === 0)
        return symbol;
    try {
        return checker.getAliasedSymbol(symbol);
    }
    catch {
        return symbol;
    }
}
function expressionParts(ts, sourceFile, expression) {
    if (ts.isIdentifier(expression))
        return { calleeName: expression.text };
    if (ts.isPropertyAccessExpression(expression)) {
        return { calleeName: expression.name.text, qualifier: expression.expression.getText(sourceFile) };
    }
    if (ts.isElementAccessExpression(expression)) {
        const argument = expression.argumentExpression;
        const calleeName = argument && ts.isStringLiteralLike(argument) ? argument.text : expression.getText(sourceFile);
        return { calleeName, qualifier: expression.expression.getText(sourceFile) };
    }
    return { calleeName: expression.getText(sourceFile) };
}
function callLookupKey(line, calleeName, qualifier) {
    return `${line}\u0000${calleeName}\u0000${qualifier ?? ''}`;
}
function targetFromDeclaration(ts, filesByPath, declaration, nameHint) {
    const file = filesByPath.get(normalizedFileName(declaration.getSourceFile().fileName));
    return file ? symbolTarget(ts, file, declaration, nameHint) : undefined;
}
function semanticResolutionForNode(ts, checker, node, filesByPath) {
    const signature = checker.getResolvedSignature(node);
    const declaration = signature?.declaration;
    if (declaration) {
        const target = targetFromDeclaration(ts, filesByPath, declaration);
        if (target)
            return { status: 'resolved', basis: 'typescript-checker', target };
    }
    const symbolLocation = ts.isPropertyAccessExpression(node.expression)
        ? node.expression.name
        : ts.isElementAccessExpression(node.expression)
            ? (node.expression.argumentExpression ?? node.expression)
            : node.expression;
    const symbolAtExpression = checker.getSymbolAtLocation(symbolLocation);
    if (!symbolAtExpression)
        return { status: 'unresolved', basis: 'typescript-checker' };
    const symbol = unwrapAlias(ts, checker, symbolAtExpression);
    const targets = (symbol.declarations ?? [])
        .map(declarationCandidate => targetFromDeclaration(ts, filesByPath, declarationCandidate, symbol.getName()))
        .filter((target) => !!target)
        .filter((target, index, array) => array.findIndex(candidate => candidate.path === target.path &&
        candidate.type === target.type &&
        candidate.name === target.name &&
        candidate.line === target.line &&
        candidate.containerName === target.containerName) === index);
    if (targets.length === 1)
        return { status: 'resolved', basis: 'typescript-checker', target: targets[0] };
    if (targets.length > 1)
        return { status: 'ambiguous', basis: 'typescript-checker', candidates: targets };
    return { status: 'unresolved', basis: 'typescript-checker' };
}
function enrichCalls(ts, checker, sourceFile, parsedFile, filesByPath, signal) {
    const pending = new Map();
    let hits = 0;
    let misses = 0;
    for (const call of parsedFile.calls) {
        const key = callLookupKey(call.line, call.calleeName, call.qualifier);
        const entries = pending.get(key) ?? [];
        entries.push(call);
        pending.set(key, entries);
    }
    const stack = [sourceFile];
    while (stack.length > 0) {
        abortIfNeeded(signal);
        const node = stack.pop();
        if (!node)
            continue;
        if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
            const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
            const parts = expressionParts(ts, sourceFile, node.expression);
            const key = callLookupKey(location.line + 1, parts.calleeName, parts.qualifier);
            const call = pending.get(key)?.shift();
            if (call) {
                hits++;
                call.semanticResolution = semanticResolutionForNode(ts, checker, node, filesByPath);
            }
            else {
                misses++;
            }
        }
        const children = [];
        ts.forEachChild(node, child => {
            children.push(child);
        });
        for (let index = children.length - 1; index >= 0; index--)
            stack.push(children[index]);
    }
    return { hits, misses };
}
function enrichInheritance(ts, checker, sourceFile, parsedFile, filesByPath, signal) {
    const byLine = new Map();
    for (const inheritance of parsedFile.inheritances) {
        const entries = byLine.get(inheritance.line) ?? [];
        entries.push(inheritance);
        byLine.set(inheritance.line, entries);
    }
    const stack = [sourceFile];
    while (stack.length > 0) {
        abortIfNeeded(signal);
        const node = stack.pop();
        if (!node)
            continue;
        if (ts.isExpressionWithTypeArguments(node)) {
            const line = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
            const inheritance = byLine.get(line)?.shift();
            if (inheritance) {
                const raw = checker.getSymbolAtLocation(node.expression);
                if (raw) {
                    const symbol = unwrapAlias(ts, checker, raw);
                    const targets = (symbol.declarations ?? [])
                        .map(declaration => targetFromDeclaration(ts, filesByPath, declaration, symbol.getName()))
                        .filter((target) => !!target);
                    if (targets.length === 1) {
                        inheritance.semanticResolution = { status: 'resolved', basis: 'typescript-checker', target: targets[0] };
                    }
                    else if (targets.length > 1) {
                        inheritance.semanticResolution = { status: 'ambiguous', basis: 'typescript-checker', candidates: targets };
                    }
                    else {
                        inheritance.semanticResolution = { status: 'unresolved', basis: 'typescript-checker' };
                    }
                }
                else {
                    inheritance.semanticResolution = { status: 'unresolved', basis: 'typescript-checker' };
                }
            }
        }
        const children = [];
        ts.forEachChild(node, child => {
            children.push(child);
        });
        for (let index = children.length - 1; index >= 0; index--)
            stack.push(children[index]);
    }
}
function resolveImports(ts, group, options, indexedPaths) {
    const cache = ts.createModuleResolutionCache(process.cwd(), fileName => fileName, options);
    for (const file of group.files) {
        for (const parsedImport of file.imports) {
            // TypeScript deliberately does not resolve Vue SFC modules without a
            // framework plugin. The syntax parser already resolved these against
            // the repository's indexed paths, so retain that exact path as a
            // narrowly-scoped fallback instead of turning every valid `.vue`
            // import into an unresolved edge during checker enrichment.
            const indexedVueFallback = parsedImport.resolvedPath &&
                path.extname(parsedImport.resolvedPath).toLowerCase() === '.vue' &&
                indexedPaths.has(normalizedFileName(parsedImport.resolvedPath))
                ? normalizedFileName(parsedImport.resolvedPath)
                : undefined;
            // Parser-time resolution is deliberately cheap and may have consulted a
            // different/root config. This project resolver is authoritative for the
            // current snapshot, including a negative result.
            delete parsedImport.resolvedPath;
            for (const parsedExport of file.exports ?? []) {
                if (parsedExport.specifier === parsedImport.specifier)
                    delete parsedExport.resolvedPath;
            }
            if (isAssetSpecifier(parsedImport.specifier)) {
                parsedImport.kind = 'external';
                continue;
            }
            const resolution = ts.resolveModuleName(parsedImport.specifier, file.path, options, ts.sys, cache).resolvedModule;
            if (!resolution) {
                if (indexedVueFallback) {
                    parsedImport.resolvedPath = indexedVueFallback;
                    parsedImport.kind = 'local';
                    for (const parsedExport of file.exports ?? []) {
                        if (parsedExport.specifier === parsedImport.specifier)
                            parsedExport.resolvedPath = indexedVueFallback;
                    }
                    continue;
                }
                parsedImport.kind =
                    parsedImport.specifier.startsWith('.') || parsedImport.specifier.startsWith('/') ? 'unresolved' : 'external';
                continue;
            }
            const resolvedName = normalizedFileName(resolution.resolvedFileName.replace(/\.d\.(?:ts|mts|cts)$/, '.ts'));
            const direct = indexedPaths.has(resolvedName)
                ? resolvedName
                : [...indexedPaths].find(candidate => {
                    const withoutExtension = candidate.replace(/\.(?:[cm]?[jt]sx?)$/, '');
                    const resolutionBase = normalizedFileName(resolution.resolvedFileName).replace(/\.(?:[cm]?[jt]sx?)$/, '');
                    return withoutExtension === resolutionBase;
                });
            if (direct) {
                parsedImport.resolvedPath = direct;
                parsedImport.kind = 'local';
                for (const parsedExport of file.exports ?? []) {
                    if (parsedExport.specifier === parsedImport.specifier)
                        parsedExport.resolvedPath = direct;
                }
            }
            else {
                parsedImport.kind = 'external';
            }
        }
    }
}
async function enrichProject(ts, group, allFiles, options, sources) {
    const project = projectOptions(ts, group);
    if ((options.mode ?? 'preferred') === 'required' &&
        project.diagnostics.some(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error)) {
        const detail = project.diagnostics
            .slice(0, 3)
            .map(diagnostic => ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'))
            .join('; ');
        throw new Error(`TypeScript checker configuration is invalid. ${detail}`);
    }
    const diagnostics = project.diagnostics.map(diagnostic => diagnosticMessage(ts, diagnostic));
    const projectDegraded = project.diagnostics.some(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error);
    const indexedPaths = new Set(allFiles.map(file => normalizedFileName(file.path)));
    const filesByPath = new Map();
    for (const file of allFiles) {
        filesByPath.set(normalizedFileName(file.path), file);
        const compilerSource = sources.get(normalizedFileName(file.path));
        if (compilerSource)
            filesByPath.set(normalizedFileName(compilerSource.fileName), file);
    }
    let auxiliaryBytes = 0;
    let auxiliaryLimitReached = false;
    const rootNames = group.files.map(file => sources.get(normalizedFileName(file.path))?.fileName ?? file.path);
    const rootSet = new Set(rootNames.map(normalizedFileName));
    const compilerSourceByName = new Map([...sources.values()].map(source => [normalizedFileName(source.fileName), source]));
    const baseHost = ts.createCompilerHost(project.options, true);
    const maxAuxiliaryBytes = options.maxAuxiliaryBytes ?? DEFAULT_MAX_AUXILIARY_BYTES;
    const host = {
        ...baseHost,
        fileExists(fileName) {
            if (compilerSourceByName.has(normalizedFileName(fileName)))
                return true;
            return baseHost.fileExists(fileName);
        },
        readFile(fileName) {
            const source = compilerSourceByName.get(normalizedFileName(fileName));
            return source?.content ?? baseHost.readFile(fileName);
        },
        getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) {
            const normalized = normalizedFileName(fileName);
            const source = compilerSourceByName.get(normalized);
            if (source?.content != null) {
                return ts.createSourceFile(fileName, source.content, languageVersionOrOptions, true, source.scriptKind ?? ts.ScriptKind.TS);
            }
            if (!rootSet.has(normalized)) {
                const text = baseHost.readFile(fileName);
                if (text != null) {
                    const size = Buffer.byteLength(text, 'utf8');
                    if (auxiliaryBytes + size > maxAuxiliaryBytes) {
                        auxiliaryLimitReached = true;
                        return undefined;
                    }
                    auxiliaryBytes += size;
                }
            }
            return baseHost.getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
        },
    };
    const program = ts.createProgram({ rootNames, options: project.options, host });
    const checker = program.getTypeChecker();
    if ((options.mode ?? 'preferred') === 'required') {
        if (auxiliaryLimitReached) {
            throw new Error(`TypeScript checker auxiliary-source budget of ${maxAuxiliaryBytes} bytes was reached in required mode.`);
        }
        for (const file of group.files) {
            const compilerName = sources.get(normalizedFileName(file.path))?.fileName ?? file.path;
            if (!program.getSourceFile(compilerName)) {
                throw new Error(`TypeScript checker did not retain required root ${file.relativePath}.`);
            }
        }
    }
    resolveImports(ts, group, project.options, indexedPaths);
    for (const file of group.files) {
        abortIfNeeded(options.signal);
        const compilerName = sources.get(normalizedFileName(file.path))?.fileName ?? file.path;
        const sourceFile = program.getSourceFile(compilerName);
        if (!sourceFile) {
            setResolverState(file, 'syntax', 'partial');
            addResolverDiagnostic(file, {
                message: 'TypeScript checker did not retain this source file; syntax-only resolution was used.',
                severity: 'warning',
                source: 'typescript',
            });
            continue;
        }
        for (const diagnostic of diagnostics)
            addResolverDiagnostic(file, diagnostic);
        if (auxiliaryLimitReached) {
            addResolverDiagnostic(file, {
                message: `TypeScript checker auxiliary-source budget of ${maxAuxiliaryBytes} bytes was reached.`,
                severity: 'warning',
                source: 'typescript',
            });
        }
        const joinCounts = enrichCalls(ts, checker, sourceFile, file, filesByPath, options.signal);
        enrichInheritance(ts, checker, sourceFile, file, filesByPath, options.signal);
        const joinMissed = joinCounts.misses > 0;
        setResolverState(file, 'typescript-checker', auxiliaryLimitReached || projectDegraded || joinMissed ? 'partial' : 'full');
        if (joinMissed) {
            addResolverDiagnostic(file, {
                message: `TypeScript checker joined ${joinCounts.hits} call ${joinCounts.hits === 1 ? 'fact' : 'facts'}; ` +
                    `${joinCounts.misses} compiler call ${joinCounts.misses === 1 ? 'had' : 'calls had'} no parser fact.`,
                severity: 'warning',
                source: 'typescript',
                reason: RESOLVER_JOIN_MISS_REASON,
                counts: joinCounts,
            });
        }
    }
}
/**
 * Add checker-backed targets to already extracted TypeScript/JavaScript facts.
 *
 * The operation mutates only the supplied plain parse records. It is run after
 * all files have been parsed so aliases, receiver types, and project exports
 * can be proved across module boundaries before graph assembly.
 */
export async function enrichTypeScriptSemantics(files, options = {}) {
    const mode = options.mode ?? 'preferred';
    const family = files.filter(isTypeScriptFamily);
    if (family.length === 0)
        return;
    for (const file of family) {
        for (const call of file.calls)
            delete call.semanticResolution;
        for (const inheritance of file.inheritances)
            delete inheritance.semanticResolution;
        for (const parsedImport of file.imports) {
            if (path.extname(parsedImport.resolvedPath ?? '').toLowerCase() !== '.vue') {
                delete parsedImport.resolvedPath;
            }
            parsedImport.kind =
                parsedImport.specifier.startsWith('.') || parsedImport.specifier.startsWith('/') ? 'unresolved' : 'external';
        }
        for (const parsedExport of file.exports ?? [])
            delete parsedExport.resolvedPath;
        if (file.provenance) {
            file.provenance.resolver = { tier: 'syntax', coverage: 'partial' };
        }
    }
    if (mode === 'disabled') {
        // Disabling the checker must not fall back to OCC's legacy root-only JSON
        // path matcher. Module resolution itself is syntactic and TypeScript's
        // resolver understands JSONC, extends, nearest project configs, package
        // imports/exports, and the configured module-resolution strategy without
        // constructing a Program or TypeChecker.
        try {
            const ts = await loadTypeScript();
            const groups = groupProjects(ts, family);
            const indexedPaths = new Set(files.map(file => normalizedFileName(file.path)));
            for (const group of groups) {
                abortIfNeeded(options.signal);
                resolveImports(ts, group, projectOptions(ts, group).options, indexedPaths);
            }
        }
        catch (error) {
            if (options.signal?.aborted)
                throw error;
            // Disabled mode promises syntax-only best effort and deliberately emits
            // no checker diagnostics. The parser's already-recorded import result is
            // retained when the TypeScript resolver cannot be loaded or configured.
        }
        for (const file of family)
            setResolverState(file, 'syntax', 'partial');
        return;
    }
    try {
        const ts = await loadTypeScript();
        const groups = groupProjects(ts, family);
        const sources = compilerSources(ts, family);
        const maxProjects = options.maxProjects ?? DEFAULT_MAX_PROJECTS;
        if (groups.length > maxProjects) {
            throw new Error(`TypeScript checker project limit exceeded (${groups.length}/${maxProjects}).`);
        }
        for (const group of groups) {
            abortIfNeeded(options.signal);
            await enrichProject(ts, group, files, options, sources);
        }
    }
    catch (error) {
        if (options.signal?.aborted)
            throw error;
        if (mode === 'required')
            throw error;
        const message = error instanceof Error ? error.message : String(error);
        for (const file of family) {
            setResolverState(file, 'syntax', 'partial');
            addResolverDiagnostic(file, {
                message: `TypeScript checker unavailable; syntax-only resolution was used. ${message}`,
                severity: 'warning',
                source: 'typescript',
                reason: TYPESCRIPT_CHECKER_UNAVAILABLE_REASON,
            });
        }
    }
}
export function semanticCallResolution(call) {
    return call.semanticResolution;
}
export function semanticInheritanceResolution(inheritance) {
    return inheritance.semanticResolution;
}
//# sourceMappingURL=typescript-semantics.js.map