"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
function _assert() {
  const data = _interopRequireDefault(require("assert"));
  _assert = function () {
    return data;
  };
  return data;
}
function _stream() {
  const data = require("stream");
  _stream = function () {
    return data;
  };
  return data;
}
function _sourceMap() {
  const data = _interopRequireDefault(require("@parcel/source-map"));
  _sourceMap = function () {
    return data;
  };
  return data;
}
function _utils() {
  const data = require("@parcel/utils");
  _utils = function () {
    return data;
  };
  return data;
}
function _rust() {
  const data = require("@parcel/rust");
  _rust = function () {
    return data;
  };
  return data;
}
var _serializer = require("./serializer");
var _Dependency = require("./Dependency");
var _Environment = require("./Environment");
var _constants = require("./constants");
var _assetUtils = require("./assetUtils");
var _types = require("./types");
var _utils2 = require("./utils");
var _projectPath = require("./projectPath");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
class UncommittedAsset {
  constructor({
    value,
    options,
    content,
    mapBuffer,
    ast,
    isASTDirty,
    idBase,
    invalidations
  }) {
    this.value = value;
    this.options = options;
    this.content = content;
    this.mapBuffer = mapBuffer;
    this.ast = ast;
    this.isASTDirty = isASTDirty || false;
    this.idBase = idBase;
    this.invalidations = invalidations || (0, _utils2.createInvalidations)();
    this.transformers = new Set();
  }

