import { ReflectionSymbolId } from "#models";
import { findPackageForPath, getCommonDirectory, getQualifiedName, normalizePath, readFile } from "#node-utils";
import { Validation } from "#utils";
import { existsSync } from "fs";
import { join, relative, resolve } from "node:path";
import ts from "typescript";
const declarationMapCache = new Map();
let transientCount = 0;
const transientIds = new WeakMap();
export function createSymbolId(symbol, declaration) {
    declaration ??= symbol.declarations?.[0];
    const tsSource = declaration?.getSourceFile().fileName ?? "";
    const sourceFileName = resolveDeclarationMaps(tsSource);
    let packageName;
    let packagePath;
    const packageInfo = findPackageForPath(tsSource);
    if (packageInfo) {
        let packageDir;
        [packageName, packageDir] = packageInfo;
        packagePath = normalizePath(relative(packageDir, sourceFileName));
    }
    else {
        packageName = ReflectionSymbolId.UNKNOWN_PACKAGE;
        packagePath = normalizePath(sourceFileName);
    }
    let qualifiedName;
    if (symbol.declarations?.some(ts.isSourceFile)) {
        qualifiedName = "";
    }
    else {
        qualifiedName = getQualifiedName(symbol, symbol.name);
    }
    const pos = declaration?.getStart() ?? Infinity;
    let transientId = NaN;
    if (symbol.flags & ts.SymbolFlags.Transient) {
        transientId = transientIds.get(symbol) ?? ++transientCount;
        transientIds.set(symbol, transientId);
    }
    const id = new ReflectionSymbolId({
        packageName,
        packagePath,
        qualifiedName,
    });
    id.pos = pos;
    id.transientId = transientId;
    id.fileName = normalizePath(sourceFileName);
    return id;
}
function resolveDeclarationMaps(file) {
    if (!/\.d\.[cm]?ts$/.test(file))
        return file;
    if (declarationMapCache.has(file))
        return declarationMapCache.get(file);
    const mapFile = file + ".map";
    if (!existsSync(mapFile))
        return file;
    let sourceMap;
    try {
        sourceMap = JSON.parse(readFile(mapFile));
    }
    catch {
        return file;
    }
    if (Validation.validate({
        file: String,
        sourceRoot: Validation.optional(String),
        sources: [Array, String],
    }, sourceMap)) {
        // There's a pretty large assumption in here that we only have
        // 1 source file per js file. This is a pretty standard typescript approach,
        // but people might do interesting things with transpilation that could break this.
        let source = sourceMap.sources[0];
        // If we have a sourceRoot, trim any leading slash from the source, and join them
        // Similar to how it's done at https://github.com/mozilla/source-map/blob/58819f09018d56ef84dc41ba9c93f554e0645169/lib/util.js#L412
        if (sourceMap.sourceRoot !== undefined) {
            source = source.replace(/^\//, "");
            source = join(sourceMap.sourceRoot, source);
        }
        const result = resolve(mapFile, "..", source);
        declarationMapCache.set(file, result);
        return result;
    }
    return file;
}
// See also: inferEntryPoints in entry-point.ts
export function addInferredDeclarationMapPaths(opts, files) {
    const rootDir = opts.rootDir || getCommonDirectory(files);
    const declDir = opts.declarationDir || opts.outDir || rootDir;
    for (const file of files) {
        const mapFile = normalizePath(resolve(declDir, relative(rootDir, file)).replace(/\.([cm]?[tj]s)x?$/, ".d.$1"));
        declarationMapCache.set(mapFile, file);
    }
}
