import _extends from "@babel/runtime/helpers/extends";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
/**
 * @jsxRuntime classic
 * @jsx jsx
 */
import React from 'react';

// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
import { jsx } from '@emotion/react';
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
import { fg } from '@atlaskit/platform-feature-flags';
import { ACTION_SUBJECT, ACTION_SUBJECT_ID } from '../analytics';
import { isSSR } from '../core-utils/is-ssr';
import { ErrorBoundary } from '../ui/ErrorBoundary';
import { analyticsEventKey, getPerformanceOptions, startMeasureReactNodeViewRendered, stopMeasureReactNodeViewRendered } from '../utils';
import { getBrowserInfo } from '../utils/browser';
import { ZERO_WIDTH_SPACE } from '../whitespace';
import { generateUniqueNodeKey } from './generateUniqueNodeKey';
import { getOrCreateOnVisibleObserver } from './onVisibleObserverFactory';
export var inlineNodeViewClassname = 'inlineNodeView';
var canRenderFallback = function canRenderFallback(node) {
  return node.type.isInline && node.type.isAtom && node.type.isLeaf;
};

// list of inline nodes with toDOM fallback implementations that can be virtualized. As
// additional nodes are converted they should be added here
var virtualizedNodeAllowlist = ['inlineCard'];
function createNodeView(_ref) {
  var nodeViewParams = _ref.nodeViewParams,
    pmPluginFactoryParams = _ref.pmPluginFactoryParams,
    Component = _ref.Component,
    extraComponentProps = _ref.extraComponentProps,
    extraNodeViewProps = _ref.extraNodeViewProps;
  // We set a variable for the current node which is
  // used for comparisions when doing updates, before being
  // overwritten to the updated node.
  var currentNode = nodeViewParams.node;
  var key = generateUniqueNodeKey();

  // First we setup the dom element which will be rendered and "tracked" by prosemirror
  // and also used as a "editor portal" (not react portal) target by the editor
  // portal provider api, for rendering the Component passed.

  var domRef = document.createElement('span');
  domRef.contentEditable = 'false';
  setDomAttrs(nodeViewParams.node, domRef);
  var fallbackRef = {
    current: null
  };

  // @see ED-3790
  // something gets messed up during mutation processing inside of a
  // nodeView if DOM structure has nested plain "div"s, it doesn't see the
  // difference between them and it kills the nodeView
  domRef.classList.add("".concat(nodeViewParams.node.type.name, "View-content-wrap"), "".concat(inlineNodeViewClassname));

  // This util is shared for tracking rendering, and the ErrorBoundary that
  // is setup to wrap the Component when rendering
  // NOTE: This is not a prosemirror dispatch
  function dispatchAnalyticsEvent(payload) {
    pmPluginFactoryParams.eventDispatcher.emit(analyticsEventKey, {
      payload: payload
    });
  }

  // This is called to render the Component into domRef which is inside the
  // prosemirror View.
  // Internally it uses the unstable_renderSubtreeIntoContainer api to render,
  // to the passed dom element (domRef) which means it is automatically
  // "cleaned up" when you do a "re render".
  function renderComponent() {
    pmPluginFactoryParams.portalProviderAPI.render(getPortalChildren({
      dispatchAnalyticsEvent: dispatchAnalyticsEvent,
      currentNode: currentNode,
      nodeViewParams: nodeViewParams,
      Component: Component,
      extraComponentProps: extraComponentProps
    }), domRef, key);
  }
  var _getPerformanceOption = getPerformanceOptions(nodeViewParams.view),
    samplingRate = _getPerformanceOption.samplingRate,
    slowThreshold = _getPerformanceOption.slowThreshold,
    trackingEnabled = _getPerformanceOption.trackingEnabled;
  trackingEnabled && startMeasureReactNodeViewRendered({
    nodeTypeName: currentNode.type.name
  });

  // We render the component while creating the node view
  renderComponent();
  trackingEnabled && stopMeasureReactNodeViewRendered({
    nodeTypeName: currentNode.type.name,
    dispatchAnalyticsEvent: dispatchAnalyticsEvent,
    samplingRate: samplingRate,
    slowThreshold: slowThreshold
  });
  var extraNodeViewPropsWithStopEvent = _objectSpread({}, extraNodeViewProps);

  // https://prosemirror.net/docs/ref/#view.NodeView
  var nodeView = _objectSpread({
    get dom() {
      return domRef;
    },
    update: function update(nextNode, _decorations) {
      // Let ProseMirror handle the update if node types are different.
      // This prevents an issue where it was not possible to select the
      // inline node view then replace it by entering text - the node
      // view contents would be deleted but the node view itself would
      // stay in the view and become uneditable.
      if (currentNode.type !== nextNode.type) {
        return false;
      }
      // On updates, we only set the new attributes if the type, attributes, and marks
      // have changed on the node.

      // NOTE: this could mean attrs changes aren't reflected in the dom,
      // when an attribute key which was previously present is no longer
      // present.
      // ie.
      // -> Original attributes { text: "hello world", color: "red" }
      // -> Updated attributes { color: "blue" }
      // in this case, the dom text attribute will not be cleared.
      //
      // This may not be an issue with any of our current node schemas.
      if (!currentNode.sameMarkup(nextNode)) {
        setDomAttrs(nextNode, domRef);
      }
      currentNode = nextNode;
      renderComponent();
      return true;
    },
    destroy: function destroy() {
      // When prosemirror destroys the node view, we need to clean up
      // what we have previously rendered using the editor portal
      // provider api.
      pmPluginFactoryParams.portalProviderAPI.remove(key);
      // @ts-expect-error Expect an error as domRef is expected to be
      // of HTMLSpanElement type however once the node view has
      // been destroyed no other consumers should still be using it.
      domRef = undefined;
      fallbackRef.current = null;
    }
  }, extraNodeViewPropsWithStopEvent);
  return nodeView;
}
function createNodeViewVirtualized(_ref2) {
  var nodeViewParams = _ref2.nodeViewParams,
    pmPluginFactoryParams = _ref2.pmPluginFactoryParams,
    Component = _ref2.Component,
    extraComponentProps = _ref2.extraComponentProps,
    extraNodeViewProps = _ref2.extraNodeViewProps;
  // We set a variable for the current node which is
  // used for comparisions when doing updates, before being
  // overwritten to the updated node.
  var currentNode = nodeViewParams.node;
  var key = generateUniqueNodeKey();

  // First we setup the dom element which will be rendered and "tracked" by prosemirror
  // and also used as a "editor portal" (not react portal) target by the editor
  // portal provider api, for rendering the Component passed.

  var domRef = document.createElement('span');
  domRef.contentEditable = 'false';
  setDomAttrs(nodeViewParams.node, domRef);
  var fallbackRef = {
    current: null
  };

  // @see ED-3790
  // something gets messed up during mutation processing inside of a
  // nodeView if DOM structure has nested plain "div"s, it doesn't see the
  // difference between them and it kills the nodeView
  domRef.classList.add("".concat(nodeViewParams.node.type.name, "View-content-wrap"), "".concat(inlineNodeViewClassname));
  function onBeforeReactDomRender() {
    if (!fallbackRef.current) {
      return;
    }
    domRef.removeChild(fallbackRef.current);
    fallbackRef.current = null;
  }

  // This util is shared for tracking rendering, and the ErrorBoundary that
  // is setup to wrap the Component when rendering
  // NOTE: This is not a prosemirror dispatch
  function dispatchAnalyticsEvent(payload) {
    pmPluginFactoryParams.eventDispatcher.emit(analyticsEventKey, {
      payload: payload
    });
  }

  // This is called to render the Component into domRef which is inside the
  // prosemirror View.
  // Internally it uses the unstable_renderSubtreeIntoContainer api to render,
  // to the passed dom element (domRef) which means it is automatically
  // "cleaned up" when you do a "re render".
  function renderComponent() {
    pmPluginFactoryParams.portalProviderAPI.render(getPortalChildren({
      dispatchAnalyticsEvent: dispatchAnalyticsEvent,
      currentNode: currentNode,
      nodeViewParams: nodeViewParams,
      Component: Component,
      extraComponentProps: extraComponentProps
    }), domRef, key, onBeforeReactDomRender);
  }
  var didRenderComponentWithIntersectionObserver = false;
  var destroyed = false;
  var removeIntersectionObserver = function removeIntersectionObserver() {};
  function renderFallback() {
    var _currentNode$type;
    if (!canRenderFallback(currentNode) || typeof ((_currentNode$type = currentNode.type) === null || _currentNode$type === void 0 || (_currentNode$type = _currentNode$type.spec) === null || _currentNode$type === void 0 ? void 0 : _currentNode$type.toDOM) !== 'function') {
      return;
    }
    var fallback = DOMSerializer.renderSpec(document, currentNode.type.spec.toDOM(currentNode));
    var dom = fallback.dom;
    fallbackRef.current = dom;
    domRef.replaceChildren(dom);
  }
  function attachNodeViewObserver() {
    var observer = getOrCreateOnVisibleObserver(nodeViewParams.view);
    if (domRef) {
      removeIntersectionObserver = observer.observe(domRef, function () {
        if (!didRenderComponentWithIntersectionObserver && !destroyed) {
          renderComponent();
          didRenderComponentWithIntersectionObserver = true;
        }
      });
    }
  }
  renderFallback();
  // allow the fallback to render first before attaching the observer.
  // Will tweak this in a follow up PR to optimise rendering of visible
  // nodes without fallback rendering.
  setTimeout(function () {
    attachNodeViewObserver();
  }, 0);
  var extraNodeViewPropsWithStopEvent = _objectSpread(_objectSpread({}, extraNodeViewProps), {}, {
    stopEvent: function stopEvent(event) {
      var maybeStopEvent = extraNodeViewProps === null || extraNodeViewProps === void 0 ? void 0 : extraNodeViewProps.stopEvent;
      if (typeof maybeStopEvent === 'function') {
        return maybeStopEvent(event);
      }
      return false;
    }
  });

  // https://prosemirror.net/docs/ref/#view.NodeView
  var nodeView = _objectSpread({
    get dom() {
      return domRef;
    },
    update: function update(nextNode, _decorations) {
      // Let ProseMirror handle the update if node types are different.
      // This prevents an issue where it was not possible to select the
      // inline node view then replace it by entering text - the node
      // view contents would be deleted but the node view itself would
      // stay in the view and become uneditable.
      if (currentNode.type !== nextNode.type) {
        return false;
      }
      // On updates, we only set the new attributes if the type, attributes, and marks
      // have changed on the node.

      // NOTE: this could mean attrs changes aren't reflected in the dom,
      // when an attribute key which was previously present is no longer
      // present.
      // ie.
      // -> Original attributes { text: "hello world", color: "red" }
      // -> Updated attributes { color: "blue" }
      // in this case, the dom text attribute will not be cleared.
      //
      // This may not be an issue with any of our current node schemas.
      if (!currentNode.sameMarkup(nextNode)) {
        setDomAttrs(nextNode, domRef);
      }
      currentNode = nextNode;
      if (didRenderComponentWithIntersectionObserver) {
        renderComponent();
      }
      return true;
    },
    destroy: function destroy() {
      removeIntersectionObserver();
      destroyed = true;

      // When prosemirror destroys the node view, we need to clean up
      // what we have previously rendered using the editor portal
      // provider api.
      pmPluginFactoryParams.portalProviderAPI.remove(key);
      // @ts-expect-error Expect an error as domRef is expected to be
      // of HTMLSpanElement type however once the node view has
      // been destroyed no other consumers should still be using it.
      domRef = undefined;
      fallbackRef.current = null;
    }
  }, extraNodeViewPropsWithStopEvent);
  return nodeView;
}

