import TraceMap from '@jridgewell/trace-mapping';
import { encode } from 'sourcemap-codec';

/**
 * A "leaf" node in the sourcemap tree, representing an original, unmodified
 * source file. Recursive segment tracing ends at the `OriginalSource`.
 */
class OriginalSource {
    constructor(filename, content) {
        this.filename = filename;
        this.content = content;
    }
    /**
     * Tracing a `SourceMapSegment` ends when we get to an `OriginalSource`,
     * meaning this line/column location originated from this source file.
     */
    originalPositionFor(line, column, name) {
        return { column, line, name, source: this };
    }
}

/**
 * FastStringArray acts like a `Set` (allowing only one occurrence of a string
 * `key`), but provides the index of the `key` in the backing array.
 *
 * This is designed to allow synchronizing a second array with the contents of
 * the backing array, like how `sourcesContent[i]` is the source content
 * associated with `source[i]`, and there are never duplicates.
 */
class FastStringArray {
    constructor() {
        this.indexes = Object.create(null);
        this.array = [];
    }
    /**
     * Puts `key` into the backing array, if it is not already present. Returns
     * the index of the `key` in the backing array.
     */
    put(key) {
        const { array, indexes } = this;
        // The key may or may not be present. If it is present, it's a number.
        let index = indexes[key];
        // If it's not yet present, we need to insert it and track the index in the
        // indexes.
        if (index === undefined) {
            index = indexes[key] = array.length;
            array.push(key);
        }
        return index;
    }
}

const INVALID_MAPPING = undefined;
const SOURCELESS_MAPPING = null;
/**
 * SourceMapTree represents a single sourcemap, with the ability to trace
 * mappings into its child nodes (which may themselves be SourceMapTrees).
 */
class SourceMapTree {
    constructor(map, sources) {
        this.map = map;
        this.sources = sources;
    }
    /**
     * traceMappings is only called on the root level SourceMapTree, and begins the process of
     * resolving each mapping in terms of the original source files.
     */
    traceMappings() {
        const names = new FastStringArray();
        const sources = new FastStringArray();
        const sourcesContent = [];
        const { sources: rootSources, map } = this;
        const rootNames = map.names;
        let lastGenLine = -1;
        let lastSourcesIndex = -1;
        let lastSourceLine = -1;
        let lastSourceColumn = -1;
        const mappings = map.map(trace);
        if (mappings.length > lastGenLine + 1) {
            mappings.length = lastGenLine + 1;
        }
        // TODO: Make all sources relative to the sourceRoot.
        return Object.assign({}, this.map, {
            mappings,
            names: names.array,
            sources: sources.array,
            sourcesContent,
        });
        function trace(genLine, genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex) {
            let traced = SOURCELESS_MAPPING;
            // 1-length segments only move the current generated column, there's no source information
            // to gather from it.
            if (sourcesIndex !== -1) {
                const source = rootSources[sourcesIndex];
                traced = source.originalPositionFor(sourceLine, sourceColumn, namesIndex === -1 ? '' : rootNames[namesIndex]);
                // If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
                // respective segment into an original source.
                if (traced === INVALID_MAPPING)
                    return null;
            }
            if (traced === SOURCELESS_MAPPING) {
                if (lastSourcesIndex === -1) {
                    // This is a consecutive source-less segment, which doesn't carry any new information.
                    return null;
                }
                lastSourcesIndex = lastSourceLine = lastSourceColumn = -1;
                return [genColumn];
            }
            // So we traced a segment down into its original source file. Now push a
            // new segment pointing to this location.
            const { column, line, name } = traced;
            const { content, filename } = traced.source;
            // Store the source location, and ensure we keep sourcesContent up to
            // date with the sources array.
            const sIndex = sources.put(filename);
            sourcesContent[sIndex] = content;
            if (lastGenLine === genLine &&
                lastSourcesIndex === sIndex &&
                lastSourceLine === line &&
                lastSourceColumn === column) {
                // This is a duplicate mapping pointing at the exact same starting point in the source
                // file. It doesn't carry any new information, and only bloats the sourcemap.
                return null;
            }
            lastGenLine = genLine;
            lastSourcesIndex = sIndex;
            lastSourceLine = line;
            lastSourceColumn = column;
            // This looks like unnecessary duplication, but it noticeably increases performance. If we
            // were to push the nameIndex onto length-4 array, v8 would internally allocate 22 slots!
            // That's 68 wasted bytes! Array literals have the same capacity as their length, saving
            // memory.
            if (name)
                return [genColumn, sIndex, line, column, names.put(name)];
            return [genColumn, sIndex, line, column];
        }
    }
    /**
     * traceSegment is only called on children SourceMapTrees. It recurses down
     * into its own child SourceMapTrees, until we find the original source map.
     */
    originalPositionFor(line, column, name) {
        const segment = this.map.traceSegment(line, column);
        // If we couldn't find a segment, then this doesn't exist in the sourcemap.
        if (segment == null)
            return INVALID_MAPPING;
        // 1-length segments only move the current generated column, there's no source information
        // to gather from it.
        if (segment.length === 1)
            return SOURCELESS_MAPPING;
        const source = this.sources[segment[1]];
        return source.originalPositionFor(segment[2], segment[3], segment.length === 5 ? this.map.names[segment[4]] : name);
    }
}

