"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.Reconciler = undefined;

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

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

var _types = require("../serializer/types.js");

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

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

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

var _invariant2 = _interopRequireDefault(_invariant);

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

var _branching = require("./branching.js");

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

var t = _interopRequireWildcard(_babelTypes);

var _components = require("./components.js");

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

var _completions = require("../completions.js");

var _logger = require("../utils/logger.js");

var _utils2 = require("../intrinsics/prepack/utils.js");

var _elements = require("./elements.js");

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; } }

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

class Reconciler {
  constructor(realm, logger, statistics, reactSerializerState, componentTreeConfig) {
    this.realm = realm;
    this.statistics = statistics;
    this.reactSerializerState = reactSerializerState;
    this.logger = logger;
    this.componentTreeState = this._createComponentTreeState();
    this.alreadyEvaluatedRootNodes = new Map();
    this.alreadyEvaluatedNestedClosures = new Set();
    this.componentTreeConfig = componentTreeConfig;
    this.nestedOptimizedClosures = [];
    this.branchedComponentTrees = [];
  }

  renderReactComponentTree(componentType, props, context, evaluatedRootNode) {
    let failed = false;
    const renderComponentTree = () => {
      // initialProps and initialContext are created from Flow types from:
      // - if a functional component, the 1st and 2nd paramater of function
      // - if a class component, use this.props and this.context
      // if there are no Flow types for props or context, we will throw a
      // FatalError, unless it's a functional component that has no paramater
      // i.e let MyComponent = () => <div>Hello world</div>
      try {
        let initialProps = props || (0, _components.getInitialProps)(this.realm, componentType);
        let initialContext = context || (0, _components.getInitialContext)(this.realm, componentType);
        let { result } = this._renderComponent(componentType, initialProps, initialContext, "ROOT", null, evaluatedRootNode);
        this.statistics.optimizedTrees++;
        this.alreadyEvaluatedRootNodes.set(componentType, evaluatedRootNode);
        return result;
      } catch (error) {
        if (error.name === "Invariant Violation") {
          throw error;
        }
        if (error instanceof _errors2.ExpectedBailOut) {
          this.logger.logWarning(componentType, `__optimizeReactComponentTree() React component tree failed due expected bail-out - ${error.message}`);
          evaluatedRootNode.message = error.message;
          evaluatedRootNode.status = "BAIL-OUT";
        } else if (error instanceof _completions.AbruptCompletion) {
          this.logger.logWarning(componentType, `__optimizeReactComponentTree() React component tree failed due runtime runtime exception thrown`);
          evaluatedRootNode.status = "ABRUPT_COMPLETION";
        } else {
          this.logger.logWarning(componentType, `__optimizeReactComponentTree() React component tree failed due to - ${error.message}`);
          evaluatedRootNode.message = "evaluation failed on new component tree branch";
          evaluatedRootNode.status = "BAIL-OUT";
        }
        failed = true;
        return this.realm.intrinsics.undefined;
      }
    };

    let effects = this.realm.wrapInGlobalEnv(() => this.realm.evaluatePure(() =>
    // TODO: (sebmarkbage): You could use the return value of this to detect if there are any mutations on objects other
    // than newly created ones. Then log those to the error logger. That'll help us track violations in
    // components. :)
    this.realm.evaluateForEffects(renderComponentTree,
    /*state*/null, `react component: ${componentType.getName()}`)));
    this._handleNestedOptimizedClosuresFromEffects(effects, evaluatedRootNode);
    if (failed) {
      return null;
    }
    return effects;
  }

  _handleNestedOptimizedClosuresFromEffects(effects, evaluatedNode) {
    for (let _ref of this.nestedOptimizedClosures) {
      let { nestedEffects } = _ref;

      if (nestedEffects.length === 0) {
        nestedEffects.push(...nestedEffects, effects);
      }
    }
  }