/**
 * Copies the attributes from a ProseMirror Node to a DOM node.
 * @param node The Prosemirror Node from which to source the attributes
 */
function setDomAttrs(node, element) {
  Object.keys(node.attrs || {}).forEach(function (attr) {
    element.setAttribute(attr, node.attrs[attr]);
  });
}
function getPortalChildren(_ref3) {
  var dispatchAnalyticsEvent = _ref3.dispatchAnalyticsEvent,
    currentNode = _ref3.currentNode,
    nodeViewParams = _ref3.nodeViewParams,
    Component = _ref3.Component,
    extraComponentProps = _ref3.extraComponentProps;
  return function portalChildren() {
    var _currentNode$type$nam, _currentNode$type2;
    //  All inline nodes use `display: inline` to allow for multi-line
    //  wrapping. This does produce an issue in Chrome where it is not
    //  possible to click select the position after the node,
    //  see: https://product-fabric.atlassian.net/browse/ED-12003
    //  however this is only a problem for node views that use
    //  `display: inline-block` somewhere within the Component.
    //  Looking at the below structure, spans with className
    //  `inlineNodeViewAddZeroWidthSpace` have pseudo elements that
    //  add a zero width space which fixes the problem.
    //  Without the additional zero width space before the Component,
    //  it is not possible to use the keyboard to range select in Safari.
    //
    //  Zero width spaces on either side of the Component also prevent
    //  the cursor from appearing inside the node view on all browsers.
    //
    //  Note:
    //  In future it is worth considering prohibiting the use of `display: inline-block`
    //  within inline node view Components however would require a sizable
    //  refactor. A test suite to catch any instances of this is ideal however
    //  the refactor required is currently out of scope for https://product-fabric.atlassian.net/browse/ED-14176

    return jsx(ErrorBoundary, {
      component: ACTION_SUBJECT.REACT_NODE_VIEW,
      componentId: (_currentNode$type$nam = currentNode === null || currentNode === void 0 || (_currentNode$type2 = currentNode.type) === null || _currentNode$type2 === void 0 ? void 0 : _currentNode$type2.name) !== null && _currentNode$type$nam !== void 0 ? _currentNode$type$nam : ACTION_SUBJECT_ID.UNKNOWN_NODE,
      dispatchAnalyticsEvent: dispatchAnalyticsEvent
    }, jsx("span", {
      className: "zeroWidthSpaceContainer"
    }, jsx("span", {
      className: "".concat(inlineNodeViewClassname, "AddZeroWidthSpace")
    }), ZERO_WIDTH_SPACE), jsx(Component, _extends({
      view: nodeViewParams.view
      // TODO: ED-13910 - Remove the boolean to fix the prosemirror view type
      // Ignored via go/ees005
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      ,
      getPos: nodeViewParams.getPos,
      node: currentNode
      // eslint-disable-next-line react/jsx-props-no-spreading -- Spreading props to pass through dynamic component props
    }, extraComponentProps)), getBrowserInfo().android ?
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
    jsx("span", {
      className: "zeroWidthSpaceContainer",
      contentEditable: "false"
    }, jsx("span", {
      className: "".concat(inlineNodeViewClassname, "AddZeroWidthSpace")
    }), ZERO_WIDTH_SPACE) :
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
    jsx("span", {
      className: "".concat(inlineNodeViewClassname, "AddZeroWidthSpace")
    }));
  };
}

