/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *
 * @format
 */

'use strict';

function ownKeys(e, r) {
  var t = Object.keys(e);
  if (Object.getOwnPropertySymbols) {
    var o = Object.getOwnPropertySymbols(e);
    r &&
      (o = o.filter(function (r) {
        return Object.getOwnPropertyDescriptor(e, r).enumerable;
      })),
      t.push.apply(t, o);
  }
  return t;
}
function _objectSpread(e) {
  for (var r = 1; r < arguments.length; r++) {
    var t = null != arguments[r] ? arguments[r] : {};
    r % 2
      ? ownKeys(Object(t), !0).forEach(function (r) {
          _defineProperty(e, r, t[r]);
        })
      : Object.getOwnPropertyDescriptors
      ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t))
      : ownKeys(Object(t)).forEach(function (r) {
          Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
        });
  }
  return e;
}
function _defineProperty(e, r, t) {
  return (
    (r = _toPropertyKey(r)) in e
      ? Object.defineProperty(e, r, {
          value: t,
          enumerable: !0,
          configurable: !0,
          writable: !0,
        })
      : (e[r] = t),
    e
  );
}
function _toPropertyKey(t) {
  var i = _toPrimitive(t, 'string');
  return 'symbol' == typeof i ? i : i + '';
}
function _toPrimitive(t, r) {
  if ('object' != typeof t || !t) return t;
  var e = t[Symbol.toPrimitive];
  if (void 0 !== e) {
    var i = e.call(t, r || 'default');
    if ('object' != typeof i) return i;
    throw new TypeError('@@toPrimitive must return a primitive value.');
  }
  return ('string' === r ? String : Number)(t);
}
function _slicedToArray(r, e) {
  return (
    _arrayWithHoles(r) ||
    _iterableToArrayLimit(r, e) ||
    _unsupportedIterableToArray(r, e) ||
    _nonIterableRest()
  );
}
function _nonIterableRest() {
  throw new TypeError(
    'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
  );
}
function _unsupportedIterableToArray(r, a) {
  if (r) {
    if ('string' == typeof r) return _arrayLikeToArray(r, a);
    var t = {}.toString.call(r).slice(8, -1);
    return (
      'Object' === t && r.constructor && (t = r.constructor.name),
      'Map' === t || 'Set' === t
        ? Array.from(r)
        : 'Arguments' === t ||
          /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)
        ? _arrayLikeToArray(r, a)
        : void 0
    );
  }
}
function _arrayLikeToArray(r, a) {
  (null == a || a > r.length) && (a = r.length);
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
  return n;
}
function _iterableToArrayLimit(r, l) {
  var t =
    null == r
      ? null
      : ('undefined' != typeof Symbol && r[Symbol.iterator]) || r['@@iterator'];
  if (null != t) {
    var e,
      n,
      i,
      u,
      a = [],
      f = !0,
      o = !1;
    try {
      if (((i = (t = t.call(r)).next), 0 === l)) {
        if (Object(t) !== t) return;
        f = !1;
      } else
        for (
          ;
          !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l);
          f = !0
        );
    } catch (r) {
      (o = !0), (n = r);
    } finally {
      try {
        if (!f && null != t.return && ((u = t.return()), Object(u) !== u))
          return;
      } finally {
        if (o) throw n;
      }
    }
    return a;
  }
}
function _arrayWithHoles(r) {
  if (Array.isArray(r)) return r;
}
const _require = require('./error-utils'),
  throwIfConfigNotfound = _require.throwIfConfigNotfound,
  throwIfEventEmitterEventTypeIsUnsupported =
    _require.throwIfEventEmitterEventTypeIsUnsupported,
  throwIfEventEmitterTypeIsUnsupported =
    _require.throwIfEventEmitterTypeIsUnsupported,
  throwIfIncorrectModuleRegistryCallArgument =
    _require.throwIfIncorrectModuleRegistryCallArgument,
  throwIfIncorrectModuleRegistryCallTypeParameterParserError =
    _require.throwIfIncorrectModuleRegistryCallTypeParameterParserError,
  throwIfModuleInterfaceIsMisnamed = _require.throwIfModuleInterfaceIsMisnamed,
  throwIfModuleInterfaceNotFound = _require.throwIfModuleInterfaceNotFound,
  throwIfModuleTypeIsUnsupported = _require.throwIfModuleTypeIsUnsupported,
  throwIfMoreThanOneCodegenNativecommands =
    _require.throwIfMoreThanOneCodegenNativecommands,
  throwIfMoreThanOneConfig = _require.throwIfMoreThanOneConfig,
  throwIfMoreThanOneModuleInterfaceParserError =
    _require.throwIfMoreThanOneModuleInterfaceParserError,
  throwIfMoreThanOneModuleRegistryCalls =
    _require.throwIfMoreThanOneModuleRegistryCalls,
  throwIfPropertyValueTypeIsUnsupported =
    _require.throwIfPropertyValueTypeIsUnsupported,
  throwIfTypeAliasIsNotInterface = _require.throwIfTypeAliasIsNotInterface,
  throwIfUnsupportedFunctionParamTypeAnnotationParserError =
    _require.throwIfUnsupportedFunctionParamTypeAnnotationParserError,
  throwIfUnsupportedFunctionReturnTypeAnnotationParserError =
    _require.throwIfUnsupportedFunctionReturnTypeAnnotationParserError,
  throwIfUntypedModule = _require.throwIfUntypedModule,
  throwIfUnusedModuleInterfaceParserError =
    _require.throwIfUnusedModuleInterfaceParserError,
  throwIfWrongNumberOfCallExpressionArgs =
    _require.throwIfWrongNumberOfCallExpressionArgs;