  /*
   * Prepares the asset for being serialized to the cache by committing its
   * content and map of the asset to the cache.
   */
  async commit() {
    // If there is a dirty AST, clear out any old content and map as these
    // must be regenerated later and shouldn't be committed.
    if (this.ast != null && this.isASTDirty) {
      this.content = null;
      this.mapBuffer = null;
    }
    let size = 0;
    let outputHash = '';
    let contentKey = this.content == null ? null : this.getCacheKey('content');
    let mapKey = this.mapBuffer == null ? null : this.getCacheKey('map');
    let astKey = this.ast == null ? null : this.getCacheKey('ast');

    // Since we can only read from the stream once, compute the content length
    // and hash while it's being written to the cache.
    await Promise.all([contentKey != null && this.commitContent(contentKey).then(s => (size = s.size, outputHash = s.hash)), this.mapBuffer != null && mapKey != null && this.options.cache.setBlob(mapKey, this.mapBuffer), astKey != null && this.options.cache.setBlob(astKey, (0, _serializer.serializeRaw)(this.ast))]);
    this.value.contentKey = contentKey;
    this.value.mapKey = mapKey;
    this.value.astKey = astKey;
    this.value.outputHash = outputHash;
    if (this.content != null) {
      this.value.stats.size = size;
    }
    this.value.isLargeBlob = this.content instanceof _stream().Readable;
    this.value.committed = true;
  }
  async commitContent(contentKey) {
    let content = await this.content;
    if (content == null) {
      return {
        size: 0,
        hash: ''
      };
    }
    let size = 0;
    if (content instanceof _stream().Readable) {
      let hash = new (_rust().Hash)();
      await this.options.cache.setStream(contentKey, content.pipe(new (_utils().TapStream)(buf => {
        hash.writeBuffer(buf);
        size += buf.length;
      })));
      return {
        size,
        hash: hash.finish()
      };
    }
    let hash;
    if (typeof content === 'string') {
      hash = (0, _rust().hashString)(content);
      size = Buffer.byteLength(content);
    } else {
      hash = (0, _rust().hashBuffer)(content);
      size = content.length;
    }
    await this.options.cache.setBlob(contentKey, content);
    return {
      size,
      hash
    };
  }
  async getCode() {
    if (this.ast != null && this.isASTDirty) {
      throw new Error('Cannot call getCode() on an asset with a dirty AST. For transformers, implement canReuseAST() and check asset.isASTDirty.');
    }
    let content = await this.content;
    if (typeof content === 'string' || content instanceof Buffer) {
      return content.toString();
    } else if (content != null) {
      this.content = (0, _utils().bufferStream)(content);
      return (await this.content).toString();
    }
    (0, _assert().default)(false, 'Internal error: missing content');
  }
  async getBuffer() {
    let content = await this.content;
    if (content == null) {
      return Buffer.alloc(0);
    } else if (content instanceof Buffer) {
      return content;
    } else if (typeof content === 'string') {
      return Buffer.from(content);
    }
    this.content = (0, _utils().bufferStream)(content);
    return this.content;
  }
  getStream() {
    if (this.content instanceof _stream().Readable) {
      // Remove content if it's a stream, as it should not be reused.
      let content = this.content;
      this.content = null;
      return content;
    }
    if (this.content instanceof Promise) {
      return (0, _utils().streamFromPromise)(this.content);
    }
    return (0, _utils().blobToStream)(this.content ?? Buffer.alloc(0));
  }
  setCode(code) {
    this.content = code;
    this.clearAST();
  }
  setBuffer(buffer) {
    this.content = buffer;
    this.clearAST();
  }
  setStream(stream) {
    this.content = stream;
    this.clearAST();
  }
  async loadExistingSourcemap() {
    if (this.map) {
      return this.map;
    }
    let code = await this.getCode();
    let map = await (0, _utils().loadSourceMap)((0, _projectPath.fromProjectPath)(this.options.projectRoot, this.value.filePath), code, {
      fs: this.options.inputFS,
      projectRoot: this.options.projectRoot
    });
    if (map) {
      this.map = map;
      this.mapBuffer = map.toBuffer();
      this.setCode(code.replace(_utils().SOURCEMAP_RE, ''));
    }
    return this.map;
  }
  getMapBuffer() {
    return Promise.resolve(this.mapBuffer);
  }
  async getMap() {
    if (this.map == null) {
      let mapBuffer = this.mapBuffer ?? (await this.getMapBuffer());
      if (mapBuffer) {
        // Get sourcemap from flatbuffer
        this.map = new (_sourceMap().default)(this.options.projectRoot, mapBuffer);
      }
    }
    return this.map;
  }
  setMap(map) {
    var _this$map;
    // If we have sourceContent available, it means this asset is source code without
    // a previous source map. Ensure that the map set by the transformer has the original
    // source content available.
    if (map != null && this.sourceContent != null) {
      map.setSourceContent((0, _projectPath.fromProjectPath)(this.options.projectRoot, this.value.filePath),
      // $FlowFixMe
      this.sourceContent);
      this.sourceContent = null;
    }
    this.map = map;
    this.mapBuffer = (_this$map = this.map) === null || _this$map === void 0 ? void 0 : _this$map.toBuffer();
  }
  getAST() {
    return Promise.resolve(this.ast);
  }
  setAST(ast) {
    this.ast = ast;
    this.isASTDirty = true;
    this.value.astGenerator = {
      type: ast.type,
      version: ast.version
    };
  }
  clearAST() {
    this.ast = null;
    this.isASTDirty = false;
    this.value.astGenerator = null;
  }
  getCacheKey(key) {
    return (0, _rust().hashString)(_constants.PARCEL_VERSION + key + this.value.id);
  }
  addDependency(opts) {
    // eslint-disable-next-line no-unused-vars
    let {
      env,
      symbols,
      ...rest
    } = opts;
    let dep = (0, _Dependency.createDependency)(this.options.projectRoot, {
      ...rest,
      // $FlowFixMe "convert" the $ReadOnlyMaps to the interal mutable one
      symbols,
      env: (0, _Environment.mergeEnvironments)(this.options.projectRoot, this.value.env, env),
      sourceAssetId: this.value.id,
      sourcePath: (0, _projectPath.fromProjectPath)(this.options.projectRoot, this.value.filePath)
    });
    let existing = this.value.dependencies.get(dep.id);
    if (existing) {
      (0, _Dependency.mergeDependencies)(existing, dep);
    } else {
      this.value.dependencies.set(dep.id, dep);
    }
    return dep.id;
  }
  invalidateOnFileChange(filePath) {
    this.invalidations.invalidateOnFileChange.add(filePath);
  }
  invalidateOnFileCreate(invalidation) {
    this.invalidations.invalidateOnFileCreate.push((0, _utils2.invalidateOnFileCreateToInternal)(this.options.projectRoot, invalidation));
  }
  invalidateOnEnvChange(key) {
    this.invalidations.invalidateOnEnvChange.add(key);
  }
  invalidateOnBuild() {
    this.invalidations.invalidateOnBuild = true;
  }
  invalidateOnStartup() {
    this.invalidations.invalidateOnStartup = true;
  }
  getDependencies() {
    return Array.from(this.value.dependencies.values());
  }
  createChildAsset(result, plugin, configPath, configKeyPath) {
    let content = result.content ?? null;
    let asset = new UncommittedAsset({
      value: (0, _assetUtils.createAsset)(this.options.projectRoot, {
        idBase: this.idBase,
        filePath: this.value.filePath,
        type: result.type,
        bundleBehavior: result.bundleBehavior === undefined ? this.value.bundleBehavior == null ? null : _types.BundleBehaviorNames[this.value.bundleBehavior] : result.bundleBehavior,
        isBundleSplittable: result.isBundleSplittable ?? this.value.isBundleSplittable,
        isSource: this.value.isSource,
        env: (0, _Environment.mergeEnvironments)(this.options.projectRoot, this.value.env, result.env),
        dependencies: this.value.type === result.type ? new Map(this.value.dependencies) : new Map(),
        meta: {
          ...this.value.meta,
          ...result.meta
        },
        pipeline: result.pipeline ?? (this.value.type === result.type ? this.value.pipeline : null),
        stats: {
          time: 0,
          size: this.value.stats.size
        },
        // $FlowFixMe
        symbols: result.symbols,
        sideEffects: result.sideEffects ?? this.value.sideEffects,
        uniqueKey: result.uniqueKey,
        astGenerator: result.ast ? {
          type: result.ast.type,
          version: result.ast.version
        } : null,
        plugin,
        configPath,
        configKeyPath
      }),
      options: this.options,
      content,
      ast: result.ast,
      isASTDirty: result.ast === this.ast ? this.isASTDirty : true,
      mapBuffer: result.map ? result.map.toBuffer() : null,
      idBase: this.idBase,
      invalidations: this.invalidations
    });
    let dependencies = result.dependencies;
    if (dependencies) {
      for (let dep of dependencies) {
        asset.addDependency(dep);
      }
    }
    return asset;
  }
  updateId() {
    // $FlowFixMe - this is fine
    this.value.id = (0, _assetUtils.createAssetIdFromOptions)(this.value);
  }
}
exports.default = UncommittedAsset;