// https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews
// The prosemirror EditorProps has a nodeViews key which has the rough shape:
// type nodeViews: {
//   [nodeViewName: string]: (node, editorView, getPos, decorations, innerDecorations) => NodeView
// }
// So the value of the keys on the nodeViews object, are a function which should return a NodeView.
// The following type NodeViewProducer, refers to these functions which return a NodeView.
//
// So the above type could also be described as
// type NodeViewProducer = (node, editorView, getPos, decorations, innerDecorations) => NodeView
// nodeViews: {
//   [nodeViewName: string]: NodeViewProducer
// }

var counterPerEditorViewMap = new WeakMap();
// This return of this function is intended to be the value of a key
// in a ProseMirror nodeViews object.
// eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
export function getInlineNodeViewProducer(_ref4) {
  var pmPluginFactoryParams = _ref4.pmPluginFactoryParams,
    Component = _ref4.Component,
    extraComponentProps = _ref4.extraComponentProps,
    extraNodeViewProps = _ref4.extraNodeViewProps;
  function nodeViewProducer() {
    var _node$type;
    var view = arguments.length <= 1 ? undefined : arguments[1];
    var node = arguments.length <= 0 ? undefined : arguments[0];
    var parameters = {
      nodeViewParams: {
        node: node,
        view: view,
        getPos: arguments.length <= 2 ? undefined : arguments[2],
        decorations: arguments.length <= 3 ? undefined : arguments[3]
      },
      pmPluginFactoryParams: pmPluginFactoryParams,
      Component: Component,
      extraComponentProps: extraComponentProps,
      extraNodeViewProps: extraNodeViewProps
    };
    var isNodeTypeAllowedToBeVirtualized = virtualizedNodeAllowlist.includes((node === null || node === void 0 || (_node$type = node.type) === null || _node$type === void 0 ? void 0 : _node$type.name) || '');
    if (!isNodeTypeAllowedToBeVirtualized || isSSR()) {
      return createNodeView(parameters);
    }
    if (fg('platform_editor_inline_node_virt_threshold_override')) {
      return createNodeViewVirtualized(parameters);
    }
    var inlineNodeViewsVirtualizationCounter = counterPerEditorViewMap.get(view) || 0;
    inlineNodeViewsVirtualizationCounter += 1;
    counterPerEditorViewMap.set(view, inlineNodeViewsVirtualizationCounter);

    // We never virtualize the 100th first elements
    if (inlineNodeViewsVirtualizationCounter <= 100) {
      return createNodeView(parameters);
    }
    return createNodeViewVirtualized(parameters);
  }
  return nodeViewProducer;
}