  renderNestedOptimizedClosure(func, nestedEffects, componentType, context, branchState, evaluatedNode) {
    let failed = false;
    const renderOptimizedClosure = () => {
      let baseObject = this.realm.$GlobalEnv.environmentRecord.WithBaseObject();
      // we want to optimize the function that is bound
      if (func instanceof _index.BoundFunctionValue) {
        // we want to set the "this" to be the bound object
        // for firstRender this will optimize the function
        // for updates, "this" will be intrinsic, so either way
        // they should both work
        baseObject = func.$BoundThis;
        (0, _invariant2.default)(func.$BoundTargetFunction instanceof _index.ECMAScriptSourceFunctionValue);
        func = func.$BoundTargetFunction;
      }
      let numArgs = func.getLength();
      let args = [];
      let targetFunc = func;

      this.alreadyEvaluatedNestedClosures.add(func);
      (0, _invariant2.default)(targetFunc instanceof _index.ECMAScriptSourceFunctionValue);
      let params = targetFunc.$FormalParameters;
      if (numArgs && numArgs > 0 && params) {
        for (let parameterId of params) {
          if (t.isIdentifier(parameterId)) {
            // Create an AbstractValue similar to __abstract being called
            args.push((0, _utils2.createAbstractArgument)(this.realm, parameterId.name, targetFunc.expressionLocation));
          } else {
            this.realm.handleError(new _errors.CompilerDiagnostic("Non-identifier args to additional functions unsupported", targetFunc.expressionLocation, "PP1005", "FatalError"));
            throw new _errors.FatalError("Non-identifier args to additional functions unsupported");
          }
        }
      }
      try {
        (0, _invariant2.default)(baseObject instanceof _index.ObjectValue || baseObject instanceof _index.AbstractObjectValue || baseObject instanceof _index.UndefinedValue);
        let value = (0, _utils.getValueFromFunctionCall)(this.realm, func, baseObject, args);
        (0, _invariant2.default)(componentType instanceof _index.Value);
        (0, _invariant2.default)(context instanceof _index.ObjectValue || context instanceof _index.AbstractObjectValue);
        let result = this._resolveDeeply(componentType, value, context, "NEW_BRANCH", branchState, evaluatedNode);
        this.statistics.optimizedNestedClosures++;
        return result;
      } catch (error) {
        if (error.name === "Invariant Violation") {
          throw error;
        }
        failed = true;
        return this.realm.intrinsics.undefined;
      }
    };

    let effects = this.realm.wrapInGlobalEnv(() => this.realm.evaluatePure(() => this.realm.evaluateForEffects(() => (0, _utils.evaluateWithNestedEffects)(this.realm, nestedEffects, renderOptimizedClosure),
    /*state*/null, `react nested optimized closure`)));
    this._handleNestedOptimizedClosuresFromEffects(effects, evaluatedNode);
    if (failed) {
      return null;
    }
    return effects;
  }

  clearComponentTreeState() {
    this.componentTreeState = this._createComponentTreeState();
  }

  _queueOptimizedClosure(func, evaluatedNode, componentType, context, branchState) {
    this.nestedOptimizedClosures.push({
      evaluatedNode,
      func,
      nestedEffects: [],
      componentType,
      context,
      branchState
    });
  }

  _queueNewComponentTree(rootValue, evaluatedNode, props = null, context = null) {
    (0, _invariant2.default)(rootValue instanceof _index.ECMAScriptSourceFunctionValue || rootValue instanceof _index.AbstractValue);
    this.componentTreeState.deadEnds++;
    this.branchedComponentTrees.push({
      context,
      evaluatedNode,
      props,
      rootValue
    });
  }

  _renderComplexClassComponent(componentType, props, context, classMetadata, branchStatus, branchState, evaluatedNode) {
    if (branchStatus !== "ROOT") {
      // if the tree is simple and we're not in a branch, we can make this tree complex
      // and make this complex component the root
      let evaluatedComplexNode = this.alreadyEvaluatedRootNodes.get(componentType);
      if (branchStatus === "NO_BRANCH" && this.componentTreeState.status === "SIMPLE" && evaluatedComplexNode && evaluatedComplexNode.status !== "RENDER_PROPS") {
        this.componentTreeState.componentType = componentType;
      } else {
        this._queueNewComponentTree(componentType, evaluatedNode);
        evaluatedNode.status = "NEW_TREE";
        throw new _errors2.NewComponentTreeBranch();
      }
    }
    this.componentTreeState.status = "COMPLEX";
    // create a new instance of this React class component
    let instance = (0, _components.createClassInstance)(this.realm, componentType, props, context, classMetadata);
    // get the "render" method off the instance
    let renderMethod = (0, _index2.Get)(this.realm, instance, "render");
    (0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue);
    // the render method doesn't have any arguments, so we just assign the context of "this" to be the instance
    return (0, _utils.getValueFromFunctionCall)(this.realm, renderMethod, instance, []);
  }

  _renderSimpleClassComponent(componentType, props, context, branchStatus, branchState, evaluatedNode) {
    // create a new simple instance of this React class component
    let instance = (0, _components.createSimpleClassInstance)(this.realm, componentType, props, context);
    // get the "render" method off the instance
    let renderMethod = (0, _index2.Get)(this.realm, instance, "render");
    (0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue);
    // the render method doesn't have any arguments, so we just assign the context of "this" to be the instance
    return (0, _utils.getValueFromFunctionCall)(this.realm, renderMethod, instance, []);
  }

