import _extends from "@babel/runtime/helpers/extends";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
// Disable no-re-export rule for entry point files
/* eslint-disable @atlaskit/editor/no-re-export */

import React from 'react';
import { ACTION_SUBJECT, ACTION_SUBJECT_ID } from '../analytics';
import { isSSR, isSSRStreaming } from '../core-utils';
import { createDispatch } from '../event-dispatcher';
import { ErrorBoundary } from '../ui/ErrorBoundary';
import { getPerformanceOptions, startMeasureReactNodeViewRendered, stopMeasureReactNodeViewRendered } from '../utils';
import { analyticsEventKey } from '../utils/analytics';
import { generateUniqueNodeKey } from './generateUniqueNodeKey';
export { getInlineNodeViewProducer, inlineNodeViewClassname } from './getInlineNodeViewProducer';
export { NodeViewContentHole } from './NodeViewContentHole';
export default class ReactNodeView {
  constructor(_node, view, getPos, portalProviderAPI, eventDispatcher, reactComponentProps,
  // Spreading props to pass through dynamic component props
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  reactComponent, viewShouldUpdate, shouldRenderImmediatelyInPortal) {
    // Spreading props to pass through dynamic component props
    // Ignored via go/ees005
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    _defineProperty(this, "decorations", []);
    _defineProperty(this, "handleRef", node => this._handleRef(node));
    _defineProperty(this, "dispatchAnalyticsEvent", payload => {
      if (this.eventDispatcher) {
        const dispatch = createDispatch(this.eventDispatcher);
        dispatch(analyticsEventKey, {
          payload
        });
      }
    });
    this.node = _node;
    this.view = view;
    this.getPos = getPos;
    this.portalProviderAPI = portalProviderAPI;
    this.reactComponentProps = reactComponentProps || {};
    this.reactComponent = reactComponent;
    this._viewShouldUpdate = viewShouldUpdate;
    this.eventDispatcher = eventDispatcher;
    this.key = generateUniqueNodeKey();
    this.shouldRenderImmediatelyInPortal = shouldRenderImmediatelyInPortal || false;
  }

  /**
   * This method exists to move initialization logic out of the constructor,
   * so object can be initialized properly before calling render first time.
   *
   * Example:
   * Instance properties get added to an object only after super call in
   * constructor, which leads to some methods being undefined during the
   * first render.
   */
  init(shouldSkipInitRender = false) {
    this.domRef = this.createDomRef();
    this.setDomAttrs(this.node, this.domRef);
    const {
      dom: contentDOMWrapper,
      contentDOM
    } = this.getContentDOM() || {
      dom: undefined,
      contentDOM: undefined
    };
    if (this.domRef && contentDOMWrapper) {
      this.domRef.appendChild(contentDOMWrapper);
      this.contentDOM = contentDOM ? contentDOM : contentDOMWrapper;
      this.contentDOMWrapper = contentDOMWrapper || contentDOM;
    }

    // @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
    this.domRef.classList.add(`${this.node.type.name}View-content-wrap`);
    const {
      samplingRate,
      slowThreshold,
      trackingEnabled
    } = getPerformanceOptions(this.view);
    trackingEnabled && startMeasureReactNodeViewRendered({
      nodeTypeName: this.node.type.name
    });
    if (!shouldSkipInitRender) {
      this.renderReactComponent(() => this.render(this.reactComponentProps, this.handleRef));

      // During SSR, renderToStaticMarkup + container.innerHTML (in portal) clobbers the
      // contentDOMWrapper that was appended above. The React ref callback
      // (forwardRef) never fires in renderToStaticMarkup, so contentDOM is
      // left detached. Re-attach it by finding the marked SSR ref target.
      if (isSSR() && isSSRStreaming() && this.domRef) {
        const refTarget = this.domRef.querySelector('[data-ssr-content-dom-ref]');
        if (refTarget) {
          this.handleRef(refTarget);
        }
      }
    }
    trackingEnabled && stopMeasureReactNodeViewRendered({
      nodeTypeName: this.node.type.name,
      dispatchAnalyticsEvent: this.dispatchAnalyticsEvent,
      samplingRate,
      slowThreshold
    });
    return this;
  }

