"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getInitialProps = getInitialProps;
exports.getInitialContext = getInitialContext;
exports.createSimpleClassInstance = createSimpleClassInstance;
exports.createClassInstanceForFirstRenderOnly = createClassInstanceForFirstRenderOnly;
exports.createClassInstance = createClassInstance;
exports.evaluateClassConstructor = evaluateClassConstructor;

var _realm = require("../realm.js");

var _index = require("../values/index.js");

var _babelTypes = require("babel-types");

var t = _interopRequireWildcard(_babelTypes);

var _utils = require("./utils");

var _errors = require("./errors.js");

var _index2 = require("../methods/index.js");

var _singletons = require("../singletons.js");

var _invariant = require("../invariant.js");

var _invariant2 = _interopRequireDefault(_invariant);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

/**
 * Copyright (c) 2017-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

const lifecycleMethods = new Set(["componentWillUnmount", "componentDidMount", "componentWillMount", "componentDidUpdate", "componentWillUpdate", "componentDidCatch", "componentWillReceiveProps"]);

const whitelistedProperties = new Set(["props", "context", "refs", "setState"]);

function getInitialProps(realm, componentType) {
  let propsName = null;
  if (componentType !== null) {
    if ((0, _utils.valueIsClassComponent)(realm, componentType)) {
      propsName = "this.props";
    } else {
      // otherwise it's a functional component, where the first paramater of the function is "props" (if it exists)
      if (componentType.$FormalParameters.length > 0) {
        let firstParam = componentType.$FormalParameters[0];
        if (t.isIdentifier(firstParam)) {
          propsName = firstParam.name;
        }
      }
    }
  }
  let value = _index.AbstractValue.createAbstractObject(realm, propsName || "props");
  // props objects don't have a key and ref, so we remove them
  (0, _utils.deleteRefAndKeyFromProps)(realm, value);
  (0, _invariant2.default)(value instanceof _index.AbstractObjectValue);
  return value;
}

function getInitialContext(realm, componentType) {
  let contextName = null;
  if ((0, _utils.valueIsClassComponent)(realm, componentType)) {
    // it's a class component, so we need to check the type on for context of the component prototype
    let superTypeParameters = componentType.$SuperTypeParameters;
    contextName = "this.context";

    if (superTypeParameters !== undefined) {
      throw new _errors.ExpectedBailOut("context on class components not yet supported");
    }
  } else {
    // otherwise it's a functional component, where the second paramater of the function is "context" (if it exists)
    if (componentType.$FormalParameters.length > 1) {
      let secondParam = componentType.$FormalParameters[1];
      if (t.isIdentifier(secondParam)) {
        contextName = secondParam.name;
      }
    }
  }
  let value = _index.AbstractValue.createAbstractObject(realm, contextName || "context");
  return value;
}

function createSimpleClassInstance(realm, componentType, props, context) {
  let componentPrototype = (0, _index2.Get)(realm, componentType, "prototype");
  (0, _invariant2.default)(componentPrototype instanceof _index.ObjectValue);
  // create an instance object and disable serialization as we don't want to output the internals we set below
  let instance = new _index.ObjectValue(realm, componentPrototype, "this", true);
  let allowedPropertyAccess = new Set(["props", "context"]);
  for (let [name] of componentPrototype.properties) {
    if (lifecycleMethods.has(name)) {
      // this error will result in the simple class falling back to a complex class
      throw new _errors.SimpleClassBailOut("lifecycle methods are not supported on simple classes");
    } else if (name !== "constructor") {
      allowedPropertyAccess.add(name);
      _singletons.Properties.Set(realm, instance, name, (0, _index2.Get)(realm, componentPrototype, name), true);
    }
  }
  // assign props
  _singletons.Properties.Set(realm, instance, "props", props, true);
  // assign context
  _singletons.Properties.Set(realm, instance, "context", context, true);
  // as this object is simple, we want to check if any access to anything other than
  // "this.props" or "this.context" or methods on the class occur
  let $GetOwnProperty = instance.$GetOwnProperty;
  instance.$GetOwnProperty = P => {
    if (!allowedPropertyAccess.has(P)) {
      // this error will result in the simple class falling back to a complex class
      throw new _errors.SimpleClassBailOut("access to basic class instance properties is not supported on simple classes");
    }
    return $GetOwnProperty.call(instance, P);
  };
  // enable serialization to support simple instance variables properties
  instance.refuseSerialization = false;
  // return the instance
  return instance;
}

function deeplyApplyInstancePrototypeProperties(realm, instance, componentPrototype, classMetadata) {
  let { instanceProperties, instanceSymbols } = classMetadata;
  let proto = componentPrototype.$Prototype;

  if (proto instanceof _index.ObjectValue && proto !== realm.intrinsics.ObjectPrototype) {
    deeplyApplyInstancePrototypeProperties(realm, instance, proto, classMetadata);
  }

  for (let [name] of componentPrototype.properties) {
    // ensure we don't set properties that were defined on the instance
    if (name !== "constructor" && !instanceProperties.has(name)) {
      _singletons.Properties.Set(realm, instance, name, (0, _index2.Get)(realm, componentPrototype, name), true);
    }
  }
  for (let [symbol] of componentPrototype.symbols) {
    // ensure we don't set symbols that were defined on the instance
    if (!instanceSymbols.has(symbol)) {
      _singletons.Properties.Set(realm, instance, symbol, (0, _index2.Get)(realm, componentPrototype, symbol), true);
    }
  }
}

function createClassInstanceForFirstRenderOnly(realm, componentType, props, context, evaluatedNode) {
  let instance = (0, _utils.getValueFromFunctionCall)(realm, componentType, realm.intrinsics.undefined, [props, context], true);
  (0, _invariant2.default)(instance instanceof _index.ObjectValue);
  instance.refuseSerialization = true;
  // assign props
  _singletons.Properties.Set(realm, instance, "props", props, true);
  // assign context
  _singletons.Properties.Set(realm, instance, "context", context, true);
  // assign a mocked setState
  let setState = new _index.NativeFunctionValue(realm, undefined, `setState`, 1, (_context, [state, callback]) => {
    let stateToUpdate = state;
    (0, _invariant2.default)(instance instanceof _index.ObjectValue);
    let currentState = (0, _index2.Get)(realm, instance, "state");
    (0, _invariant2.default)(currentState instanceof _index.ObjectValue);

    if (state instanceof _index.ECMAScriptSourceFunctionValue && state.$Call) {
      stateToUpdate = state.$Call(instance, [currentState]);
    }
    if (stateToUpdate instanceof _index.ObjectValue) {
      for (let [key, binding] of stateToUpdate.properties) {
        if (binding && binding.descriptor && binding.descriptor.enumerable) {
          let value = (0, _utils.getProperty)(realm, stateToUpdate, key);
          _singletons.Properties.Set(realm, currentState, key, value, true);
        }
      }
    }
    if (callback instanceof _index.ECMAScriptSourceFunctionValue && callback.$Call) {
      callback.$Call(instance, []);
    }
    return realm.intrinsics.undefined;
  });
  setState.intrinsicName = "(function() {})";
  _singletons.Properties.Set(realm, instance, "setState", setState, true);

  instance.refuseSerialization = false;
  return instance;
}

function createClassInstance(realm, componentType, props, context, classMetadata) {
  let componentPrototype = (0, _index2.Get)(realm, componentType, "prototype");
  (0, _invariant2.default)(componentPrototype instanceof _index.ObjectValue);
  // create an instance object and disable serialization as we don't want to output the internals we set below
  let instance = new _index.ObjectValue(realm, componentPrototype, "this", true);
  deeplyApplyInstancePrototypeProperties(realm, instance, componentPrototype, classMetadata);

  // assign refs
  _singletons.Properties.Set(realm, instance, "refs", _index.AbstractValue.createAbstractObject(realm, "this.refs"), true);
  // assign props
  _singletons.Properties.Set(realm, instance, "props", props, true);
  // assign context
  _singletons.Properties.Set(realm, instance, "context", context, true);
  // enable serialization to support simple instance variables properties
  instance.refuseSerialization = false;
  // return the instance in an abstract object
  let value = _index.AbstractValue.createAbstractObject(realm, "this", instance);
  (0, _invariant2.default)(value instanceof _index.AbstractObjectValue);
  return value;
}

function evaluateClassConstructor(realm, constructorFunc, props, context) {
  let instanceProperties = new Set();
  let instanceSymbols = new Set();

  realm.evaluatePure(() => realm.evaluateForEffects(() => {
    let instance = (0, _index2.Construct)(realm, constructorFunc, [props, context]);
    (0, _invariant2.default)(instance instanceof _index.ObjectValue);
    for (let [propertyName] of instance.properties) {
      if (!whitelistedProperties.has(propertyName)) {
        instanceProperties.add(propertyName);
      }
    }
    for (let [symbol] of instance.symbols) {
      instanceSymbols.add(symbol);
    }
    return instance;
  },
  /*state*/null, `react component constructor: ${constructorFunc.getName()}`));

  return {
    instanceProperties,
    instanceSymbols
  };
}
//# sourceMappingURL=components.js.map