import * as React from "react";

type ElementProps = {
  type: string;
  style: any;
  props: any;
  elements?: ElementProps[];
  key: string;
};

export function mapElementWithKey(fn) {
  return function (element, key) {
    return fn({ ...element, key });
  };
}

// Initialize these outside of the function so they only get created once
const focusableTypes = new Set([
  "View",
  "ButtonContainerView",
  "FocusableView",
  "CollapsibleTextContainer",
  "BorderContainerView",
]);

const cellPlayerTypes = new Set([
  "Image",
  "LiveImage",
  "Button",
  "DynamicBadge",
]);

/**
 * Maps a provided node in a master cell configuration to a React Component tree with appropriate props & styles
 * Curried function of the form elementMapper(components)(element)
 * @param {Object} components map of primitive React components to use to render the elements
 * @param {Object} element element in the current master cell configuration
 * @param {String} element.type type of the component to render. Should match an entry in the components map
 * @param {Object} element.style style object for the component
 * @param {Object} element.props React props to pass to the component
 * @param {?[Object]} element.elements Optional array of nested elements to render within the current node
 * @returns {Object} React component
 */
export function elementMapper(
  components,
  otherProps?: {
    skipButtons?: boolean;
    cellUUID?: string;
    [key: string]: any;
    emitAsyncElementRegistrate?: () => void;
    emitAsyncElementLayout?: () => void;
  }
) {
  return function Element({
    type,
    style,
    props,
    elements = [],
    key,
  }: ElementProps) {
    const propsForCellPlayer = cellPlayerTypes.has(type)
      ? {
          cellUUID: otherProps?.cellUUID,
        }
      : {};

    const propsForFocusable = focusableTypes.has(type)
      ? {
          ...otherProps,
          // eslint-disable-next-line react/prop-types
          preferredFocus: otherProps?.preferredFocus && props?.preferredFocus,
        }
      : {};

    const componentProps = {
      style,
      skipButtons: otherProps?.skipButtons,
      emitAsyncElementRegistrate: otherProps?.emitAsyncElementRegistrate,
      emitAsyncElementLayout: otherProps?.emitAsyncElementLayout,
      ...props,
      ...propsForFocusable,
      ...propsForCellPlayer,
    };

    const { hidden } = componentProps;

    if (hidden) return null;

    const Component = components[type];
    const fn = mapElementWithKey(elementMapper(components, otherProps));

    return (
      <Component key={key} {...componentProps}>
        {focusableTypes.has(type) && elements.length > 0
          ? elements.map(fn)
          : null}
      </Component>
    );
  };
}