  _renderFunctionalComponent(componentType, props, context, evaluatedNode) {
    return (0, _utils.getValueFromFunctionCall)(this.realm, componentType, this.realm.intrinsics.undefined, [props, context]);
  }

  _getClassComponentMetadata(componentType, props, context) {
    if (this.realm.react.classComponentMetadata.has(componentType)) {
      let classMetadata = this.realm.react.classComponentMetadata.get(componentType);
      (0, _invariant2.default)(classMetadata);
      return classMetadata;
    }
    // get all this assignments in the constructor
    let classMetadata = (0, _components.evaluateClassConstructor)(this.realm, componentType, props, context);
    this.realm.react.classComponentMetadata.set(componentType, classMetadata);
    return classMetadata;
  }

  _resolveContextProviderComponent(componentType, reactElement, context, branchStatus, branchState, evaluatedNode) {
    let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");

    let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NORMAL", "Context.Provider");
    evaluatedNode.children.push(evaluatedChildNode);
    this.statistics.componentsEvaluated++;
    (0, _invariant2.default)(typeValue instanceof _index.ObjectValue || typeValue instanceof _index.AbstractObjectValue);
    const contextConsumer = (0, _utils.getProperty)(this.realm, typeValue, "context");
    (0, _invariant2.default)(contextConsumer instanceof _index.ObjectValue || contextConsumer instanceof _index.AbstractObjectValue);
    let lastValueProp = (0, _utils.getProperty)(this.realm, contextConsumer, "currentValue");
    this._incremementReferenceForContextNode(contextConsumer);

    const setContextCurrentValue = value => {
      if (value instanceof _index.Value) {
        // update the currentValue
        (0, _utils.setProperty)(contextConsumer, "currentValue", value);
      }
    };
    // if we have a value prop, set it
    if (propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue) {
      let valueProp = (0, _index2.Get)(this.realm, propsValue, "value");
      setContextCurrentValue(valueProp);
    }
    if (this.componentTreeConfig.firstRenderOnly) {
      if (propsValue instanceof _index.ObjectValue) {
        this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedChildNode);
        setContextCurrentValue(lastValueProp);
        this._decremementReferenceForContextNode(contextConsumer);
        // if we no dead ends, we know the rest of the tree and can safely remove the provider
        if (this.componentTreeState.deadEnds === 0) {
          let childrenValue = (0, _index2.Get)(this.realm, propsValue, "children");
          evaluatedChildNode.status = "INLINED";
          this.statistics.inlinedComponents++;
          return childrenValue;
        }
        return reactElement;
      }
    }
    let children = this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedChildNode);
    setContextCurrentValue(lastValueProp);
    this._decremementReferenceForContextNode(contextConsumer);
    return children;
  }

  _decremementReferenceForContextNode(contextNode) {
    let references = this.componentTreeState.contextNodeReferences.get(contextNode);
    if (!references) {
      references = 0;
    } else {
      references--;
    }
    this.componentTreeState.contextNodeReferences.set(contextNode, references);
  }

  _incremementReferenceForContextNode(contextNode) {
    let references = this.componentTreeState.contextNodeReferences.get(contextNode);
    if (!references) {
      references = 1;
    } else {
      references++;
    }
    this.componentTreeState.contextNodeReferences.set(contextNode, references);
  }

  _hasReferenceForContextNode(contextNode) {
    if (this.componentTreeState.contextNodeReferences.has(contextNode)) {
      let references = this.componentTreeState.contextNodeReferences.get(contextNode);
      if (!references) {
        return false;
      }
      return references > 0;
    }
    return false;
  }

  _resolveContextConsumerComponent(componentType, reactElement, context, branchState, evaluatedNode) {
    let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");
    let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("RENDER_PROPS", "Context.Consumer");
    evaluatedNode.children.push(evaluatedChildNode);

    if (propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue) {
      // get the "render" prop child off the instance
      if (propsValue instanceof _index.ObjectValue && propsValue.properties.has("children")) {
        let renderProp = (0, _utils.getProperty)(this.realm, propsValue, "children");

        this._findReactComponentTrees(propsValue, evaluatedChildNode, "NORMAL_FUNCTIONS");
        if (renderProp instanceof _index.ECMAScriptSourceFunctionValue) {
          if (this.componentTreeConfig.firstRenderOnly) {
            if (typeValue instanceof _index.ObjectValue || typeValue instanceof _index.AbstractObjectValue) {
              // make sure this context is in our tree
              if (this._hasReferenceForContextNode(typeValue)) {
                let valueProp = (0, _index2.Get)(this.realm, typeValue, "currentValue");
                let result = (0, _utils.getValueFromFunctionCall)(this.realm, renderProp, this.realm.intrinsics.undefined, [valueProp]);
                this.statistics.inlinedComponents++;
                this.statistics.componentsEvaluated++;
                evaluatedChildNode.status = "INLINED";
                return result;
              }
            }
          }
          this._queueOptimizedClosure(renderProp, evaluatedChildNode, componentType, context, branchState);
          return;
        } else {
          this._findReactComponentTrees(renderProp, evaluatedChildNode, "NESTED_CLOSURES");
        }
      }
    }
    this.componentTreeState.deadEnds++;
    return;
  }

  _resolveRelayQueryRendererComponent(componentType, reactElement, context, branchState, evaluatedNode) {
    let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");

    let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("RENDER_PROPS", (0, _utils.getComponentName)(this.realm, typeValue));
    evaluatedNode.children.push(evaluatedChildNode);

    if (propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue) {
      // get the "render" prop
      if (propsValue instanceof _index.ObjectValue && propsValue.properties.has("render")) {
        let renderProp = (0, _utils.getProperty)(this.realm, propsValue, "render");

        if (renderProp instanceof _index.ECMAScriptSourceFunctionValue) {
          this._queueOptimizedClosure(renderProp, evaluatedChildNode, componentType, context, branchState);
        } else if (renderProp instanceof _index.AbstractValue) {
          this._findReactComponentTrees(renderProp, evaluatedChildNode, "NESTED_CLOSURES", componentType, context, branchState);
        }
      }
      this._findReactComponentTrees(propsValue, evaluatedChildNode, "NORMAL_FUNCTIONS");
      return;
    }
    // this is the worst case, we were unable to find the render prop function
    // and won't be able to find any further components to evaluate as trees
    // because of that
    this.componentTreeState.deadEnds++;
    return;
  }

  _renderClassComponent(componentType, props, context, branchStatus, branchState, evaluatedNode) {
    let value;

    let classMetadata = this._getClassComponentMetadata(componentType, props, context);
    let { instanceProperties, instanceSymbols } = classMetadata;

    // if there were no this assignments we can try and render it as a simple class component
    if (instanceProperties.size === 0 && instanceSymbols.size === 0) {
      // We first need to know what type of class component we're dealing with.
      // A "simple" class component is defined as:
      //
      // - having only a "render" method
      // - having no lifecycle events
      // - having no state
      // - having no instance variables
      //
      // the only things a class component should be able to access on "this" are:
      // - this.props
      // - this.context
      // - this._someRenderMethodX() etc
      //
      // Otherwise, the class component is a "complex" one.
      // To begin with, we don't know what type of component it is, so we try and render it as if it were
      // a simple component using the above heuristics. If an error occurs during this process, we assume
      // that the class wasn't simple, then try again with the "complex" heuristics.
      try {
        value = this._renderSimpleClassComponent(componentType, props, context, branchStatus, branchState, evaluatedNode);
      } catch (error) {
        // if we get back a SimpleClassBailOut error, we know that this class component
        // wasn't a simple one and is likely to be a complex class component instead
        if (error instanceof _errors2.SimpleClassBailOut) {
          // the component was not simple, so we continue with complex case
        } else {
          // else we rethrow the error
          throw error;
        }
      }
    }
    // handle the complex class component if there is not value
    if (value === undefined) {
      value = this._renderComplexClassComponent(componentType, props, context, classMetadata, branchStatus, branchState, evaluatedNode);
    }
    return value;
  }

  _renderClassComponentForFirstRenderOnly(componentType, props, context, branchStatus, branchState, evaluatedNode) {
    // create a new simple instance of this React class component
    let instance = (0, _components.createClassInstanceForFirstRenderOnly)(this.realm, componentType, props, context, evaluatedNode);
    // get the "componentWillMount" and "render" methods off the instance
    let componentWillMount = (0, _index2.Get)(this.realm, instance, "componentWillMount");
    let renderMethod = (0, _index2.Get)(this.realm, instance, "render");

    if (componentWillMount instanceof _index.ECMAScriptSourceFunctionValue && componentWillMount.$Call) {
      componentWillMount.$Call(instance, []);
    }
    (0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue);
    return (0, _utils.getValueFromFunctionCall)(this.realm, renderMethod, instance, []);
  }

  _renderComponent(componentType, props, context, branchStatus, branchState, evaluatedNode) {
    this.statistics.componentsEvaluated++;
    if ((0, _utils.valueIsKnownReactAbstraction)(this.realm, componentType)) {
      (0, _invariant2.default)(componentType instanceof _index.AbstractValue);
      this._queueNewComponentTree(componentType, evaluatedNode);
      evaluatedNode.status = "NEW_TREE";
      evaluatedNode.message = "RelayContainer";
      throw new _errors2.NewComponentTreeBranch();
    }
    (0, _invariant2.default)(componentType instanceof _index.ECMAScriptSourceFunctionValue);
    let value;
    let childContext = context;

    // first we check if it's a legacy class component
    if ((0, _utils.valueIsLegacyCreateClassComponent)(this.realm, componentType)) {
      throw new _errors2.ExpectedBailOut("components created with create-react-class are not supported");
    } else if ((0, _utils.valueIsClassComponent)(this.realm, componentType)) {
      if (this.componentTreeConfig.firstRenderOnly) {
        value = this._renderClassComponentForFirstRenderOnly(componentType, props, context, branchStatus, branchState, evaluatedNode);
      } else {
        value = this._renderClassComponent(componentType, props, context, branchStatus, branchState, evaluatedNode);
      }
    } else {
      value = this._renderFunctionalComponent(componentType, props, context, evaluatedNode);
      if ((0, _utils.valueIsFactoryClassComponent)(this.realm, value)) {
        (0, _invariant2.default)(value instanceof _index.ObjectValue);
        if (branchStatus !== "ROOT") {
          throw new _errors2.ExpectedBailOut("non-root factory class components are not suppoted");
        } else {
          // TODO support factory components
          return {
            result: value,
            childContext
          };
        }
      }
    }
    (0, _invariant2.default)(value !== undefined);
    return {
      result: this._resolveDeeply(componentType, value, context, branchStatus === "ROOT" ? "NO_BRANCH" : branchStatus, branchState, evaluatedNode),
      childContext
    };
  }

  _createComponentTreeState() {
    return {
      componentType: undefined,
      deadEnds: 0,
      status: "SIMPLE",
      contextNodeReferences: new Map()
    };
  }

  _getComponentResolutionStrategy(value) {
    // check if it's a ReactRelay.QueryRenderer
    if (this.realm.fbLibraries.reactRelay !== undefined) {
      let QueryRenderer = (0, _utils.getProperty)(this.realm, this.realm.fbLibraries.reactRelay, "QueryRenderer");
      if (value === QueryRenderer) {
        return "RELAY_QUERY_RENDERER";
      }
    }
    if (value === (0, _utils.getReactSymbol)("react.fragment", this.realm)) {
      return "FRAGMENT";
    }
    if ((value instanceof _index.ObjectValue || value instanceof _index.AbstractObjectValue) && value.kind !== "conditional") {
      let $$typeof = (0, _index2.Get)(this.realm, value, "$$typeof");

      if ($$typeof === (0, _utils.getReactSymbol)("react.context", this.realm)) {
        return "CONTEXT_CONSUMER";
      }
      if ($$typeof === (0, _utils.getReactSymbol)("react.provider", this.realm)) {
        return "CONTEXT_PROVIDER";
      }
    }
    return "NORMAL";
  }

  _resolveAbstractValue(componentType, value, context, branchStatus, branchState, evaluatedNode) {
    let length = value.args.length;
    if (length > 0) {
      let newBranchState = new _branching.BranchState();
      // TODO investigate what other kinds than "conditional" might be safe to deeply resolve
      for (let i = 0; i < length; i++) {
        value.args[i] = this._resolveDeeply(componentType, value.args[i], context, "NEW_BRANCH", newBranchState, evaluatedNode);
      }
      let didBranch = newBranchState.applyBranchedLogic(this.realm, this.reactSerializerState);
      if (didBranch && branchState !== null) {
        branchState.mergeBranchedLogic(newBranchState);
      }
    } else {
      this.componentTreeState.deadEnds++;
    }
    return value;
  }

  _resolveBranchedComponentType(componentType, reactElement, context, branchStatus, branchState, evaluatedNode) {
    let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");
    let keyValue = (0, _utils.getProperty)(this.realm, reactElement, "key");
    let refValue = (0, _utils.getProperty)(this.realm, reactElement, "ref");

    const resolveBranch = abstract => {
      (0, _invariant2.default)(abstract.kind === "conditional", "the reconciler tried to resolve a non conditional abstract");
      let condition = abstract.args[0];
      (0, _invariant2.default)(condition instanceof _index.AbstractValue);
      let left = abstract.args[1];
      let right = abstract.args[2];

      if (left instanceof _index.AbstractValue && left.kind === "conditional") {
        left = resolveBranch(left);
      } else {
        (0, _invariant2.default)(propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractValue);
        left = (0, _elements.createInternalReactElement)(this.realm, left, keyValue, refValue, propsValue);
      }
      if (right instanceof _index.AbstractValue && right.kind === "conditional") {
        right = resolveBranch(right);
      } else {
        (0, _invariant2.default)(propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractValue);
        right = (0, _elements.createInternalReactElement)(this.realm, right, keyValue, refValue, propsValue);
      }
      let val = _index.AbstractValue.createFromConditionalOp(this.realm, condition, left, right);
      (0, _invariant2.default)(val instanceof _index.AbstractValue);
      return val;
    };
    (0, _invariant2.default)(typeValue instanceof _index.AbstractValue);

    return this._resolveAbstractValue(componentType, resolveBranch(typeValue), context, branchStatus, branchState, evaluatedNode);
  }

  _resolveUnknownComponentType(reactElement, evaluatedNode) {
    let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");

    this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
    if (typeValue instanceof _index.AbstractValue) {
      this._findReactComponentTrees(typeValue, evaluatedNode, "FUNCTIONAL_COMPONENTS");
      return reactElement;
    } else {
      let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("BAIL-OUT", (0, _utils.getComponentName)(this.realm, typeValue));
      evaluatedNode.children.push(evaluatedChildNode);
      let bailOutMessage = `type on <Component /> was not a ECMAScriptSourceFunctionValue`;
      evaluatedChildNode.message = bailOutMessage;
      this._assignBailOutMessage(reactElement, bailOutMessage);
      this.componentTreeState.deadEnds++;
      return reactElement;
    }
  }

  _resolveReactElementBadRef(reactElement, evaluatedNode) {
    let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");

    let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("BAIL-OUT", (0, _utils.getComponentName)(this.realm, typeValue));
    evaluatedNode.children.push(evaluatedChildNode);
    let bailOutMessage = `refs are not supported on <Components />`;
    evaluatedChildNode.message = bailOutMessage;

    this._queueNewComponentTree(typeValue, evaluatedChildNode);
    this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
    this._assignBailOutMessage(reactElement, bailOutMessage);
    return reactElement;
  }

  _resolveReactElementUndefinedRender(reactElement, evaluatedNode, branchStatus, branchState) {
    let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");

    let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("BAIL-OUT", (0, _utils.getComponentName)(this.realm, typeValue));
    evaluatedNode.children.push(evaluatedChildNode);
    let bailOutMessage = `undefined was returned from render`;
    evaluatedChildNode.message = bailOutMessage;

    this._assignBailOutMessage(reactElement, bailOutMessage);
    this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
    if (branchStatus === "NEW_BRANCH" && branchState) {
      return branchState.captureBranchedValue(typeValue, reactElement);
    }
    return reactElement;
  }

  _resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedNode) {
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");
    // terminal host component. Start evaluating its children.
    if (propsValue instanceof _index.ObjectValue && propsValue.properties.has("children")) {
      let childrenValue = (0, _index2.Get)(this.realm, propsValue, "children");

      if (childrenValue instanceof _index.Value) {
        let resolvedChildren = this._resolveDeeply(componentType, childrenValue, context, branchStatus, branchState, evaluatedNode);
        // we can optimize further and flatten arrays on non-composite components
        if (resolvedChildren instanceof _index.ArrayValue) {
          resolvedChildren = (0, _utils.flattenChildren)(this.realm, resolvedChildren);
        }
        if (propsValue.properties.has("children")) {
          propsValue.refuseSerialization = true;
          (0, _utils.setProperty)(propsValue, "children", resolvedChildren);
          propsValue.refuseSerialization = false;
        }
      }
    }
    return reactElement;
  }

  _resolveFragmentComponent(componentType, reactElement, context, branchStatus, branchState, evaluatedNode) {
    this.statistics.componentsEvaluated++;
    if (this.componentTreeConfig.firstRenderOnly) {
      let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("INLINED", "React.Fragment");
      evaluatedNode.children.push(evaluatedChildNode);
      this.statistics.inlinedComponents++;
      let children = this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedChildNode);
      return children;
    } else {
      let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NORMAL", "React.Fragment");
      evaluatedNode.children.push(evaluatedChildNode);
      return this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedChildNode);
    }
  }

  _resolveReactElement(componentType, reactElement, context, branchStatus, branchState, evaluatedNode) {
    reactElement = this.componentTreeConfig.firstRenderOnly ? (0, _utils.sanitizeReactElementForFirstRenderOnly)(this.realm, reactElement) : reactElement;

    let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");
    let refValue = (0, _utils.getProperty)(this.realm, reactElement, "ref");

    if (typeValue instanceof _index.StringValue) {
      return this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedNode);
    }
    // we do not support "ref" on <Component /> ReactElements
    if (!(refValue instanceof _index.NullValue)) {
      this._resolveReactElementBadRef(reactElement, evaluatedNode);
    }
    if (!(propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue || propsValue instanceof _index.AbstractValue)) {
      this._assignBailOutMessage(reactElement, `props on <Component /> was not not an ObjectValue or an AbstractValue`);
      return reactElement;
    }
    let componentResolutionStrategy = this._getComponentResolutionStrategy(typeValue);

    try {
      let result;

      switch (componentResolutionStrategy) {
        case "NORMAL":
          {
            if (typeValue instanceof _index.AbstractValue && typeValue.kind === "conditional") {
              return this._resolveBranchedComponentType(componentType, reactElement, context, branchStatus, branchState, evaluatedNode);
            }
            if (!(typeValue instanceof _index.ECMAScriptSourceFunctionValue || (0, _utils.valueIsKnownReactAbstraction)(this.realm, typeValue))) {
              return this._resolveUnknownComponentType(reactElement, evaluatedNode);
            }
            let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("INLINED", (0, _utils.getComponentName)(this.realm, typeValue));
            evaluatedNode.children.push(evaluatedChildNode);
            let render = this._renderComponent(typeValue, propsValue, context, branchStatus === "NEW_BRANCH" ? "BRANCH" : branchStatus, null, evaluatedChildNode);
            result = render.result;
            this.statistics.inlinedComponents++;
            break;
          }
        case "FRAGMENT":
          {
            return this._resolveFragmentComponent(componentType, reactElement, context, branchStatus, branchState, evaluatedNode);
          }
        case "RELAY_QUERY_RENDERER":
          {
            (0, _invariant2.default)(typeValue instanceof _index.AbstractObjectValue);
            result = this._resolveRelayQueryRendererComponent(componentType, reactElement, context, branchState, evaluatedNode);
            break;
          }
        case "CONTEXT_PROVIDER":
          {
            return this._resolveContextProviderComponent(componentType, reactElement, context, branchStatus, branchState, evaluatedNode);
          }
        case "CONTEXT_CONSUMER":
          {
            result = this._resolveContextConsumerComponent(componentType, reactElement, context, branchState, evaluatedNode);
            break;
          }
        default:
          (0, _invariant2.default)(false, "unsupported component resolution strategy");
      }

      if (result === undefined) {
        result = reactElement;
      }
      if (result instanceof _index.UndefinedValue) {
        return this._resolveReactElementUndefinedRender(reactElement, evaluatedNode, branchStatus, branchState);
      }
      if (branchStatus === "NEW_BRANCH" && branchState) {
        return branchState.captureBranchedValue(typeValue, result);
      }
      return result;
    } catch (error) {
      return this._resolveComponentResolutionFailure(error, reactElement, evaluatedNode, branchStatus, branchState);
    }
  }

  _resolveComponentResolutionFailure(error, reactElement, evaluatedNode, branchStatus, branchState) {
    if (error.name === "Invariant Violation") {
      throw error;
    }
    let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
    let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");
    // assign a bail out message
    if (error instanceof _errors2.NewComponentTreeBranch) {
      this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
      // NO-OP (we don't queue a newComponentTree as this was already done)
    } else {
      // handle abrupt completions
      if (error instanceof _completions.AbruptCompletion) {
        let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("ABRUPT_COMPLETION", (0, _utils.getComponentName)(this.realm, typeValue));
        evaluatedNode.children.push(evaluatedChildNode);
      } else {
        let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("BAIL-OUT", (0, _utils.getComponentName)(this.realm, typeValue));
        evaluatedNode.children.push(evaluatedChildNode);
        this._queueNewComponentTree(typeValue, evaluatedChildNode);
        this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
        if (error instanceof _errors2.ExpectedBailOut) {
          evaluatedChildNode.message = error.message;
          this._assignBailOutMessage(reactElement, error.message);
        } else if (error instanceof _errors.FatalError) {
          let message = "evaluation failed";
          evaluatedChildNode.message = message;
          this._assignBailOutMessage(reactElement, message);
        } else {
          evaluatedChildNode.message = `unknown error`;
          throw error;
        }
      }
    }
    // a child component bailed out during component folding, so return the function value and continue
    if (branchStatus === "NEW_BRANCH" && branchState) {
      return branchState.captureBranchedValue(typeValue, reactElement);
    }
    return reactElement;
  }

  _resolveDeeply(componentType, value, context, branchStatus, branchState, evaluatedNode) {
    if (value instanceof _index.StringValue || value instanceof _index.NumberValue || value instanceof _index.BooleanValue || value instanceof _index.NullValue || value instanceof _index.UndefinedValue) {
      // terminal values
      return value;
    } else if (value instanceof _index.AbstractValue) {
      return this._resolveAbstractValue(componentType, value, context, branchStatus, branchState, evaluatedNode);
    }
    // TODO investigate what about other iterables type objects
    if (value instanceof _index.ArrayValue) {
      this._resolveArray(componentType, value, context, branchStatus, branchState, evaluatedNode);
      return value;
    }
    if (value instanceof _index.ObjectValue && (0, _utils.isReactElement)(value)) {
      return this._resolveReactElement(componentType, value, context, branchStatus, branchState, evaluatedNode);
    } else {
      throw new _errors2.ExpectedBailOut("unsupported value type during reconcilation");
    }
  }

  _assignBailOutMessage(reactElement, message) {
    // $BailOutReason is a field on ObjectValue that allows us to specify a message
    // that gets serialized as a comment node during the ReactElement serialization stage
    message = `Bail-out: ${message}`;
    if (reactElement.$BailOutReason !== undefined) {
      // merge bail out messages if one already exists
      reactElement.$BailOutReason += `, ${message}`;
    } else {
      reactElement.$BailOutReason = message;
    }
  }

  _resolveArray(componentType, arrayValue, context, branchStatus, branchState, evaluatedNode) {
    (0, _utils.forEachArrayValue)(this.realm, arrayValue, (elementValue, elementPropertyDescriptor) => {
      elementPropertyDescriptor.value = this._resolveDeeply(componentType, elementValue, context, "NEW_BRANCH", branchState, evaluatedNode);
    });
  }

  hasEvaluatedRootNode(componentType, evaluateNode) {
    if (this.alreadyEvaluatedRootNodes.has(componentType)) {
      let alreadyEvaluatedNode = this.alreadyEvaluatedRootNodes.get(componentType);
      (0, _invariant2.default)(alreadyEvaluatedNode);
      evaluateNode.children = alreadyEvaluatedNode.children;
      evaluateNode.status = alreadyEvaluatedNode.status;
      evaluateNode.name = alreadyEvaluatedNode.name;
      return true;
    }
    return false;
  }

  hasEvaluatedNestedClosure(func) {
    return this.alreadyEvaluatedNestedClosures.has(func);
  }

  _findReactComponentTrees(value, evaluatedNode, treatFunctionsAs, componentType, context, branchState) {
    if (value instanceof _index.AbstractValue) {
      if (value.args.length > 0) {
        for (let arg of value.args) {
          this._findReactComponentTrees(arg, evaluatedNode, treatFunctionsAs, componentType, context, branchState);
        }
      } else {
        this.componentTreeState.deadEnds++;
      }
    } else if ((0, _utils.valueIsKnownReactAbstraction)(this.realm, value)) {
      let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NEW_TREE", (0, _utils.getComponentName)(this.realm, value));
      evaluatedNode.children.push(evaluatedChildNode);
      this._queueNewComponentTree(value, evaluatedChildNode);
    } else if (value instanceof _index.ECMAScriptSourceFunctionValue || value instanceof _index.BoundFunctionValue) {
      if ((0, _utils.valueIsClassComponent)(this.realm, value) || treatFunctionsAs === "FUNCTIONAL_COMPONENTS") {
        let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NEW_TREE", (0, _utils.getComponentName)(this.realm, value));
        evaluatedNode.children.push(evaluatedChildNode);
        this._queueNewComponentTree(value, evaluatedChildNode);
      } else if (treatFunctionsAs === "NESTED_CLOSURES") {
        (0, _invariant2.default)(componentType && context);
        (0, _invariant2.default)(branchState !== undefined);
        this._queueOptimizedClosure(value, evaluatedNode, componentType, context, branchState);
      }
    } else if (value instanceof _index.ObjectValue) {
      if ((0, _utils.isReactElement)(value)) {
        let typeValue = (0, _utils.getProperty)(this.realm, value, "type");
        let ref = (0, _utils.getProperty)(this.realm, value, "ref");
        let props = (0, _utils.getProperty)(this.realm, value, "props");

        if ((0, _utils.valueIsKnownReactAbstraction)(this.realm, typeValue) || typeValue instanceof _index.ECMAScriptSourceFunctionValue) {
          let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NEW_TREE", (0, _utils.getComponentName)(this.realm, typeValue));
          evaluatedNode.children.push(evaluatedChildNode);
          this._queueNewComponentTree(typeValue, evaluatedChildNode);
        }
        this._findReactComponentTrees(ref, evaluatedNode, treatFunctionsAs, componentType, context, branchState);
        this._findReactComponentTrees(props, evaluatedNode, treatFunctionsAs, componentType, context, branchState);
      } else {
        for (let [propName, binding] of value.properties) {
          if (binding && binding.descriptor && binding.descriptor.enumerable) {
            this._findReactComponentTrees((0, _utils.getProperty)(this.realm, value, propName), evaluatedNode, treatFunctionsAs, componentType, context, branchState);
          }
        }
      }
    }
  }
}
exports.Reconciler = Reconciler; /**
                                  * 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.
                                  */
//# sourceMappingURL=reconcilation.js.map