"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ExtensionNode = void 0;
exports.default = ExtensionNodeView;
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireDefault(require("react"));
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
var _coreUtils = require("../core-utils");
var _reactNodeView = _interopRequireDefault(require("../react-node-view"));
var _Extension = require("./Extension");
var _ExtensionNodeWrapper = require("./ExtensionNodeWrapper");
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
function _superPropGet(t, o, e, r) { var p = (0, _get2.default)((0, _getPrototypeOf2.default)(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; }
/**
 * Allowlists of extension keys + layout
 * Currently, only toc with default layout allows to skip React render.
 * Extensions NOT in this list always follow the normal React render path.
 */
var ssrHydrationExtensionAllowlist = ['toc'];
var ssrHydrationLayoutAllowlist = ['default'];
var isSSRHydrationEligible = function isSSRHydrationEligible(node) {
  var _node$attrs;
  if (node.type.name !== 'extension') {
    return false;
  }
  var _ref = (_node$attrs = node.attrs) !== null && _node$attrs !== void 0 ? _node$attrs : {},
    extensionKey = _ref.extensionKey,
    layout = _ref.layout;
  if (!ssrHydrationExtensionAllowlist.includes(extensionKey)) {
    return false;
  }
  // Treat a missing layout attr as `default` (the schema default).
  var effectiveLayout = layout !== null && layout !== void 0 ? layout : 'default';
  return ssrHydrationLayoutAllowlist.includes(effectiveLayout);
};

/**
 * Per-(EditorView, extensionKey + localId) record of which extension identities
 * have already had their initial-hydration init pass. SSR DOM reuse is only valid
 * the very first time an ExtensionNode init runs for a given identity in a given
 * editor — that is the only moment a real SSR-rendered element for that identity
 * can exist in the editor DOM.
 *
 * After the first init, any element matching the SSR selector is the previous
 * node view's React-rendered domRef that ProseMirror has not yet detached (e.g.
 * during DnD / layout resize, where ProseMirror constructs the new node view
 * BEFORE destroying the old one). Reusing it as if it were SSR DOM causes the
 * new node view to skip React rendering and leaves the extension invisible
 * (EDITOR-6613).
 */
var consumedHydrationIdentitiesByEditor = new WeakMap();
var _getHydrationIdentityKey = function getHydrationIdentityKey(extensionKey, localId) {
  if (typeof extensionKey !== 'string' || typeof localId !== 'string') {
    return null;
  }
  if (extensionKey === '' || localId === '') {
    return null;
  }
  return "".concat(extensionKey, "::").concat(localId);
};
var hasHydrationIdentityBeenConsumed = function hasHydrationIdentityBeenConsumed(view, identityKey) {
  var consumed = consumedHydrationIdentitiesByEditor.get(view);
  return consumed ? consumed.has(identityKey) : false;
};
var markHydrationIdentityAsConsumed = function markHydrationIdentityAsConsumed(view, identityKey) {
  var consumed = consumedHydrationIdentitiesByEditor.get(view);
  if (!consumed) {
    consumed = new Set();
    consumedHydrationIdentitiesByEditor.set(view, consumed);
  }
  consumed.add(identityKey);
};

// getInlineNodeViewProducer is a new api to use instead of ReactNodeView
// when creating inline node views, however, it is difficult to test the impact
// on selections when migrating inlineExtension to use the new api.
// The ReactNodeView api will be visited in the second phase of the selections
// project whilst investigating block nodes. We will revisit the Extension node view there too.
var ExtensionNode = exports.ExtensionNode = /*#__PURE__*/function (_ReactNodeView) {
  function ExtensionNode() {
    var _this;
    (0, _classCallCheck2.default)(this, ExtensionNode);
    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }
    _this = _callSuper(this, ExtensionNode, [].concat(args));
    /** True between SSR DOM adoption in `createDomRef` and the SSR→React handoff in `update`. */
    (0, _defineProperty2.default)(_this, "didReuseSsrDom", false);
    return _this;
  }
  (0, _inherits2.default)(ExtensionNode, _ReactNodeView);
  return (0, _createClass2.default)(ExtensionNode, [{
    key: "ignoreMutation",
    value: function ignoreMutation(mutation) {
      // Extensions can perform async operations that will change the DOM.
      // To avoid having their tree rebuilt, we need to ignore the mutation
      // for atom based extensions if its not a layout, we need to give
      // children a chance to recalc
      return this.node.type.isAtom || mutation.type !== 'selection' && mutation.attributeName !== 'data-layout';
    }

    /** See {@link consumedHydrationIdentitiesByEditor}. Null when attrs are missing → SSR reuse skipped. */
  }, {
    key: "getHydrationIdentityKey",
    value: function getHydrationIdentityKey() {
      var _this$node$attrs, _this$node$attrs2;
      return _getHydrationIdentityKey((_this$node$attrs = this.node.attrs) === null || _this$node$attrs === void 0 ? void 0 : _this$node$attrs.extensionKey, (_this$node$attrs2 = this.node.attrs) === null || _this$node$attrs2 === void 0 ? void 0 : _this$node$attrs2.localId);
    }

    /** True only for the first ExtensionNode of this identity in this editor. See {@link consumedHydrationIdentitiesByEditor}. */
  }, {
    key: "isInInitialHydrationWindow",
    value: function isInInitialHydrationWindow() {
      var identityKey = this.getHydrationIdentityKey();
      if (identityKey === null) {
        return false;
      }
      return !hasHydrationIdentityBeenConsumed(this.view, identityKey);
    }

    // Reserve height by setting a minimum height for the extension node view element
  }, {
    key: "createDomRef",
    value: function createDomRef() {
      if (!(0, _platformFeatureFlags.fg)('confluence_connect_macro_preset_height')) {
        // SSR DOM reuse — see {@link consumedHydrationIdentitiesByEditor}.
        if (!(0, _coreUtils.isSSR)() && isSSRHydrationEligible(this.node) && this.isInInitialHydrationWindow() && (0, _expValEquals.expValEquals)('platform_editor_hydration_skip_react_portal', 'isEnabled', true)) {
          var ssrElement = this.findSSRElement();
          if (ssrElement) {
            this.didReuseSsrDom = true;
            return ssrElement;
          }
        }
        return _superPropGet(ExtensionNode, "createDomRef", this, 3)([]);
      }
      if (!this.node.isInline) {
        var _this$reactComponentP, _this$reactComponentP2;
        var _htmlElement = document.createElement('div');
        var extensionHeight = (_this$reactComponentP = this.reactComponentProps) === null || _this$reactComponentP === void 0 || (_this$reactComponentP = _this$reactComponentP.extensionNodeViewOptions) === null || _this$reactComponentP === void 0 || (_this$reactComponentP2 = _this$reactComponentP.getExtensionHeight) === null || _this$reactComponentP2 === void 0 ? void 0 : _this$reactComponentP2.call(_this$reactComponentP, this.node);
        if (extensionHeight) {
          _htmlElement.style.setProperty('min-height', "".concat(extensionHeight, "px"));
        }
        return _htmlElement;
      }
      var htmlElement = document.createElement('span');
      return htmlElement;
    }

    /**
     * Cache for SSR element lookup to avoid repeated DOM queries.
     * undefined = not yet searched, null = searched but not found, HTMLElement = found
     */
  }, {
    key: "findSSRElement",
    value:
    /**
     * Attempts to find an existing SSR'd DOM element for this extension node by extensionKey and localId
     * which should uniquely identify the
     * extension node within the editor content.
     *
     * @returns The SSR'd element if found, otherwise null
     */
    function findSSRElement() {
      if (this.cachedSsrElement !== undefined) {
        return this.cachedSsrElement || null;
      }
      var extensionKey = this.node.attrs.extensionKey;
      var localId = this.node.attrs.localId;
      var editorDom = this.view.dom;
      if (extensionKey && localId) {
        var selector = "[extensionkey=\"".concat(extensionKey, "\"][localid=\"").concat(localId, "\"]");
        var element = editorDom.querySelector(selector);
        if (element && element instanceof HTMLElement) {
          this.cachedSsrElement = element;
          return element;
        }
      }
      this.cachedSsrElement = null;
      return null;
    }

    /** Skip React Portal render on first init when reusing SSR DOM. See {@link consumedHydrationIdentitiesByEditor}. */
  }, {
    key: "init",
    value: function init() {
      if (!(0, _expValEquals.expValEquals)('platform_editor_hydration_skip_react_portal', 'isEnabled', true)) {
        _superPropGet(ExtensionNode, "init", this, 3)([]);
      } else {
        var isEligibleForSsrReuse = !(0, _coreUtils.isSSR)() && isSSRHydrationEligible(this.node);
        if (isEligibleForSsrReuse && this.isInInitialHydrationWindow()) {
          var ssrElement = this.findSSRElement();
          var shouldSkipInitRender = ssrElement !== null;
          _superPropGet(ExtensionNode, "init", this, 3)([shouldSkipInitRender]);
          var identityKey = this.getHydrationIdentityKey();
          if (identityKey !== null) {
            // Close the hydration window — see {@link consumedHydrationIdentitiesByEditor}.
            markHydrationIdentityAsConsumed(this.view, identityKey);
          }
        } else {
          _superPropGet(ExtensionNode, "init", this, 3)([]);
        }
      }
      return this;
    }
  }, {
    key: "update",
    value: function update(node, decorations, _innerDecorations) {
      var validUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () {
        return true;
      };
      // Remove extensionNodeWrapper aka span.relative if we previously reused SSR DOM
      // control is back to React afterwards
      if (this.didReuseSsrDom && (0, _expValEquals.expValEquals)('platform_editor_hydration_skip_react_portal', 'isEnabled', true)) {
        var ssrElement = this.findSSRElement();
        if (ssrElement) {
          var extensionNodeWrapper = ssrElement.querySelector('[data-testid="extension-node-wrapper"]');
          if (extensionNodeWrapper) {
            extensionNodeWrapper.remove();
          }
          this.didReuseSsrDom = false;
        }
      }
      return _superPropGet(ExtensionNode, "update", this, 3)([node, decorations, _innerDecorations, validUpdate]);
    }

    /**
     * Prevents ProseMirror from handling events that belong to the extension's React UI.
     *
     * For multibodied extensions: the node consists of a React app (e.g. tab bar) that controls
     * frames, and the editable frames themselves. Events inside frames must reach ProseMirror
     * for normal editing; events in the surrounding React app (buttons, navigation) are stopped
     * so ProseMirror doesn't create a NodeSelection on the entire node.
     *
     * For other extensions: input/textarea events are stopped so users can interact
     * with form elements inside the extension body without the editor intercepting them.
     */
  }, {
    key: "stopEvent",
    value: function stopEvent(event) {
      if (this.node.type.name === 'multiBodiedExtension') {
        var target = event.target;
        if (target instanceof Element) {
          if (target.closest('[data-multibodiedextension-frames]')) {
            return false;
          }
          return !!target.closest('[data-multiBodiedExtension-container]');
        }
        return false;
      }
      if ((0, _platformFeatureFlags.fg)('forge-ui-extensionnodeview-stop-event-for-textarea')) {
        return event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement;
      }
      return event.target instanceof HTMLInputElement;
    }
  }, {
    key: "getContentDOM",
    value: function getContentDOM() {
      var _this$reactComponentP3;
      if (this.node.isInline) {
        return;
      }
      if (this.didReuseSsrDom) {
        return;
      }
      var contentDomWrapper = document.createElement('div');
      contentDomWrapper.className = "".concat(this.node.type.name, "-content-dom-wrapper");
      var isBodiedExtension = this.node.type.name === 'bodiedExtension';
      var showMacroInteractionDesignUpdates = (_this$reactComponentP3 = this.reactComponentProps) === null || _this$reactComponentP3 === void 0 || (_this$reactComponentP3 = _this$reactComponentP3.macroInteractionDesignFeatureFlags) === null || _this$reactComponentP3 === void 0 ? void 0 : _this$reactComponentP3.showMacroInteractionDesignUpdates;
      if (isBodiedExtension && showMacroInteractionDesignUpdates && (0, _expValEquals.expValEquals)('platform_editor_bodiedextension_layoutshift_fix', 'isEnabled', true)) {
        // Create outer wrapper to hold space for the lozenge
        var outerWrapper = document.createElement('div');
        outerWrapper.className = "".concat(this.node.type.name, "-content-outer-wrapper");

        // Create inner wrapper to position inner content
        var innerWrapper = document.createElement('div');
        innerWrapper.className = "".concat(this.node.type.name, "-content-inner-wrapper");
        innerWrapper.appendChild(contentDomWrapper);
        outerWrapper.appendChild(innerWrapper);
        return {
          dom: outerWrapper,
          contentDOM: contentDomWrapper
        };
      }
      return {
        dom: contentDomWrapper
      };
    }
  }, {
    key: "render",
    value: function render(props, forwardRef) {
      var _props$extensionNodeV;
      // While sitting on SSR DOM, skip the React portal — see {@link didReuseSsrDom}.
      if (this.didReuseSsrDom) {
        return null;
      }
      return /*#__PURE__*/_react.default.createElement(_ExtensionNodeWrapper.ExtensionNodeWrapper, {
        intl: props.intl,
        nodeType: this.node.type.name,
        macroInteractionDesignFeatureFlags: props.macroInteractionDesignFeatureFlags
      }, /*#__PURE__*/_react.default.createElement(_Extension.Extension, {
        editorView: this.view,
        node: this.node,
        eventDispatcher: this.eventDispatcher
        // The getPos arg is always a function when used with nodes
        // the version of the types we use has a union with the type
        // for marks.
        // This has been fixed in later versions of the definitly typed
        // types (and also in prosmirror-views inbuilt types).
        // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/57384
        ,
        getPos: this.getPos,
        providerFactory: props.providerFactory,
        handleContentDOMRef: forwardRef,
        extensionHandlers: props.extensionHandlers,
        editorAppearance: (_props$extensionNodeV = props.extensionNodeViewOptions) === null || _props$extensionNodeV === void 0 ? void 0 : _props$extensionNodeV.appearance,
        pluginInjectionApi: props.pluginInjectionApi,
        macroInteractionDesignFeatureFlags: props.macroInteractionDesignFeatureFlags,
        showLivePagesBodiedMacrosRendererView: props.showLivePagesBodiedMacrosRendererView,
        showUpdatedLivePages1PBodiedExtensionUI: props.showUpdatedLivePages1PBodiedExtensionUI,
        rendererExtensionHandlers: props.rendererExtensionHandlers
      }));
    }
  }]);
}(_reactNodeView.default);
function ExtensionNodeView(portalProviderAPI, eventDispatcher, providerFactory, extensionHandlers, extensionNodeViewOptions, pluginInjectionApi, macroInteractionDesignFeatureFlags, showLivePagesBodiedMacrosRendererView, showUpdatedLivePages1PBodiedExtensionUI, rendererExtensionHandlers, intl) {
  return function (node, view, getPos) {
    return new ExtensionNode(node, view, getPos, portalProviderAPI, eventDispatcher, {
      providerFactory: providerFactory,
      extensionHandlers: extensionHandlers,
      extensionNodeViewOptions: extensionNodeViewOptions,
      pluginInjectionApi: pluginInjectionApi,
      macroInteractionDesignFeatureFlags: macroInteractionDesignFeatureFlags,
      showLivePagesBodiedMacrosRendererView: showLivePagesBodiedMacrosRendererView,
      showUpdatedLivePages1PBodiedExtensionUI: showUpdatedLivePages1PBodiedExtensionUI,
      rendererExtensionHandlers: rendererExtensionHandlers,
      intl: intl
    }).init();
  };
}