import { memo, useRef, useEffect } from 'react';
import { useRemoteReceived } from './hooks/remote-received.mjs';
import { usePropsForRemoteElement } from './hooks/props-for-element.mjs';
import { REMOTE_ELEMENT_PROP, REMOTE_ELEMENT_ATTACHED_PROP } from './constants.mjs';
import { jsx } from 'react/jsx-runtime';

/**
 * Takes a React component, and returns a new component that can be used to render that
 * component from a remote element. This wrapper will handle subscribing to changes in that
 * element, and will normalize the properties on that remote element before passing them
 * to your React component.
 */
function createRemoteComponentRenderer(Component, {
  name,
  eventProps
} = {}) {
  const RemoteComponentRenderer = /*#__PURE__*/memo(function RemoteComponentRenderer({
    element,
    receiver,
    components
  }) {
    const internalsRef = useRef();
    const attachedElement = useRemoteReceived(element, receiver);
    const resolvedElement = attachedElement ?? element;
    const resolvedId = resolvedElement.id;
    const props = usePropsForRemoteElement(resolvedElement, {
      receiver,
      components,
      eventProps
    });
    props[REMOTE_ELEMENT_PROP] = resolvedElement;
    props[REMOTE_ELEMENT_ATTACHED_PROP] = attachedElement != null;
    if (internalsRef.current == null) {
      const internals = {
        id: resolvedId,
        receiver
      };
      internals.instanceRef = createImplementationRef(internals);
      internalsRef.current = internals;
    }
    internalsRef.current.id = resolvedId;
    internalsRef.current.receiver = receiver;
    useEffect(() => {
      const node = {
        id: resolvedId
      };
      receiver.implement(node, internalsRef.current?.instanceRef.current);
      return () => {
        receiver.implement(node, null);
      };
    }, [resolvedId, receiver]);
    return /*#__PURE__*/jsx(Component, {
      ref: internalsRef.current.instanceRef,
      ...props
    });
  });
  RemoteComponentRenderer.displayName = name ?? `RemoteComponentRenderer(${Component.displayName ?? Component.name ?? 'Component'})`;
  return RemoteComponentRenderer;
}
function createImplementationRef(internals) {
  let current = null;
  return {
    get current() {
      return current;
    },
    set current(implementation) {
      current = implementation;
      internals.receiver.implement(internals, implementation);
    }
  };
}

export { createRemoteComponentRenderer };