  // Spreading props to pass through dynamic component props
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  renderReactComponent(component) {
    if (!this.domRef || !component) {
      return;
    }
    const componentWithErrorBoundary = () => {
      var _this$node$type$name, _this$node, _this$node$type;
      return /*#__PURE__*/React.createElement(ErrorBoundary, {
        component: ACTION_SUBJECT.REACT_NODE_VIEW,
        componentId: (_this$node$type$name = this === null || this === void 0 ? void 0 : (_this$node = this.node) === null || _this$node === void 0 ? void 0 : (_this$node$type = _this$node.type) === null || _this$node$type === void 0 ? void 0 : _this$node$type.name) !== null && _this$node$type$name !== void 0 ? _this$node$type$name : ACTION_SUBJECT_ID.UNKNOWN_NODE,
        dispatchAnalyticsEvent: this.dispatchAnalyticsEvent
      }, component());
    };
    this.portalProviderAPI.render(componentWithErrorBoundary,
    // Spreading props to pass through dynamic component props
    // Ignored via go/ees005
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    this.domRef, this.key, undefined, this.shouldRenderImmediatelyInPortal);
  }
  createDomRef() {
    if (!this.node.isInline) {
      return document.createElement('div');
    }
    const htmlElement = document.createElement('span');
    return htmlElement;
  }
  getContentDOM() {
    return undefined;
  }
  _handleRef(node) {
    const contentDOM = this.contentDOMWrapper || this.contentDOM;
    // @ts-ignore
    // Spreading props to pass through dynamic component props
    // Ignored via go/ees005
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let oldIgnoreMutation;
    let selectionBookmark;
    let mutationsIgnored = false;

    // move the contentDOM node inside the inner reference after rendering
    if (node && contentDOM && !node.contains(contentDOM)) {
      // @ts-ignore - ignoreMutation may not be declared
      oldIgnoreMutation = this.ignoreMutation; // store ref to previous ignoreMutation

      // ignore all mutations caused by ProseMirror's MutationObserver triggering
      // after DOM change, except selection changes
      // @ts-ignore ProseMirror adds selection type to MutationRecord
      this.ignoreMutation = m => {
        const isSelectionMutation = m.type === 'selection';
        if (!isSelectionMutation) {
          mutationsIgnored = true;
        }
        return !isSelectionMutation;
      };

      // capture document selection state before React DOM changes triggers ProseMirror selection change transaction
      if (this.view.state.selection.visible) {
        selectionBookmark = this.view.state.selection.getBookmark();
      }
      node.appendChild(contentDOM);

      // After the next frame:
      requestAnimationFrame(() => {
        // Restore the original mutation handler
        // @ts-ignore - this may not have been declared by implementing class
        this.ignoreMutation = oldIgnoreMutation;

        // Restore the selection only if:
        // - We have a selection bookmark
        // - Mutations were ignored during the table move
        // - The bookmarked selection is different from the current selection.
        if (selectionBookmark && mutationsIgnored) {
          const resolvedSelection = selectionBookmark.resolve(this.view.state.tr.doc);
          // Don't set the selection if it's the same as the current selection.
          if (!resolvedSelection.eq(this.view.state.selection)) {
            const tr = this.view.state.tr.setSelection(resolvedSelection);
            tr.setMeta('source', 'ReactNodeView:_handleRef:selection-resync');
            this.view.dispatch(tr);
          }
        }
      });
    }
  }

  // Spreading props to pass through dynamic component props
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  render(props, forwardRef) {
    return this.reactComponent ? /*#__PURE__*/React.createElement(this.reactComponent, _extends({
      view: this.view,
      getPos: this.getPos,
      node: this.node,
      forwardRef: forwardRef
      // eslint-disable-next-line react/jsx-props-no-spreading -- Spreading props to pass through dynamic component props
    }, props)) : null;
  }
  update(node, decorations, _innerDecorations, validUpdate = () => true) {
    // @see https://github.com/ProseMirror/prosemirror/issues/648
    const isValidUpdate = this.node.type === node.type && validUpdate(this.node, node);
    this.decorations = decorations;
    if (!isValidUpdate) {
      return false;
    }
    if (this.domRef && !this.node.sameMarkup(node)) {
      this.setDomAttrs(node, this.domRef);
    }

    // View should not process a re-render if this is false.
    // We dont want to destroy the view, so we return true.
    // TODO: ED-13910 - Fix viewShouldUpdate readonly decoration array
    if (!this.viewShouldUpdate(node, decorations)) {
      this.node = node;
      return true;
    }
    this.node = node;
    this.renderReactComponent(() => this.render(this.reactComponentProps, this.handleRef));
    return true;
  }
  viewShouldUpdate(nextNode, _decorations) {
    if (this._viewShouldUpdate) {
      return this._viewShouldUpdate(nextNode);
    }
    return true;
  }

  /**
   * Copies the attributes from a ProseMirror Node to a DOM node.
   * @param node The Prosemirror Node from which to source the attributes
   */
  setDomAttrs(node, element) {
    Object.keys(node.attrs || {}).forEach(attr => {
      element.setAttribute(attr, node.attrs[attr]);
    });
  }
  get dom() {
    // Only return reference if domRef is defined
    if (this.domRef === undefined) {
      //raise an error
      throw new Error('domRef is not defined or may have been destroyed');
    }

    // Spreading props to pass through dynamic component props
    return this.domRef;
  }
  destroy() {
    if (!this.domRef) {
      return;
    }
    this.portalProviderAPI.remove(this.key);
    this.domRef = undefined;
    this.contentDOM = undefined;
  }
  static fromComponent(
  // Spreading props to pass through dynamic component props
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  component, portalProviderAPI, eventDispatcher, props, viewShouldUpdate) {
    return (node, view, getPos) => new ReactNodeView(node, view, getPos, portalProviderAPI, eventDispatcher, props, component, viewShouldUpdate).init();
  }
}