import _defineProperty from "@babel/runtime/helpers/defineProperty";
import { isEmptyDocument } from '../utils';
import { DynamicBitArray } from './dynamic-bit-array';
import { LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD, LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD } from './limited-mode-document-thresholds';
export class NodeAnchorProvider {
  constructor(limitedMode = false, emptyDoc = false) {
    _defineProperty(this, "cache", new WeakMap());
    _defineProperty(this, "count", BigInt(0));
    _defineProperty(this, "existingPos", new DynamicBitArray());
    _defineProperty(this, "limitedMode", false);
    _defineProperty(this, "emptyDoc", false);
    this.limitedMode = limitedMode;
    this.emptyDoc = emptyDoc;
  }
  isEmptyDoc() {
    return this.emptyDoc;
  }
  setEmptyDoc(isEmpty) {
    this.emptyDoc = isEmpty;
  }
  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
  getOrGenerateId(node, pos) {
    if (this.limitedMode) {
      return undefined;
    }
    if (this.cache.has(node)) {
      return this.cache.get(node);
    }
    let idSuffix = '';
    if (this.existingPos.get(pos)) {
      idSuffix = `-${(this.count++).toString(36)}`;
    } else {
      this.existingPos.set(pos, true);
    }
    const anchorName = `--anchor-${node.type.name}-${pos}${idSuffix}`;
    this.cache.set(node, anchorName);
    return anchorName;
  }
  getIdForNode(node) {
    if (this.limitedMode) {
      return undefined;
    }
    return this.cache.get(node);
  }
  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
  setLimitedMode() {
    this.limitedMode = true;
    this.cache = new WeakMap();
    this.existingPos = new DynamicBitArray();
    this.count = BigInt(0);
  }
}
const nodeIdProviderMap = new WeakMap();

/**
 * 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`.
 *
 * Limited mode is activated when ANY of the following conditions are met:
 * 1. Document size exceeds `LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD` — checked first as O(1)
 * 2. Node count exceeds `LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD`
 * 3. Document contains a legacy-content macro (LCM)
 *
 * Performance optimisations:
 * - Doc size is checked first (O(1)) - if it exceeds threshold, we skip traversal entirely.
 * - If we find an LCM during traversal, we exit early since limited mode will be enabled.
 */
const isLimitedModeEnabled = editorView => {
  const doc = editorView.state.doc;
  const nodeCountThreshold = LIMITED_MODE_DEFAULT_NODE_COUNT_THRESHOLD;
  const docSizeThreshold = LIMITED_MODE_DEFAULT_DOC_SIZE_THRESHOLD;

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

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

      // Early exit: LCM found — limited mode will be enabled
      return false;
    }
  });

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

  // Check node count threshold
  if (nodeCount > nodeCountThreshold) {
    return true;
  }
  return false;
};

// Get the NodeIdProvider for a specific EditorView instance.
// This allows access to the node ids anywhere.
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
export const 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
    const limitedMode = isLimitedModeEnabled(editorView);
    const isEmptyDoc = isEmptyDocument(editorView.state.doc);
    const provider = new NodeAnchorProvider(limitedMode, isEmptyDoc);
    nodeIdProviderMap.set(editorView, provider);
    return provider;
  }
  const 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() && !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);
};