function asArray(value) {
    if (Array.isArray(value))
        return value;
    return [value];
}
/**
 * Recursively builds a tree structure out of sourcemap files, with each node
 * being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
 * `OriginalSource`s and `SourceMapTree`s.
 *
 * Every sourcemap is composed of a collection of source files and mappings
 * into locations of those source files. When we generate a `SourceMapTree` for
 * the sourcemap, we attempt to load each source file's own sourcemap. If it
 * does not have an associated sourcemap, it is considered an original,
 * unmodified source file.
 */
function buildSourceMapTree(input, loader) {
    const maps = asArray(input).map((m) => new TraceMap(m));
    const map = maps.pop();
    for (let i = 0; i < maps.length; i++) {
        if (maps[i].sources.length > 1) {
            throw new Error(`Transformation map ${i} must have exactly one source file.\n` +
                'Did you specify these with the most recent transformation maps first?');
        }
    }
    let tree = build(map, loader);
    for (let i = maps.length - 1; i >= 0; i--) {
        tree = new SourceMapTree(maps[i], [tree]);
    }
    return tree;
}
function build(map, loader) {
    const { resolvedSources, sourcesContent } = map;
    const children = resolvedSources.map((sourceFile, i) => {
        const source = sourceFile || '';
        // Use the provided loader callback to retrieve the file's sourcemap.
        // TODO: We should eventually support async loading of sourcemap files.
        const sourceMap = loader(source);
        // If there is no sourcemap, then it is an unmodified source file.
        if (!sourceMap) {
            // The source file's actual contents must be included in the sourcemap
            // (done when generating the sourcemap) for it to be included as a
            // sourceContent in the output sourcemap.
            const sourceContent = sourcesContent ? sourcesContent[i] : null;
            return new OriginalSource(source, sourceContent);
        }
        // Else, it's a real sourcemap, and we need to recurse into it to load its
        // source files.
        return build(new TraceMap(sourceMap, source), loader);
    });
    return new SourceMapTree(map, children);
}

/**
 * A SourceMap v3 compatible sourcemap, which only includes fields that were
 * provided to it.
 */
class SourceMap {
    constructor(map, options) {
        this.version = 3; // SourceMap spec says this should be first.
        if ('file' in map)
            this.file = map.file;
        this.mappings = options.decodedMappings ? map.mappings : encode(map.mappings);
        this.names = map.names;
        // TODO: We first need to make all source URIs relative to the sourceRoot
        // before we can support a sourceRoot.
        // if ('sourceRoot' in map) this.sourceRoot = map.sourceRoot;
        this.sources = map.sources;
        if (!options.excludeContent && 'sourcesContent' in map) {
            this.sourcesContent = map.sourcesContent;
        }
    }
    toString() {
        return JSON.stringify(this);
    }
}

/**
 * Traces through all the mappings in the root sourcemap, through the sources
 * (and their sourcemaps), all the way back to the original source location.
 *
 * `loader` will be called every time we encounter a source file. If it returns
 * a sourcemap, we will recurse into that sourcemap to continue the trace. If
 * it returns a falsey value, that source file is treated as an original,
 * unmodified source file.
 *
 * Pass `excludeContent` to exclude any self-containing source file content
 * from the output sourcemap.
 *
 * Pass `decodedMappings` to receive a SourceMap with decoded (instead of
 * VLQ encoded) mappings.
 */
function remapping(input, loader, options) {
    const opts = typeof options === 'object' ? options : { excludeContent: !!options, decodedMappings: false };
    const graph = buildSourceMapTree(input, loader);
    return new SourceMap(graph.traceMappings(), opts);
}

export { remapping as default };
//# sourceMappingURL=remapping.mjs.map