const _require2 = require('./errors'),
  MissingTypeParameterGenericParserError =
    _require2.MissingTypeParameterGenericParserError,
  MoreThanOneTypeParameterGenericParserError =
    _require2.MoreThanOneTypeParameterGenericParserError,
  UnnamedFunctionParamParserError = _require2.UnnamedFunctionParamParserError,
  UnsupportedObjectDirectRecursivePropertyParserError =
    _require2.UnsupportedObjectDirectRecursivePropertyParserError;
const _require3 = require('./utils'),
  createParserErrorCapturer = _require3.createParserErrorCapturer,
  extractNativeModuleName = _require3.extractNativeModuleName,
  getConfigType = _require3.getConfigType,
  getSortedObject = _require3.getSortedObject,
  isModuleRegistryCall = _require3.isModuleRegistryCall,
  verifyPlatforms = _require3.verifyPlatforms,
  visit = _require3.visit;
const invariant = require('invariant');

// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser

function wrapModuleSchema(nativeModuleSchema, hasteModuleName) {
  return {
    modules: {
      [hasteModuleName]: nativeModuleSchema,
    },
  };
}

// $FlowFixMe[unsupported-variance-annotation]
function unwrapNullable(x) {
  if (x.type === 'NullableTypeAnnotation') {
    return [x.typeAnnotation, true];
  }
  return [x, false];
}

