"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getNodeIdProvider = exports.NodeAnchorProvider = void 0;
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _expVal = require("@atlaskit/tmp-editor-statsig/expVal");
var _utils = require("../utils");
var _dynamicBitArray = require("./dynamic-bit-array");
/**
 * Gets a numeric experiment param, returning undefined if the value is not a valid number.
 * This guards against test overrides returning booleans or strings for numeric params.
 */
var getNumericExperimentParam = function getNumericExperimentParam(paramName, fallbackValue) {
  var rawValue = (0, _expVal.expVal)('cc_editor_limited_mode_expanded', paramName, fallbackValue);
  if (typeof rawValue === 'number') {
    return rawValue;
  }

  // Handle string values from test overrides
  if (typeof rawValue === 'string') {
    var parsed = parseInt(rawValue, 10);
    if (!isNaN(parsed)) {
      return parsed;
    }
  }
  return undefined;
};
var NodeAnchorProvider = exports.NodeAnchorProvider = /*#__PURE__*/function () {
  function NodeAnchorProvider() {
    var limitedMode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    var emptyDoc = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
    (0, _classCallCheck2.default)(this, NodeAnchorProvider);
    (0, _defineProperty2.default)(this, "cache", new WeakMap());
    (0, _defineProperty2.default)(this, "count", BigInt(0));
    (0, _defineProperty2.default)(this, "existingPos", new _dynamicBitArray.DynamicBitArray());
    (0, _defineProperty2.default)(this, "limitedMode", false);
    (0, _defineProperty2.default)(this, "emptyDoc", false);
    this.limitedMode = limitedMode;
    this.emptyDoc = emptyDoc;
  }
  return (0, _createClass2.default)(NodeAnchorProvider, [{
    key: "isEmptyDoc",
    value: function isEmptyDoc() {
      return this.emptyDoc;
    }
  }, {
    key: "setEmptyDoc",
    value: function setEmptyDoc(isEmpty) {
      this.emptyDoc = isEmpty;
    }
  }, {
    key: "isLimitedMode",
    value: function isLimitedMode() {
      return this.limitedMode;
    }

    // We use pos to generate unique ids for each node at a specific position
    // This is to ensure the same ADF will always generate the same DOM initially
  }, {
    key: "getOrGenerateId",
    value: function getOrGenerateId(node, pos) {
      if (this.limitedMode) {
        return undefined;
      }
      if (this.cache.has(node)) {
        return this.cache.get(node);
      }
      var idSuffix = '';
      if (this.existingPos.get(pos)) {
        idSuffix = "-".concat((this.count++).toString(36));
      } else {
        this.existingPos.set(pos, true);
      }
      var anchorName = "--anchor-".concat(node.type.name, "-").concat(pos).concat(idSuffix);
      this.cache.set(node, anchorName);
      return anchorName;
    }
  }, {
    key: "getIdForNode",
    value: function getIdForNode(node) {
      if (this.limitedMode) {
        return undefined;
      }
      return this.cache.get(node);
    }
  }, {
    key: "setIdForNode",
    value: function setIdForNode(node, id) {
      if (this.limitedMode) {
        return;
      }
      this.cache.set(node, id);
    }

    // After set to limited mode, we clear the cache to free up memory
    // and prevent further ids from being generated
    // Once in limited mode, we won't exit it
  }, {
    key: "setLimitedMode",
    value: function setLimitedMode() {
      this.limitedMode = true;
      this.cache = new WeakMap();
      this.existingPos = new _dynamicBitArray.DynamicBitArray();
      this.count = BigInt(0);
    }
  }]);
}();
var nodeIdProviderMap = new WeakMap();
var LIMITED_MODE_NODE_SIZE_THRESHOLD = 40000;

/**
 * Calculates custom document size including LCM ADF lengths (for non-expanded path).
 * This function can be removed when cc_editor_limited_mode_expanded is cleaned up.
 */
