import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { dirname, join, posix } from "node:path";
import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping";
const COMMON_BUILD_DIRS = ["dist", "build", ".next", "out", "public", ".vite"];
const MAX_DEPTH = 4;
export class SourceMapResolver {
    projectDir;
    cache = new Map();
    constructor(projectDir) {
        this.projectDir = projectDir;
    }
    /**
     * Resolve a minified stack into the original source. Frames that can't
     * be resolved are returned unchanged so the caller still sees something.
     */
    resolveStack(stack) {
        return stack.map((f) => this.resolveFrame(f));
    }
    resolveFrame(frame) {
        const minifiedName = filenameFromUrl(frame.file);
        if (!minifiedName)
            return frame;
        const entry = this.findMap(minifiedName);
        if (!entry)
            return frame;
        let resolved;
        try {
            resolved = originalPositionFor(entry.trace, { line: frame.line, column: frame.col });
        }
        catch {
            return frame;
        }
        if (!resolved.source)
            return frame;
        const sourceAbs = posix.normalize(posix.join(entry.mapDir, resolved.source));
        return {
            file: sourceAbs,
            line: resolved.line ?? frame.line,
            col: resolved.column ?? frame.col,
            fn: resolved.name || frame.fn,
            in_app: !sourceAbs.includes("/node_modules/") && !sourceAbs.startsWith("webpack:///"),
        };
    }
    findMap(minifiedName) {
        const cached = this.cache.get(minifiedName);
        if (cached !== undefined)
            return cached;
        const result = this.searchForMap(minifiedName);
        this.cache.set(minifiedName, result);
        return result;
    }
    searchForMap(minifiedName) {
        for (const dir of COMMON_BUILD_DIRS) {
            const root = join(this.projectDir, dir);
            if (!existsSync(root))
                continue;
            const found = walkForMap(root, minifiedName, MAX_DEPTH);
            if (found)
                return loadMap(found);
        }
        const rootCandidate = join(this.projectDir, minifiedName + ".map");
        if (existsSync(rootCandidate))
            return loadMap(rootCandidate);
        return null;
    }
}
function filenameFromUrl(file) {
    if (!file)
        return null;
    const cleaned = file.split("?")[0].split("#")[0];
    const last = cleaned.split("/").pop();
    return last || null;
}
function walkForMap(root, minifiedName, depth) {
    const target = minifiedName + ".map";
    const direct = join(root, target);
    if (existsSync(direct))
        return direct;
    if (depth <= 0)
        return null;
    let entries;
    try {
        entries = readdirSync(root);
    }
    catch {
        return null;
    }
    for (const entry of entries) {
        if (entry.startsWith("."))
            continue;
        const child = join(root, entry);
        if (!isDirectorySafe(child))
            continue;
        const found = walkForMap(child, minifiedName, depth - 1);
        if (found)
            return found;
    }
    return null;
}
function isDirectorySafe(path) {
    try {
        return statSync(path).isDirectory();
    }
    catch {
        return false;
    }
}
function loadMap(mapPath) {
    try {
        const raw = readFileSync(mapPath, "utf8");
        const parsed = JSON.parse(raw);
        return { trace: new TraceMap(parsed), mapDir: dirname(mapPath) };
    }
    catch {
        return null;
    }
}
/**
 * Best-effort wrapper that swallows all errors. The runner calls this on
 * every error fetch — if anything goes wrong, we don't want to break the
 * whole TUI.
 */
export function tryResolveStack(stack, projectDir) {
    try {
        const resolver = new SourceMapResolver(projectDir);
        return resolver.resolveStack(stack);
    }
    catch {
        return stack;
    }
}
//# sourceMappingURL=source-maps.js.map