// $FlowFixMe[unsupported-variance-annotation]
function wrapNullable(nullable, typeAnnotation) {
  if (!nullable) {
    return typeAnnotation;
  }
  return {
    type: 'NullableTypeAnnotation',
    typeAnnotation,
  };
}
function assertGenericTypeAnnotationHasExactlyOneTypeParameter(
  moduleName,
  /**
   * TODO(T108222691): Use flow-types for @babel/parser
   */
  typeAnnotation,
  parser,
) {
  if (typeAnnotation.typeParameters == null) {
    throw new MissingTypeParameterGenericParserError(
      moduleName,
      typeAnnotation,
      parser,
    );
  }
  const typeAnnotationType = parser.typeParameterInstantiation;
  invariant(
    typeAnnotation.typeParameters.type === typeAnnotationType,
    `assertGenericTypeAnnotationHasExactlyOneTypeParameter: Type parameters must be an AST node of type '${typeAnnotationType}'`,
  );
  if (typeAnnotation.typeParameters.params.length !== 1) {
    throw new MoreThanOneTypeParameterGenericParserError(
      moduleName,
      typeAnnotation,
      parser,
    );
  }
}
function isObjectProperty(property, language) {
  switch (language) {
    case 'Flow':
      return property.type === 'ObjectTypeProperty';
    case 'TypeScript':
      return property.type === 'TSPropertySignature';
    default:
      return false;
  }
}
function getObjectTypeAnnotations(
  hasteModuleName,
  types,
  tryParse,
  translateTypeAnnotation,
  parser,
) {
  const aliasMap = {};
  Object.entries(types).forEach(([key, value]) => {
    const isTypeAlias =
      value.type === 'TypeAlias' || value.type === 'TSTypeAliasDeclaration';
    if (!isTypeAlias) {
      return;
    }
    const parent = parser.nextNodeForTypeAlias(value);
    if (
      parent.type !== 'ObjectTypeAnnotation' &&
      parent.type !== 'TSTypeLiteral'
    ) {
      return;
    }
    const typeProperties = parser
      .getAnnotatedElementProperties(value)
      .map(prop =>
        parseObjectProperty(
          parent,
          prop,
          hasteModuleName,
          types,
          aliasMap,
          {},
          // enumMap
          tryParse,
          true,
          // cxxOnly
          (prop === null || prop === void 0 ? void 0 : prop.optional) || false,
          translateTypeAnnotation,
          parser,
        ),
      );
    aliasMap[key] = {
      type: 'ObjectTypeAnnotation',
      properties: typeProperties,
    };
  });
  return aliasMap;
}
function parseObjectProperty(
  parentObject,
  property,
  hasteModuleName,
  types,
  aliasMap,
  enumMap,
  tryParse,
  cxxOnly,
  nullable,
  translateTypeAnnotation,
  parser,
) {
  const language = parser.language();
  const name = parser.getKeyName(property, hasteModuleName);
  const _property$optional = property.optional,
    optional = _property$optional === void 0 ? false : _property$optional;
  const languageTypeAnnotation =
    language === 'TypeScript'
      ? property.typeAnnotation.typeAnnotation
      : property.value;

  // Handle recursive types
  if (parentObject) {
    var _languageTypeAnnotati, _languageTypeAnnotati2;
    const propertyType = parser.getResolveTypeAnnotationFN()(
      languageTypeAnnotation,
      types,
      parser,
    );
    if (
      propertyType.typeResolutionStatus.successful === true &&
      propertyType.typeResolutionStatus.type === 'alias' &&
      (language === 'TypeScript'
        ? parentObject.typeName &&
          parentObject.typeName.name ===
            ((_languageTypeAnnotati = languageTypeAnnotation.typeName) ===
              null || _languageTypeAnnotati === void 0
              ? void 0
              : _languageTypeAnnotati.name)
        : parentObject.id &&
          parentObject.id.name ===
            ((_languageTypeAnnotati2 = languageTypeAnnotation.id) === null ||
            _languageTypeAnnotati2 === void 0
              ? void 0
              : _languageTypeAnnotati2.name))
    ) {
      if (!optional) {
        throw new UnsupportedObjectDirectRecursivePropertyParserError(
          name,
          languageTypeAnnotation,
          hasteModuleName,
        );
      }
      return {
        name,
        optional,
        typeAnnotation: {
          type: 'TypeAliasTypeAnnotation',
          name: propertyType.typeResolutionStatus.name,
        },
      };
    }
  }

  // Handle non-recursive types
  const _unwrapNullable = unwrapNullable(
      translateTypeAnnotation(
        hasteModuleName,
        languageTypeAnnotation,
        types,
        aliasMap,
        enumMap,
        tryParse,
        cxxOnly,
        parser,
      ),
    ),
    _unwrapNullable2 = _slicedToArray(_unwrapNullable, 2),
    propertyTypeAnnotation = _unwrapNullable2[0],
    isPropertyNullable = _unwrapNullable2[1];
  if (
    (propertyTypeAnnotation.type === 'FunctionTypeAnnotation' && !cxxOnly) ||
    propertyTypeAnnotation.type === 'PromiseTypeAnnotation' ||
    propertyTypeAnnotation.type === 'VoidTypeAnnotation'
  ) {
    throwIfPropertyValueTypeIsUnsupported(
      hasteModuleName,
      languageTypeAnnotation,
      property.key,
      propertyTypeAnnotation.type,
    );
  }
  return {
    name,
    optional,
    typeAnnotation: wrapNullable(isPropertyNullable, propertyTypeAnnotation),
  };
}
function translateFunctionTypeAnnotation(
  hasteModuleName,
  // TODO(T108222691): Use flow-types for @babel/parser
  // TODO(T71778680): This is a FunctionTypeAnnotation. Type this.
  functionTypeAnnotation,
  types,
  aliasMap,
  enumMap,
  tryParse,
  cxxOnly,
  translateTypeAnnotation,
  parser,
) {
  const params = [];
  for (const param of parser.getFunctionTypeAnnotationParameters(
    functionTypeAnnotation,
  )) {
    const parsedParam = tryParse(() => {
      if (parser.getFunctionNameFromParameter(param) == null) {
        throw new UnnamedFunctionParamParserError(param, hasteModuleName);
      }
      const paramName = parser.getParameterName(param);
      const _unwrapNullable3 = unwrapNullable(
          translateTypeAnnotation(
            hasteModuleName,
            parser.getParameterTypeAnnotation(param),
            types,
            aliasMap,
            enumMap,
            tryParse,
            cxxOnly,
            parser,
          ),
        ),
        _unwrapNullable4 = _slicedToArray(_unwrapNullable3, 2),
        paramTypeAnnotation = _unwrapNullable4[0],
        isParamTypeAnnotationNullable = _unwrapNullable4[1];
      if (
        paramTypeAnnotation.type === 'VoidTypeAnnotation' ||
        paramTypeAnnotation.type === 'PromiseTypeAnnotation'
      ) {
        return throwIfUnsupportedFunctionParamTypeAnnotationParserError(
          hasteModuleName,
          param.typeAnnotation,
          paramName,
          paramTypeAnnotation.type,
        );
      }
      return {
        name: paramName,
        optional: Boolean(param.optional),
        typeAnnotation: wrapNullable(
          isParamTypeAnnotationNullable,
          paramTypeAnnotation,
        ),
      };
    });
    if (parsedParam != null) {
      params.push(parsedParam);
    }
  }
  const _unwrapNullable5 = unwrapNullable(
      translateTypeAnnotation(
        hasteModuleName,
        parser.getFunctionTypeAnnotationReturnType(functionTypeAnnotation),
        types,
        aliasMap,
        enumMap,
        tryParse,
        cxxOnly,
        parser,
      ),
    ),
    _unwrapNullable6 = _slicedToArray(_unwrapNullable5, 2),
    returnTypeAnnotation = _unwrapNullable6[0],
    isReturnTypeAnnotationNullable = _unwrapNullable6[1];
  throwIfUnsupportedFunctionReturnTypeAnnotationParserError(
    hasteModuleName,
    functionTypeAnnotation,
    'FunctionTypeAnnotation',
    cxxOnly,
    returnTypeAnnotation.type,
  );
  return {
    type: 'FunctionTypeAnnotation',
    returnTypeAnnotation: wrapNullable(
      isReturnTypeAnnotationNullable,
      returnTypeAnnotation,
    ),
    params,
  };
}
function buildPropertySchema(
  hasteModuleName,
  // TODO(T108222691): [TS] Use flow-types for @babel/parser
  // TODO(T71778680): [Flow] This is an ObjectTypeProperty containing either:
  // - a FunctionTypeAnnotation or GenericTypeAnnotation
  // - a NullableTypeAnnoation containing a FunctionTypeAnnotation or GenericTypeAnnotation
  // Flow type this node
  property,
  types,
  aliasMap,
  enumMap,
  tryParse,
  cxxOnly,
  translateTypeAnnotation,
  parser,
) {
  let nullable = false;
  let key = property.key,
    value = property.value;
  const methodName = key.name;
  if (parser.language() === 'TypeScript') {
    value =
      property.type === 'TSMethodSignature'
        ? property
        : property.typeAnnotation;
  }
  const resolveTypeAnnotationFN = parser.getResolveTypeAnnotationFN();
  var _resolveTypeAnnotatio = resolveTypeAnnotationFN(value, types, parser);
  nullable = _resolveTypeAnnotatio.nullable;
  value = _resolveTypeAnnotatio.typeAnnotation;
  throwIfModuleTypeIsUnsupported(
    hasteModuleName,
    property.value,
    key.name,
    value.type,
    parser,
  );
  return {
    name: methodName,
    optional: Boolean(property.optional),
    typeAnnotation: wrapNullable(
      nullable,
      translateFunctionTypeAnnotation(
        hasteModuleName,
        value,
        types,
        aliasMap,
        enumMap,
        tryParse,
        cxxOnly,
        translateTypeAnnotation,
        parser,
      ),
    ),
  };
}
function buildEventEmitterSchema(
  hasteModuleName,
  // TODO(T108222691): [TS] Use flow-types for @babel/parser
  // TODO(T71778680): [Flow] This is an ObjectTypeProperty containing either:
  // - a FunctionTypeAnnotation or GenericTypeAnnotation
  // - a NullableTypeAnnoation containing a FunctionTypeAnnotation or GenericTypeAnnotation
  // Flow type this node
  property,
  types,
  aliasMap,
  enumMap,
  tryParse,
  cxxOnly,
  translateTypeAnnotation,
  parser,
) {
  const key = property.key;
  const value =
    parser.language() === 'TypeScript'
      ? property.typeAnnotation.typeAnnotation
      : property.value;
  const eventemitterName = key.name;
  const resolveTypeAnnotationFN = parser.getResolveTypeAnnotationFN();
  const _unwrapNullable7 = unwrapNullable(value),
    _unwrapNullable8 = _slicedToArray(_unwrapNullable7, 2),
    typeAnnotation = _unwrapNullable8[0],
    typeAnnotationNullable = _unwrapNullable8[1];
  const typeAnnotationUntyped =
    value.typeParameters.params.length === 1 &&
    parser.language() === 'TypeScript'
      ? value.typeParameters.params[0].type === 'TSTypeLiteral' &&
        value.typeParameters.params[0].members.length === 0
      : value.typeParameters.params[0].type === 'ObjectTypeAnnotation' &&
        value.typeParameters.params[0].properties.length === 0;
  throwIfEventEmitterTypeIsUnsupported(
    hasteModuleName,
    key.name,
    typeAnnotation.type,
    parser,
    typeAnnotationNullable,
    typeAnnotationUntyped,
  );
  const eventTypeResolutionStatus = resolveTypeAnnotationFN(
    typeAnnotation.typeParameters.params[0],
    types,
    parser,
  );
  throwIfEventEmitterEventTypeIsUnsupported(
    hasteModuleName,
    key.name,
    eventTypeResolutionStatus.typeAnnotation,
    parser,
    eventTypeResolutionStatus.nullable,
  );
  const eventTypeAnnotation = translateTypeAnnotation(
    hasteModuleName,
    typeAnnotation.typeParameters.params[0],
    types,
    aliasMap,
    enumMap,
    tryParse,
    cxxOnly,
    parser,
  );
  return {
    name: eventemitterName,
    optional: false,
    typeAnnotation: {
      type: 'EventEmitterTypeAnnotation',
      typeAnnotation: eventTypeAnnotation,
    },
  };
}
function buildSchemaFromConfigType(
  configType,
  filename,
  ast,
  wrapComponentSchema,
  buildComponentSchema,
  buildModuleSchema,
  parser,
  translateTypeAnnotation,
) {
  switch (configType) {
    case 'component': {
      return wrapComponentSchema(buildComponentSchema(ast, parser));
    }
    case 'module': {
      if (filename === undefined || filename === null) {
        throw new Error('Filepath expected while parasing a module');
      }
      const nativeModuleName = extractNativeModuleName(filename);
      const _createParserErrorCap = createParserErrorCapturer(),
        _createParserErrorCap2 = _slicedToArray(_createParserErrorCap, 2),
        parsingErrors = _createParserErrorCap2[0],
        tryParse = _createParserErrorCap2[1];
      const schema = tryParse(() =>
        buildModuleSchema(
          nativeModuleName,
          ast,
          tryParse,
          parser,
          translateTypeAnnotation,
        ),
      );
      if (parsingErrors.length > 0) {
        /**
         * TODO(T77968131): We have two options:
         *  - Throw the first error, but indicate there are more then one errors.
         *  - Display all errors, nicely formatted.
         *
         * For the time being, we're just throw the first error.
         **/

        throw parsingErrors[0];
      }
      invariant(
        schema != null,
        'When there are no parsing errors, the schema should not be null',
      );
      return wrapModuleSchema(schema, nativeModuleName);
    }
    default:
      return {
        modules: {},
      };
  }
}
function buildSchema(
  contents,
  filename,
  wrapComponentSchema,
  buildComponentSchema,
  buildModuleSchema,
  Visitor,
  parser,
  translateTypeAnnotation,
) {
  // Early return for non-Spec JavaScript files
  if (
    !contents.includes('codegenNativeComponent') &&
    !contents.includes('TurboModule')
  ) {
    return {
      modules: {},
    };
  }
  const ast = parser.getAst(contents, filename);
  const configType = getConfigType(ast, Visitor);
  return buildSchemaFromConfigType(
    configType,
    filename,
    ast,
    wrapComponentSchema,
    buildComponentSchema,
    buildModuleSchema,
    parser,
    translateTypeAnnotation,
  );
}
function createComponentConfig(foundConfig, commandsTypeNames) {
  return _objectSpread(
    _objectSpread({}, foundConfig),
    {},
    {
      commandTypeName:
        commandsTypeNames[0] == null
          ? null
          : commandsTypeNames[0].commandTypeName,
      commandOptionsExpression:
        commandsTypeNames[0] == null
          ? null
          : commandsTypeNames[0].commandOptionsExpression,
    },
  );
}
const parseModuleName = (hasteModuleName, moduleSpec, ast, parser) => {
  const callExpressions = [];
  visit(ast, {
    CallExpression(node) {
      if (isModuleRegistryCall(node)) {
        callExpressions.push(node);
      }
    },
  });
  throwIfUnusedModuleInterfaceParserError(
    hasteModuleName,
    moduleSpec,
    callExpressions,
  );
  throwIfMoreThanOneModuleRegistryCalls(
    hasteModuleName,
    callExpressions,
    callExpressions.length,
  );
  const callExpression = callExpressions[0];
  const typeParameters = parser.callExpressionTypeParameters(callExpression);
  const methodName = callExpression.callee.property.name;
  throwIfWrongNumberOfCallExpressionArgs(
    hasteModuleName,
    callExpression,
    methodName,
    callExpression.arguments.length,
  );
  throwIfIncorrectModuleRegistryCallArgument(
    hasteModuleName,
    callExpression.arguments[0],
    methodName,
  );
  const $moduleName = callExpression.arguments[0].value;
  throwIfUntypedModule(
    typeParameters,
    hasteModuleName,
    callExpression,
    methodName,
    $moduleName,
  );
  throwIfIncorrectModuleRegistryCallTypeParameterParserError(
    hasteModuleName,
    typeParameters,
    methodName,
    $moduleName,
    parser,
  );
  return $moduleName;
};
const buildModuleSchema = (
  hasteModuleName,
  ast,
  tryParse,
  parser,
  translateTypeAnnotation,
) => {
  const language = parser.language();
  const types = parser.getTypes(ast);
  const moduleSpecs = Object.values(types).filter(t =>
    parser.isModuleInterface(t),
  );
  throwIfModuleInterfaceNotFound(
    moduleSpecs.length,
    hasteModuleName,
    ast,
    language,
  );
  throwIfMoreThanOneModuleInterfaceParserError(
    hasteModuleName,
    moduleSpecs,
    language,
  );
  const _moduleSpecs = _slicedToArray(moduleSpecs, 1),
    moduleSpec = _moduleSpecs[0];
  throwIfModuleInterfaceIsMisnamed(hasteModuleName, moduleSpec.id, language);

  // Parse Module Name
  const moduleName = parseModuleName(hasteModuleName, moduleSpec, ast, parser);

  // Some module names use platform suffix to indicate platform-exclusive modules.
  // Eventually this should be made explicit in the Flow type itself.
  // Also check the hasteModuleName for platform suffix.
  // Note: this shape is consistent with ComponentSchema.
  const _verifyPlatforms = verifyPlatforms(hasteModuleName, moduleName),
    cxxOnly = _verifyPlatforms.cxxOnly,
    excludedPlatforms = _verifyPlatforms.excludedPlatforms;
  const aliasMap = cxxOnly
    ? getObjectTypeAnnotations(
        hasteModuleName,
        types,
        tryParse,
        translateTypeAnnotation,
        parser,
      )
    : {};
  const properties =
    language === 'Flow' ? moduleSpec.body.properties : moduleSpec.body.body;
  // $FlowFixMe[missing-type-arg]
  const nativeModuleSchema = properties
    .filter(
      property =>
        property.type === 'ObjectTypeProperty' ||
        property.type === 'TSPropertySignature' ||
        property.type === 'TSMethodSignature',
    )
    .map(property => {
      var _property$typeAnnotat, _property$value, _property$value2;
      const enumMap = {};
      const isEventEmitter =
        language === 'TypeScript'
          ? (property === null || property === void 0
              ? void 0
              : property.type) === 'TSPropertySignature' &&
            (property === null ||
            property === void 0 ||
            (_property$typeAnnotat = property.typeAnnotation) === null ||
            _property$typeAnnotat === void 0 ||
            (_property$typeAnnotat = _property$typeAnnotat.typeAnnotation) ===
              null ||
            _property$typeAnnotat === void 0 ||
            (_property$typeAnnotat = _property$typeAnnotat.typeName) === null ||
            _property$typeAnnotat === void 0
              ? void 0
              : _property$typeAnnotat.name) === 'EventEmitter'
          : (property === null ||
            property === void 0 ||
            (_property$value = property.value) === null ||
            _property$value === void 0
              ? void 0
              : _property$value.type) === 'GenericTypeAnnotation' &&
            (property === null ||
            property === void 0 ||
            (_property$value2 = property.value) === null ||
            _property$value2 === void 0 ||
            (_property$value2 = _property$value2.id) === null ||
            _property$value2 === void 0
              ? void 0
              : _property$value2.name) === 'EventEmitter';
      return tryParse(() => ({
        aliasMap,
        enumMap,
        propertyShape: isEventEmitter
          ? {
              type: 'eventEmitter',
              value: buildEventEmitterSchema(
                hasteModuleName,
                property,
                types,
                aliasMap,
                enumMap,
                tryParse,
                cxxOnly,
                translateTypeAnnotation,
                parser,
              ),
            }
          : {
              type: 'method',
              value: buildPropertySchema(
                hasteModuleName,
                property,
                types,
                aliasMap,
                enumMap,
                tryParse,
                cxxOnly,
                translateTypeAnnotation,
                parser,
              ),
            },
      }));
    })
    .filter(Boolean)
    .reduce(
      (moduleSchema, {enumMap, propertyShape}) => ({
        type: 'NativeModule',
        aliasMap: _objectSpread(
          _objectSpread({}, moduleSchema.aliasMap),
          aliasMap,
        ),
        enumMap: _objectSpread(
          _objectSpread({}, moduleSchema.enumMap),
          enumMap,
        ),
        spec: {
          eventEmitters: [...moduleSchema.spec.eventEmitters].concat(
            propertyShape.type === 'eventEmitter' ? [propertyShape.value] : [],
          ),
          methods: [...moduleSchema.spec.methods].concat(
            propertyShape.type === 'method' ? [propertyShape.value] : [],
          ),
        },
        moduleName: moduleSchema.moduleName,
        excludedPlatforms: moduleSchema.excludedPlatforms,
      }),
      {
        type: 'NativeModule',
        aliasMap: {},
        enumMap: {},
        spec: {
          eventEmitters: [],
          methods: [],
        },
        moduleName,
        excludedPlatforms:
          excludedPlatforms.length !== 0 ? [...excludedPlatforms] : undefined,
      },
    );
  return {
    type: 'NativeModule',
    aliasMap: getSortedObject(nativeModuleSchema.aliasMap),
    enumMap: getSortedObject(nativeModuleSchema.enumMap),
    spec: {
      eventEmitters: nativeModuleSchema.spec.eventEmitters.sort(),
      methods: nativeModuleSchema.spec.methods.sort(),
    },
    moduleName,
    excludedPlatforms: nativeModuleSchema.excludedPlatforms,
  };
};

