import sourceMap from "source-map";

/**
 * Build a sourcemap.
 */

export default class SourceMap {
  private _cachedMap: sourceMap.SourceMapGenerator | null;
  private _code: any;
  private _opts: any;
  private _rawMappings: any[];
  private _lastGenLine: number;
  private _lastSourceLine: number;
  private _lastSourceColumn: number;
  constructor(opts, code) {
    this._cachedMap = null;
    this._code = code;
    this._opts = opts;
    this._rawMappings = [];
  }

  /**
   * Get the sourcemap.
   */

  get() {
    if (!this._cachedMap) {
      const map = (this._cachedMap = new sourceMap.SourceMapGenerator({
        sourceRoot: this._opts.sourceRoot,
      }));

      const code = this._code;
      if (typeof code === "string") {
        map.setSourceContent(
          this._opts.sourceFileName.replace(/\\/g, "/"),
          code,
        );
      } else if (typeof code === "object") {
        Object.keys(code).forEach(sourceFileName => {
          map.setSourceContent(
            sourceFileName.replace(/\\/g, "/"),
            code[sourceFileName],
          );
        });
      }

      this._rawMappings.forEach(mapping => map.addMapping(mapping), map);
    }

    return this._cachedMap.toJSON();
  }

  getRawMappings() {
    return this._rawMappings.slice();
  }

  /**
   * Mark the current generated position with a source position. May also be passed null line/column
   * values to insert a mapping to nothing.
   */

  mark(
    generatedLine: number,
    generatedColumn: number,
    line: number,
    column: number,
    identifierName?: string | null,
    filename?: string | null,
    force?: boolean,
  ) {
    // Adding an empty mapping at the start of a generated line just clutters the map.
    if (this._lastGenLine !== generatedLine && line === null) return;

    // If this mapping points to the same source location as the last one, we can ignore it since
    // the previous one covers it.
    if (
      !force &&
      this._lastGenLine === generatedLine &&
      this._lastSourceLine === line &&
      this._lastSourceColumn === column
    ) {
      return;
    }

    this._cachedMap = null;
    this._lastGenLine = generatedLine;
    this._lastSourceLine = line;
    this._lastSourceColumn = column;

    // We are deliberately not using the `source-map` library here to allow
    // callers to use these mappings without any overhead
    this._rawMappings.push({
      // undefined to allow for more compact json serialization
      name: identifierName || undefined,
      generated: {
        line: generatedLine,
        column: generatedColumn,
      },
      source:
        line == null
          ? undefined
          : (filename || this._opts.sourceFileName).replace(/\\/g, "/"),
      original:
        line == null
          ? undefined
          : {
              line: line,
              column: column,
            },
    });
  }
}