var getCustomDocSize = function getCustomDocSize(doc) {
  var lcmAdfLength = 0;
  doc.descendants(function (node) {
    var _node$attrs;
    if (((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.extensionKey) === 'legacy-content') {
      var _node$attrs$parameter, _node$attrs2;
      lcmAdfLength += (_node$attrs$parameter = (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 || (_node$attrs2 = _node$attrs2.parameters) === null || _node$attrs2 === void 0 || (_node$attrs2 = _node$attrs2.adf) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.length) !== null && _node$attrs$parameter !== void 0 ? _node$attrs$parameter : 0;
    }
  });
  return doc.nodeSize + lcmAdfLength;
};

/**
 * Determines whether limited mode should be enabled.
 * This logic mirrors the limited mode plugin implementation, but lives here to avoid a circular dependency.
 * If it changes, update the matching logic in `editor-plugin-limited-mode/src/pm-plugins/main.ts`.
 *
 * Under the expanded gate, limited mode is activated when ANY of the following conditions are met:
 * 1. Document size exceeds `docSizeThreshold` (if defined) - checked first as O(1)
 * 2. Node count exceeds `nodeCountThreshold` (if defined)
 * 3. Document contains a legacy-content macro (LCM) (if `includeLcmInThreshold` is true)
 *
 * Performance optimisations:
 * - Doc size is checked first (O(1)) - if it exceeds threshold, we skip traversal entirely.
 * - If `includeLcmInThreshold` is enabled and we find an LCM, we exit traversal early.
 * - If neither node count nor LCM conditions are configured, we skip traversal entirely.
 */
var isLimitedModeEnabled = function isLimitedModeEnabled(editorView) {
  var doc = editorView.state.doc;
  if ((0, _expVal.expVal)('cc_editor_limited_mode_expanded', 'isEnabled', false)) {
    var nodeCountThreshold = getNumericExperimentParam('nodeCountThreshold', 5000);
    var docSizeThreshold = getNumericExperimentParam('docSizeThreshold', 30000);
    var includeLcmInThreshold = Boolean((0, _expVal.expVal)('cc_editor_limited_mode_expanded', 'includeLcmInThreshold', false));

    // Early exit: doc size exceeds threshold - O(1), no traversal needed
    if (docSizeThreshold !== undefined && doc.nodeSize > docSizeThreshold) {
      return true;
    }

    // Early exit: no traversal needed if neither condition is configured
    var needNodeCount = nodeCountThreshold !== undefined;
    if (!needNodeCount && !includeLcmInThreshold) {
      return false;
    }

    // Single traversal for node count and/or LCM detection
    var nodeCount = 0;
    var hasLcm = false;
    doc.descendants(function (node) {
      var _node$attrs3;
      nodeCount += 1;
      if (((_node$attrs3 = node.attrs) === null || _node$attrs3 === void 0 ? void 0 : _node$attrs3.extensionKey) === 'legacy-content') {
        hasLcm = true;

        // Early exit: LCM found and condition is enabled - no need to continue counting
        if (includeLcmInThreshold) {
          return false;
        }
      }
    });

    // LCM condition takes precedence (if we early exited traversal, this is why)
    if (includeLcmInThreshold && hasLcm) {
      return true;
    }

    // Check node count threshold
    if (needNodeCount && nodeCount > nodeCountThreshold) {
      return true;
    }
    return false;
  } else {
    var customDocSize = getCustomDocSize(doc);
    return customDocSize > LIMITED_MODE_NODE_SIZE_THRESHOLD;
  }
};

// Get the NodeIdProvider for a specific EditorView instance.
// This allows access to the node ids anywhere.
var getNodeIdProvider = exports.getNodeIdProvider = function getNodeIdProvider(editorView) {
  if (!nodeIdProviderMap.has(editorView)) {
    // if the limited mode flag is on, enable limited mode based on the threshold
    // only for the first time
    var limitedMode = isLimitedModeEnabled(editorView);
    var isEmptyDoc = (0, _utils.isEmptyDocument)(editorView.state.doc);
    var provider = new NodeAnchorProvider(limitedMode, isEmptyDoc);
    nodeIdProviderMap.set(editorView, provider);
    return provider;
  }
  var nodeIdProvider = nodeIdProviderMap.get(editorView);

  // in some cases we need to re-check limited mode state
  // Confluence editor can start with an empty doc and then load content later
  // so we need to check first time from an empty doc to a non-empty doc
  if (nodeIdProvider.isEmptyDoc() && !(0, _utils.isEmptyDocument)(editorView.state.doc)) {
    // set empty doc to false regardless of limited mode state
    nodeIdProvider.setEmptyDoc(false);
    if (!nodeIdProvider.isLimitedMode() && isLimitedModeEnabled(editorView)) {
      nodeIdProvider.setLimitedMode();
    }
  }

  // This is based on the fact that editorView is a singleton.
  return nodeIdProviderMap.get(editorView);
};