/**
 * This function is used to find the type of a native component
 * provided the default exports statement from generated AST.
 * @param statement The statement to be parsed.
 * @param foundConfigs The 'mutable' array of configs that have been found.
 * @param parser The language parser to be used.
 * @returns void
 */
function findNativeComponentType(statement, foundConfigs, parser) {
  let declaration = statement.declaration;

  // codegenNativeComponent can be nested inside a cast
  // expression so we need to go one level deeper
  if (
    declaration.type === 'TSAsExpression' ||
    declaration.type === 'AsExpression' ||
    declaration.type === 'TypeCastExpression'
  ) {
    declaration = declaration.expression;
  }
  try {
    if (declaration.callee.name === 'codegenNativeComponent') {
      const typeArgumentParams =
        parser.getTypeArgumentParamsFromDeclaration(declaration);
      const funcArgumentParams = declaration.arguments;
      const nativeComponentType = parser.getNativeComponentType(
        typeArgumentParams,
        funcArgumentParams,
      );
      if (funcArgumentParams.length > 1) {
        nativeComponentType.optionsExpression = funcArgumentParams[1];
      }
      foundConfigs.push(nativeComponentType);
    }
  } catch (e) {
    // ignore
  }
}
function getCommandOptions(commandOptionsExpression) {
  if (commandOptionsExpression == null) {
    return null;
  }
  let foundOptions;
  try {
    foundOptions = commandOptionsExpression.properties.reduce(
      (options, prop) => {
        options[prop.key.name] = (
          (prop && prop.value && prop.value.elements) ||
          []
        ).map(element => element && element.value);
        return options;
      },
      {},
    );
  } catch (e) {
    throw new Error(
      'Failed to parse command options, please check that they are defined correctly',
    );
  }
  return foundOptions;
}
function getOptions(optionsExpression) {
  if (!optionsExpression) {
    return null;
  }
  let foundOptions;
  try {
    foundOptions = optionsExpression.properties.reduce((options, prop) => {
      if (prop.value.type === 'ArrayExpression') {
        options[prop.key.name] = prop.value.elements.map(
          element => element.value,
        );
      } else {
        options[prop.key.name] = prop.value.value;
      }
      return options;
    }, {});
  } catch (e) {
    throw new Error(
      'Failed to parse codegen options, please check that they are defined correctly',
    );
  }
  if (
    foundOptions.paperComponentName &&
    foundOptions.paperComponentNameDeprecated
  ) {
    throw new Error(
      'Failed to parse codegen options, cannot use both paperComponentName and paperComponentNameDeprecated',
    );
  }
  return foundOptions;
}
function getCommandTypeNameAndOptionsExpression(namedExport, parser) {
  let callExpression;
  let calleeName;
  try {
    callExpression = namedExport.declaration.declarations[0].init;
    calleeName = callExpression.callee.name;
  } catch (e) {
    return;
  }
  if (calleeName !== 'codegenNativeCommands') {
    return;
  }
  if (callExpression.arguments.length !== 1) {
    throw new Error(
      'codegenNativeCommands must be passed options including the supported commands',
    );
  }
  const typeArgumentParam =
    parser.getTypeArgumentParamsFromDeclaration(callExpression)[0];
  if (!parser.isGenericTypeAnnotation(typeArgumentParam.type)) {
    throw new Error(
      "codegenNativeCommands doesn't support inline definitions. Specify a file local type alias",
    );
  }
  return {
    commandTypeName: parser.getTypeAnnotationName(typeArgumentParam),
    commandOptionsExpression: callExpression.arguments[0],
  };
}
function propertyNames(properties) {
  return properties
    .map(property => property && property.key && property.key.name)
    .filter(Boolean);
}
function extendsForProp(prop, types, parser) {
  const argument = parser.argumentForProp(prop);
  if (!argument) {
    console.log('null', prop);
  }
  const name = parser.nameForArgument(prop);
  if (types[name] != null) {
    // This type is locally defined in the file
    return null;
  }
  switch (name) {
    case 'ViewProps':
      return {
        type: 'ReactNativeBuiltInType',
        knownTypeName: 'ReactNativeCoreViewProps',
      };
    default: {
      throw new Error(`Unable to handle prop spread: ${name}`);
    }
  }
}
function buildPropSchema(property, types, parser) {
  const getSchemaInfoFN = parser.getGetSchemaInfoFN();
  const info = getSchemaInfoFN(property, types);
  if (info == null) {
    return null;
  }
  const name = info.name,
    optional = info.optional,
    typeAnnotation = info.typeAnnotation,
    defaultValue = info.defaultValue,
    withNullDefault = info.withNullDefault;
  const getTypeAnnotationFN = parser.getGetTypeAnnotationFN();
  return {
    name,
    optional,
    typeAnnotation: getTypeAnnotationFN(
      name,
      typeAnnotation,
      defaultValue,
      withNullDefault,
      types,
      parser,
      buildPropSchema,
    ),
  };
}

