import log from '../utils/log';
import { isAsyncIterable } from '../utils/iterable-utils';
import { parsePropTypes } from './prop-types';
import { COMPONENT_SYMBOL, PROP_TYPES_SYMBOL, DEPRECATED_PROPS_SYMBOL, ASYNC_ORIGINAL_SYMBOL, ASYNC_RESOLVED_SYMBOL, ASYNC_DEFAULTS_SYMBOL } from './constants';
export function createProps(component, propObjects) {
  let extensions;

  for (let i = propObjects.length - 1; i >= 0; i--) {
    const props = propObjects[i];

    if ('extensions' in props) {
      extensions = props.extensions;
    }
  }

  const propsPrototype = getPropsPrototype(component.constructor, extensions);
  const propsInstance = Object.create(propsPrototype);
  propsInstance[COMPONENT_SYMBOL] = component;
  propsInstance[ASYNC_ORIGINAL_SYMBOL] = {};
  propsInstance[ASYNC_RESOLVED_SYMBOL] = {};

  for (let i = 0; i < propObjects.length; ++i) {
    const props = propObjects[i];

    for (const key in props) {
      propsInstance[key] = props[key];
    }
  }

  Object.freeze(propsInstance);
  return propsInstance;
}
const MergedDefaultPropsCacheKey = '_mergedDefaultProps';

function getPropsPrototype(componentClass, extensions) {
  let cacheKey = MergedDefaultPropsCacheKey;

  if (extensions) {
    for (const extension of extensions) {
      const ExtensionClass = extension.constructor;

      if (ExtensionClass) {
        cacheKey += `:${ExtensionClass.extensionName || ExtensionClass.name}`;
      }
    }
  }

  const defaultProps = getOwnProperty(componentClass, cacheKey);

  if (!defaultProps) {
    return componentClass[cacheKey] = createPropsPrototypeAndTypes(componentClass, extensions || []);
  }

  return defaultProps;
}

function createPropsPrototypeAndTypes(componentClass, extensions) {
  const parent = componentClass.prototype;

  if (!parent) {
    return null;
  }

  const parentClass = Object.getPrototypeOf(componentClass);
  const parentDefaultProps = getPropsPrototype(parentClass);
  const componentDefaultProps = getOwnProperty(componentClass, 'defaultProps') || {};
  const componentPropDefs = parsePropTypes(componentDefaultProps);
  const defaultProps = Object.assign(Object.create(null), parentDefaultProps, componentPropDefs.defaultProps);
  const propTypes = Object.assign(Object.create(null), parentDefaultProps === null || parentDefaultProps === void 0 ? void 0 : parentDefaultProps[PROP_TYPES_SYMBOL], componentPropDefs.propTypes);
  const deprecatedProps = Object.assign(Object.create(null), parentDefaultProps === null || parentDefaultProps === void 0 ? void 0 : parentDefaultProps[DEPRECATED_PROPS_SYMBOL], componentPropDefs.deprecatedProps);

  for (const extension of extensions) {
    const extensionDefaultProps = getPropsPrototype(extension.constructor);

    if (extensionDefaultProps) {
      Object.assign(defaultProps, extensionDefaultProps);
      Object.assign(propTypes, extensionDefaultProps[PROP_TYPES_SYMBOL]);
      Object.assign(deprecatedProps, extensionDefaultProps[DEPRECATED_PROPS_SYMBOL]);
    }
  }

  createPropsPrototype(defaultProps, componentClass);
  addAsyncPropsToPropPrototype(defaultProps, propTypes);
  addDeprecatedPropsToPropPrototype(defaultProps, deprecatedProps);
  defaultProps[PROP_TYPES_SYMBOL] = propTypes;
  defaultProps[DEPRECATED_PROPS_SYMBOL] = deprecatedProps;

  if (extensions.length === 0 && !hasOwnProperty(componentClass, '_propTypes')) {
    componentClass._propTypes = propTypes;
  }

  return defaultProps;
}

function createPropsPrototype(defaultProps, componentClass) {
  const id = getComponentName(componentClass);
  Object.defineProperties(defaultProps, {
    id: {
      writable: true,
      value: id
    }
  });
}

function addDeprecatedPropsToPropPrototype(defaultProps, deprecatedProps) {
  for (const propName in deprecatedProps) {
    Object.defineProperty(defaultProps, propName, {
      enumerable: false,

      set(newValue) {
        const nameStr = `${this.id}: ${propName}`;

        for (const newPropName of deprecatedProps[propName]) {
          if (!hasOwnProperty(this, newPropName)) {
            this[newPropName] = newValue;
          }
        }

        log.deprecated(nameStr, deprecatedProps[propName].join('/'))();
      }

    });
  }
}

function addAsyncPropsToPropPrototype(defaultProps, propTypes) {
  const defaultValues = {};
  const descriptors = {};

  for (const propName in propTypes) {
    const propType = propTypes[propName];
    const {
      name,
      value
    } = propType;

    if (propType.async) {
      defaultValues[name] = value;
      descriptors[name] = getDescriptorForAsyncProp(name);
    }
  }

  defaultProps[ASYNC_DEFAULTS_SYMBOL] = defaultValues;
  defaultProps[ASYNC_ORIGINAL_SYMBOL] = {};
  Object.defineProperties(defaultProps, descriptors);
}

function getDescriptorForAsyncProp(name) {
  return {
    enumerable: true,

    set(newValue) {
      if (typeof newValue === 'string' || newValue instanceof Promise || isAsyncIterable(newValue)) {
        this[ASYNC_ORIGINAL_SYMBOL][name] = newValue;
      } else {
        this[ASYNC_RESOLVED_SYMBOL][name] = newValue;
      }
    },

    get() {
      if (this[ASYNC_RESOLVED_SYMBOL]) {
        if (name in this[ASYNC_RESOLVED_SYMBOL]) {
          const value = this[ASYNC_RESOLVED_SYMBOL][name];
          return value || this[ASYNC_DEFAULTS_SYMBOL][name];
        }

        if (name in this[ASYNC_ORIGINAL_SYMBOL]) {
          const state = this[COMPONENT_SYMBOL] && this[COMPONENT_SYMBOL].internalState;

          if (state && state.hasAsyncProp(name)) {
            return state.getAsyncProp(name) || this[ASYNC_DEFAULTS_SYMBOL][name];
          }
        }
      }

      return this[ASYNC_DEFAULTS_SYMBOL][name];
    }

  };
}

function hasOwnProperty(object, prop) {
  return Object.prototype.hasOwnProperty.call(object, prop);
}

function getOwnProperty(object, prop) {
  return hasOwnProperty(object, prop) && object[prop];
}

function getComponentName(componentClass) {
  const componentName = componentClass.componentName;

  if (!componentName) {
    log.warn(`${componentClass.name}.componentName not specified`)();
  }

  return componentName || componentClass.name;
}
//# sourceMappingURL=create-props.js.map