"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true,
});
exports.default = void 0;
var common = _interopRequireWildcard(require("./common"));
var _RecrawlWarning = _interopRequireDefault(require("./RecrawlWarning"));
var _assert = _interopRequireDefault(require("assert"));
var _crypto = require("crypto");
var _events = _interopRequireDefault(require("events"));
var _fbWatchman = _interopRequireDefault(require("fb-watchman"));
var _invariant = _interopRequireDefault(require("invariant"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}
function _getRequireWildcardCache(nodeInterop) {
  if (typeof WeakMap !== "function") return null;
  var cacheBabelInterop = new WeakMap();
  var cacheNodeInterop = new WeakMap();
  return (_getRequireWildcardCache = function (nodeInterop) {
    return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  })(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
  if (!nodeInterop && obj && obj.__esModule) {
    return obj;
  }
  if (obj === null || (typeof obj !== "object" && typeof obj !== "function")) {
    return { default: obj };
  }
  var cache = _getRequireWildcardCache(nodeInterop);
  if (cache && cache.has(obj)) {
    return cache.get(obj);
  }
  var newObj = {};
  var hasPropertyDescriptor =
    Object.defineProperty && Object.getOwnPropertyDescriptor;
  for (var key in obj) {
    if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
      var desc = hasPropertyDescriptor
        ? Object.getOwnPropertyDescriptor(obj, key)
        : null;
      if (desc && (desc.get || desc.set)) {
        Object.defineProperty(newObj, key, desc);
      } else {
        newObj[key] = obj[key];
      }
    }
  }
  newObj.default = obj;
  if (cache) {
    cache.set(obj, newObj);
  }
  return newObj;
}
const debug = require("debug")("Metro:WatchmanWatcher");
const CHANGE_EVENT = common.CHANGE_EVENT;
const DELETE_EVENT = common.DELETE_EVENT;
const ADD_EVENT = common.ADD_EVENT;
const ALL_EVENT = common.ALL_EVENT;
const SUB_PREFIX = "metro-file-map";
class WatchmanWatcher extends _events.default {
  #deferringStates = null;
  constructor(dir, opts) {
    super();
    common.assignOptions(this, opts);
    this.root = _path.default.resolve(dir);
    const watchKey = (0, _crypto.createHash)("md5")
      .update(this.root)
      .digest("hex");
    const readablePath = this.root
      .replace(/[\/\\]/g, "-")
      .replace(/[^\-\w]/g, "");
    this.subscriptionName = `${SUB_PREFIX}-${process.pid}-${readablePath}-${watchKey}`;
    this._init();
  }
  _init() {
    if (this.client) {
      this.client.removeAllListeners();
    }
    const self = this;
    this.client = new _fbWatchman.default.Client();
    this.client.on("error", (error) => {
      self.emit("error", error);
    });
    this.client.on("subscription", (changeEvent) =>
      this._handleChangeEvent(changeEvent)
    );
    this.client.on("end", () => {
      console.warn(
        "[metro-file-map] Warning: Lost connection to Watchman, reconnecting.."
      );
      self._init();
    });
    this.watchProjectInfo = null;
    function getWatchRoot() {
      return self.watchProjectInfo ? self.watchProjectInfo.root : self.root;
    }
    function onWatchProject(error, resp) {
      if (handleError(self, error)) {
        return;
      }
      debug("Received watch-project response: %s", resp.relative_path);
      handleWarning(resp);
      self.watchProjectInfo = {
        relativePath: resp.relative_path ? resp.relative_path : "",
        root: resp.watch,
      };
      self.client.command(["clock", getWatchRoot()], onClock);
    }
    function onClock(error, resp) {
      if (handleError(self, error)) {
        return;
      }
      debug("Received clock response: %s", resp.clock);
      const watchProjectInfo = self.watchProjectInfo;
      (0, _invariant.default)(
        watchProjectInfo != null,
        "watch-project response should have been set before clock response"
      );
      handleWarning(resp);
      const options = {
        fields: ["name", "exists", "new", "type", "size", "mtime_ms"],
        since: resp.clock,
        defer: self.watchmanDeferStates,
        relative_root: watchProjectInfo.relativePath,
      };
      if (self.globs.length === 0 && !self.dot) {
        options.expression = [
          "match",
          "**",
          "wholename",
          {
            includedotfiles: false,
          },
        ];
      }
      self.client.command(
        ["subscribe", getWatchRoot(), self.subscriptionName, options],
        onSubscribe
      );
    }
    const onSubscribe = (error, resp) => {
      if (handleError(self, error)) {
        return;
      }
      debug("Received subscribe response: %s", resp.subscribe);
      handleWarning(resp);
      if (resp["asserted-states"] != null) {
        this.#deferringStates = new Set(resp["asserted-states"]);
      }
      self.emit("ready");
    };
    self.client.command(["watch-project", getWatchRoot()], onWatchProject);
  }
  _handleChangeEvent(resp) {
    debug(
      "Received subscription response: %s (fresh: %s, files: %s, enter: %s, leave: %s)",
      resp.subscription,
      resp.is_fresh_instance,
      resp.files?.length,
      resp["state-enter"],
      resp["state-leave"]
    );
    _assert.default.equal(
      resp.subscription,
      this.subscriptionName,
      "Invalid subscription event."
    );
    if (resp.is_fresh_instance) {
      this.emit("fresh_instance");
    }
    if (resp.is_fresh_instance) {
      this.emit("fresh_instance");
    }
    if (Array.isArray(resp.files)) {
      resp.files.forEach((change) => this._handleFileChange(change));
    }
    const { "state-enter": stateEnter, "state-leave": stateLeave } = resp;
    if (
      stateEnter != null &&
      (this.watchmanDeferStates ?? []).includes(stateEnter)
    ) {
      this.#deferringStates?.add(stateEnter);
      debug(
        'Watchman reports "%s" just started. Filesystem notifications are paused.',
        stateEnter
      );
    }
    if (
      stateLeave != null &&
      (this.watchmanDeferStates ?? []).includes(stateLeave)
    ) {
      this.#deferringStates?.delete(stateLeave);
      debug(
        'Watchman reports "%s" ended. Filesystem notifications resumed.',
        stateLeave
      );
    }
  }
  _handleFileChange(changeDescriptor) {
    const self = this;
    const watchProjectInfo = self.watchProjectInfo;
    (0, _invariant.default)(
      watchProjectInfo != null,
      "watch-project response should have been set before receiving subscription events"
    );
    const {
      name: relativePath,
      new: isNew = false,
      exists = false,
      type,
      mtime_ms,
      size,
    } = changeDescriptor;
    debug(
      "Handling change to: %s (new: %s, exists: %s, type: %s)",
      relativePath,
      isNew,
      exists,
      type
    );
    if (type != null && !(type === "f" || type === "d" || type === "l")) {
      return;
    }
    if (
      !common.isIncluded(
        type,
        this.globs,
        this.dot,
        this.doIgnore,
        relativePath
      )
    ) {
      return;
    }
    if (!exists) {
      self._emitEvent(DELETE_EVENT, relativePath, self.root);
    } else {
      const eventType = isNew ? ADD_EVENT : CHANGE_EVENT;
      (0, _invariant.default)(
        type != null && mtime_ms != null && size != null,
        'Watchman file change event for "%s" missing some requested metadata. ' +
          "Got type: %s, mtime_ms: %s, size: %s",
        relativePath,
        type,
        mtime_ms,
        size
      );
      if (!(type === "d" && eventType === CHANGE_EVENT)) {
        const mtime = Number(mtime_ms);
        self._emitEvent(eventType, relativePath, self.root, {
          modifiedTime: mtime !== 0 ? mtime : null,
          size,
          type,
        });
      }
    }
  }
  _emitEvent(eventType, filepath, root, changeMetadata) {
    this.emit(eventType, filepath, root, changeMetadata);
    this.emit(ALL_EVENT, eventType, filepath, root, changeMetadata);
  }
  async close() {
    this.client.removeAllListeners();
    this.client.end();
    this.#deferringStates = null;
  }
  getPauseReason() {
    if (this.#deferringStates == null || this.#deferringStates.size === 0) {
      return null;
    }
    const states = [...this.#deferringStates];
    if (states.length === 1) {
      return `The watch is in the '${states[0]}' state.`;
    }
    return `The watch is in the ${states
      .slice(0, -1)
      .map((s) => `'${s}'`)
      .join(", ")} and '${states[states.length - 1]}' states.`;
  }
}
exports.default = WatchmanWatcher;
function handleError(emitter, error) {
  if (error != null) {
    emitter.emit("error", error);
    return true;
  } else {
    return false;
  }
}
function handleWarning(resp) {
  if ("warning" in resp) {
    if (_RecrawlWarning.default.isRecrawlWarningDupe(resp.warning)) {
      return true;
    }
    console.warn(resp.warning);
    return true;
  } else {
    return false;
  }
}