/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
 * LTI update could not be added via codemod */
function getEventArgument(argumentProps, parser, getPropertyType) {
  return {
    type: 'ObjectTypeAnnotation',
    properties: argumentProps.map(member =>
      buildPropertiesForEvent(member, parser, getPropertyType),
    ),
  };
}

/* $FlowFixMe[signature-verification-failure] there's no flowtype for AST.
 * TODO(T108222691): Use flow-types for @babel/parser */
function findComponentConfig(ast, parser) {
  const foundConfigs = [];
  const defaultExports = ast.body.filter(
    node => node.type === 'ExportDefaultDeclaration',
  );
  defaultExports.forEach(statement => {
    findNativeComponentType(statement, foundConfigs, parser);
  });
  throwIfConfigNotfound(foundConfigs);
  throwIfMoreThanOneConfig(foundConfigs);
  const foundConfig = foundConfigs[0];
  const namedExports = ast.body.filter(
    node => node.type === 'ExportNamedDeclaration',
  );
  const commandsTypeNames = namedExports
    .map(statement => getCommandTypeNameAndOptionsExpression(statement, parser))
    .filter(Boolean);
  throwIfMoreThanOneCodegenNativecommands(commandsTypeNames);
  return createComponentConfig(foundConfig, commandsTypeNames);
}

// $FlowFixMe[signature-verification-failure] there's no flowtype for AST
function getCommandProperties(ast, parser) {
  const _findComponentConfig = findComponentConfig(ast, parser),
    commandTypeName = _findComponentConfig.commandTypeName,
    commandOptionsExpression = _findComponentConfig.commandOptionsExpression;
  if (commandTypeName == null) {
    return [];
  }
  const types = parser.getTypes(ast);
  const typeAlias = types[commandTypeName];
  throwIfTypeAliasIsNotInterface(typeAlias, parser);
  const properties = parser.bodyProperties(typeAlias);
  if (!properties) {
    throw new Error(
      `Failed to find type definition for "${commandTypeName}", please check that you have a valid codegen file`,
    );
  }
  const commandPropertyNames = propertyNames(properties);
  const commandOptions = getCommandOptions(commandOptionsExpression);
  if (commandOptions == null || commandOptions.supportedCommands == null) {
    throw new Error(
      'codegenNativeCommands must be given an options object with supportedCommands array',
    );
  }
  if (
    commandOptions.supportedCommands.length !== commandPropertyNames.length ||
    !commandOptions.supportedCommands.every(supportedCommand =>
      commandPropertyNames.includes(supportedCommand),
    )
  ) {
    throw new Error(
      `codegenNativeCommands expected the same supportedCommands specified in the ${commandTypeName} interface: ${commandPropertyNames.join(
        ', ',
      )}`,
    );
  }
  return properties;
}
function getTypeResolutionStatus(type, typeAnnotation, parser) {
  return {
    successful: true,
    type,
    name: parser.getTypeAnnotationName(typeAnnotation),
  };
}
function handleGenericTypeAnnotation(
  typeAnnotation,
  resolvedTypeAnnotation,
  parser,
) {
  let typeResolutionStatus;
  let node;
  switch (resolvedTypeAnnotation.type) {
    case parser.typeAlias: {
      typeResolutionStatus = getTypeResolutionStatus(
        'alias',
        typeAnnotation,
        parser,
      );
      node = parser.nextNodeForTypeAlias(resolvedTypeAnnotation);
      break;
    }
    case parser.enumDeclaration: {
      typeResolutionStatus = getTypeResolutionStatus(
        'enum',
        typeAnnotation,
        parser,
      );
      node = parser.nextNodeForEnum(resolvedTypeAnnotation);
      break;
    }
    // parser.interfaceDeclaration is not used here because for flow it should fall through to default case and throw an error
    case 'TSInterfaceDeclaration': {
      typeResolutionStatus = getTypeResolutionStatus(
        'alias',
        typeAnnotation,
        parser,
      );
      node = resolvedTypeAnnotation;
      break;
    }
    default: {
      throw new TypeError(
        parser.genericTypeAnnotationErrorMessage(resolvedTypeAnnotation),
      );
    }
  }
  return {
    typeAnnotation: node,
    typeResolutionStatus,
  };
}
function buildPropertiesForEvent(property, parser, getPropertyType) {
  const name = property.key.name;
  const optional = parser.isOptionalProperty(property);
  const typeAnnotation = parser.getTypeAnnotationFromProperty(property);
  return getPropertyType(name, optional, typeAnnotation, parser);
}
function verifyPropNotAlreadyDefined(props, needleProp) {
  const propName = needleProp.key.name;
  const foundProp = props.some(prop => prop.key.name === propName);
  if (foundProp) {
    throw new Error(`A prop was already defined with the name ${propName}`);
  }
}
function handleEventHandler(
  name,
  typeAnnotation,
  parser,
  types,
  findEventArgumentsAndType,
) {
  const eventType = name === 'BubblingEventHandler' ? 'bubble' : 'direct';
  const paperTopLevelNameDeprecated =
    parser.getPaperTopLevelNameDeprecated(typeAnnotation);
  switch (typeAnnotation.typeParameters.params[0].type) {
    case parser.nullLiteralTypeAnnotation:
    case parser.undefinedLiteralTypeAnnotation:
      return {
        argumentProps: [],
        bubblingType: eventType,
        paperTopLevelNameDeprecated,
      };
    default:
      return findEventArgumentsAndType(
        parser,
        typeAnnotation.typeParameters.params[0],
        types,
        eventType,
        paperTopLevelNameDeprecated,
      );
  }
}
function emitBuildEventSchema(
  paperTopLevelNameDeprecated,
  name,
  optional,
  nonNullableBubblingType,
  argument,
) {
  if (paperTopLevelNameDeprecated != null) {
    return {
      name,
      optional,
      bubblingType: nonNullableBubblingType,
      paperTopLevelNameDeprecated,
      typeAnnotation: {
        type: 'EventTypeAnnotation',
        argument: argument,
      },
    };
  }
  return {
    name,
    optional,
    bubblingType: nonNullableBubblingType,
    typeAnnotation: {
      type: 'EventTypeAnnotation',
      argument: argument,
    },
  };
}
module.exports = {
  wrapModuleSchema,
  unwrapNullable,
  wrapNullable,
  assertGenericTypeAnnotationHasExactlyOneTypeParameter,
  isObjectProperty,
  parseObjectProperty,
  translateFunctionTypeAnnotation,
  buildPropertySchema,
  buildSchemaFromConfigType,
  buildSchema,
  createComponentConfig,
  parseModuleName,
  buildModuleSchema,
  findNativeComponentType,
  propertyNames,
  getCommandOptions,
  getOptions,
  getCommandTypeNameAndOptionsExpression,
  extendsForProp,
  buildPropSchema,
  getEventArgument,
  findComponentConfig,
  getCommandProperties,
  handleGenericTypeAnnotation,
  getTypeResolutionStatus,
  buildPropertiesForEvent,
  verifyPropNotAlreadyDefined,
  handleEventHandler,
  emitBuildEventSchema,
};
