/**
 * 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 _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);
}
const {
  UnsupportedObjectPropertyTypeAnnotationParserError,
} = require('../errors');
const {
  buildModuleSchema,
  buildPropSchema,
  buildSchema,
  extendsForProp,
  handleGenericTypeAnnotation,
} = require('../parsers-commons.js');
const {Visitor} = require('../parsers-primitives');
const {wrapComponentSchema} = require('../schema.js');
const {buildComponentSchema} = require('./components');
const {
  flattenProperties,
  getSchemaInfo,
  getTypeAnnotation,
} = require('./components/componentsUtils');
const {typeScriptTranslateTypeAnnotation} = require('./modules');
const {parseTopLevelType} = require('./parseTopLevelType');
// $FlowFixMe[untyped-import] Use flow-types for @babel/parser
const babelParser = require('@babel/parser');
const fs = require('fs');
const invariant = require('invariant');
class TypeScriptParser {
  constructor() {
    _defineProperty(
      this,
      'typeParameterInstantiation',
      'TSTypeParameterInstantiation',
    );
    _defineProperty(this, 'typeAlias', 'TSTypeAliasDeclaration');
    _defineProperty(this, 'enumDeclaration', 'TSEnumDeclaration');
    _defineProperty(this, 'interfaceDeclaration', 'TSInterfaceDeclaration');
    _defineProperty(this, 'nullLiteralTypeAnnotation', 'TSNullKeyword');
    _defineProperty(
      this,
      'undefinedLiteralTypeAnnotation',
      'TSUndefinedKeyword',
    );
  }
  isProperty(property) {
    return property.type === 'TSPropertySignature';
  }
  getKeyName(property, hasteModuleName) {
    if (!this.isProperty(property)) {
      throw new UnsupportedObjectPropertyTypeAnnotationParserError(
        hasteModuleName,
        property,
        property.type,
        this.language(),
      );
    }
    return property.key.name;
  }
  language() {
    return 'TypeScript';
  }
  getTypeAnnotationName(typeAnnotation) {
    var _typeAnnotation$typeN, _typeAnnotation$typeN2;
    if (
      (typeAnnotation === null ||
      typeAnnotation === void 0 ||
      (_typeAnnotation$typeN = typeAnnotation.typeName) === null ||
      _typeAnnotation$typeN === void 0
        ? void 0
        : _typeAnnotation$typeN.type) === 'TSQualifiedName'
    ) {
      return typeAnnotation.typeName.right.name;
    }
    return typeAnnotation === null ||
      typeAnnotation === void 0 ||
      (_typeAnnotation$typeN2 = typeAnnotation.typeName) === null ||
      _typeAnnotation$typeN2 === void 0
      ? void 0
      : _typeAnnotation$typeN2.name;
  }
  checkIfInvalidModule(typeArguments) {
    return (
      typeArguments.type !== 'TSTypeParameterInstantiation' ||
      typeArguments.params.length !== 1 ||
      typeArguments.params[0].type !== 'TSTypeReference' ||
      typeArguments.params[0].typeName.name !== 'Spec'
    );
  }
  remapUnionTypeAnnotationMemberNames(membersTypes) {
    const remapLiteral = item => {
      return item.literal
        ? item.literal.type
            .replace('NumericLiteral', 'NumberTypeAnnotation')
            .replace('StringLiteral', 'StringTypeAnnotation')
        : 'ObjectTypeAnnotation';
    };

    /* $FlowFixMe[incompatible-type] Natural Inference rollout. See
     * https://fburl.com/workplace/6291gfvu */
    return [...new Set(membersTypes.map(remapLiteral))];
  }
  getStringLiteralUnionTypeAnnotationStringLiterals(membersTypes) {
    return membersTypes.map(item => item.literal.value);
  }
  parseFile(filename) {
    const contents = fs.readFileSync(filename, 'utf8');
    return this.parseString(contents, filename);
  }
  parseString(contents, filename) {
    return buildSchema(
      contents,
      filename,
      wrapComponentSchema,
      buildComponentSchema,
      buildModuleSchema,
      Visitor,
      this,
      typeScriptTranslateTypeAnnotation,
    );
  }
  parseModuleFixture(filename) {
    const contents = fs.readFileSync(filename, 'utf8');
    return this.parseString(contents, 'path/NativeSampleTurboModule.ts');
  }
  getAst(contents, filename) {
    return babelParser.parse(contents, {
      sourceType: 'module',
      plugins: ['typescript'],
    }).program;
  }
  getFunctionTypeAnnotationParameters(functionTypeAnnotation) {
    return functionTypeAnnotation.parameters;
  }
  getFunctionNameFromParameter(parameter) {
    return parameter.typeAnnotation;
  }
  getParameterName(parameter) {
    return parameter.name;
  }
  getParameterTypeAnnotation(parameter) {
    return parameter.typeAnnotation.typeAnnotation;
  }
  getFunctionTypeAnnotationReturnType(functionTypeAnnotation) {
    return functionTypeAnnotation.typeAnnotation.typeAnnotation;
  }
  parseEnumMembersType(typeAnnotation) {
    var _typeAnnotation$membe;
    const enumInitializer =
      (_typeAnnotation$membe = typeAnnotation.members[0]) === null ||
      _typeAnnotation$membe === void 0
        ? void 0
        : _typeAnnotation$membe.initializer;
    const enumInitializerType =
      enumInitializer === null || enumInitializer === void 0
        ? void 0
        : enumInitializer.type;
    let enumMembersType = null;
    if (!enumInitializerType) {
      return 'StringTypeAnnotation';
    }
    switch (enumInitializerType) {
      case 'StringLiteral':
        enumMembersType = 'StringTypeAnnotation';
        break;
      case 'NumericLiteral':
        enumMembersType = 'NumberTypeAnnotation';
        break;
      case 'UnaryExpression':
        if (enumInitializer.operator === '-') {
          enumMembersType = 'NumberTypeAnnotation';
        }
        break;
      default:
        enumMembersType = null;
    }
    if (!enumMembersType) {
      throw new Error(
        'Enum values must be either blank, number, or string values.',
      );
    }
    return enumMembersType;
  }
  validateEnumMembersSupported(typeAnnotation, enumMembersType) {
    if (!typeAnnotation.members || typeAnnotation.members.length === 0) {
      throw new Error('Enums should have at least one member.');
    }
    const enumInitializerType =
      enumMembersType === 'StringTypeAnnotation'
        ? 'StringLiteral'
        : enumMembersType === 'NumberTypeAnnotation'
          ? 'NumericLiteral'
          : null;
    typeAnnotation.members.forEach(member => {
      var _member$initializer,
        _member$initializer2,
        _member$initializer3,
        _member$initializer4;
      const isNegative =
        ((_member$initializer = member.initializer) === null ||
        _member$initializer === void 0
          ? void 0
          : _member$initializer.type) === 'UnaryExpression' &&
        ((_member$initializer2 = member.initializer) === null ||
        _member$initializer2 === void 0
          ? void 0
          : _member$initializer2.operator) === '-';
      const initializerType = isNegative
        ? (_member$initializer3 = member.initializer) === null ||
          _member$initializer3 === void 0 ||
          (_member$initializer3 = _member$initializer3.argument) === null ||
          _member$initializer3 === void 0
          ? void 0
          : _member$initializer3.type
        : (_member$initializer4 = member.initializer) === null ||
            _member$initializer4 === void 0
          ? void 0
          : _member$initializer4.type;
      if (
        (initializerType !== null && initializerType !== void 0
          ? initializerType
          : 'StringLiteral') !== enumInitializerType
      ) {
        throw new Error(
          'Enum values can not be mixed. They all must be either blank, number, or string values.',
        );
      }
    });
  }
  parseEnumMembers(typeAnnotation) {
    return typeAnnotation.members.map(member => {
      var _member$initializer5,
        _member$initializer6,
        _member$initializer7,
        _member$initializer8,
        _member$initializer9,
        _member$initializer0;
      const value =
        ((_member$initializer5 = member.initializer) === null ||
        _member$initializer5 === void 0
          ? void 0
          : _member$initializer5.operator) === '-'
          ? {
              type: 'NumberLiteralTypeAnnotation',
              value:
                -1 *
                ((_member$initializer6 = member.initializer) === null ||
                _member$initializer6 === void 0 ||
                (_member$initializer6 = _member$initializer6.argument) ===
                  null ||
                _member$initializer6 === void 0
                  ? void 0
                  : _member$initializer6.value),
            }
          : typeof ((_member$initializer7 = member.initializer) === null ||
              _member$initializer7 === void 0
                ? void 0
                : _member$initializer7.value) === 'number'
            ? {
                type: 'NumberLiteralTypeAnnotation',
                value:
                  (_member$initializer8 = member.initializer) === null ||
                  _member$initializer8 === void 0
                    ? void 0
                    : _member$initializer8.value,
              }
            : typeof ((_member$initializer9 = member.initializer) === null ||
                _member$initializer9 === void 0
                  ? void 0
                  : _member$initializer9.value) === 'string'
              ? {
                  type: 'StringLiteralTypeAnnotation',
                  value:
                    (_member$initializer0 = member.initializer) === null ||
                    _member$initializer0 === void 0
                      ? void 0
                      : _member$initializer0.value,
                }
              : {
                  type: 'StringLiteralTypeAnnotation',
                  value: member.id.name,
                };
      return {
        name: member.id.name,
        value,
      };
    });
  }
  isModuleInterface(node) {
    var _node$extends;
    return (
      node.type === 'TSInterfaceDeclaration' &&
      ((_node$extends = node.extends) === null || _node$extends === void 0
        ? void 0
        : _node$extends.length) === 1 &&
      node.extends[0].type === 'TSExpressionWithTypeArguments' &&
      node.extends[0].expression.name === 'TurboModule'
    );
  }
  isGenericTypeAnnotation(type) {
    return type === 'TSTypeReference';
  }
  extractAnnotatedElement(typeAnnotation, types) {
    return types[typeAnnotation.typeParameters.params[0].typeName.name];
  }

  /**
   * TODO(T108222691): Use flow-types for @babel/parser
   */
  getTypes(ast) {
    return ast.body.reduce((types, node) => {
      switch (node.type) {
        case 'ExportNamedDeclaration': {
          if (node.declaration) {
            switch (node.declaration.type) {
              case 'TSTypeAliasDeclaration':
              case 'TSInterfaceDeclaration':
              case 'TSEnumDeclaration': {
                types[node.declaration.id.name] = node.declaration;
                break;
              }
            }
          }
          break;
        }
        case 'TSTypeAliasDeclaration':
        case 'TSInterfaceDeclaration':
        case 'TSEnumDeclaration': {
          types[node.id.name] = node;
          break;
        }
      }
      return types;
    }, {});
  }
  callExpressionTypeParameters(callExpression) {
    return callExpression.typeParameters || null;
  }
  computePartialProperties(
    properties,
    hasteModuleName,
    types,
    aliasMap,
    enumMap,
    tryParse,
    cxxOnly,
  ) {
    return properties.map(prop => {
      return {
        name: prop.key.name,
        optional: true,
        typeAnnotation: typeScriptTranslateTypeAnnotation(
          hasteModuleName,
          prop.typeAnnotation.typeAnnotation,
          types,
          aliasMap,
          enumMap,
          tryParse,
          cxxOnly,
          this,
        ),
      };
    });
  }
  functionTypeAnnotation(propertyValueType) {
    return (
      propertyValueType === 'TSFunctionType' ||
      propertyValueType === 'TSMethodSignature'
    );
  }
  getTypeArgumentParamsFromDeclaration(declaration) {
    return declaration.typeParameters.params;
  }

  // This FlowFixMe is supposed to refer to typeArgumentParams and funcArgumentParams of generated AST.
  getNativeComponentType(typeArgumentParams, funcArgumentParams) {
    return {
      propsTypeName: typeArgumentParams[0].typeName.name,
      componentName: funcArgumentParams[0].value,
    };
  }
  getAnnotatedElementProperties(annotatedElement) {
    return annotatedElement.typeAnnotation.members;
  }
  bodyProperties(typeAlias) {
    return typeAlias.body.body;
  }
  convertKeywordToTypeAnnotation(keyword) {
    switch (keyword) {
      case 'TSArrayType':
        return 'ArrayTypeAnnotation';
      case 'TSBooleanKeyword':
        return 'BooleanTypeAnnotation';
      case 'TSNumberKeyword':
        return 'NumberTypeAnnotation';
      case 'TSVoidKeyword':
        return 'VoidTypeAnnotation';
      case 'TSStringKeyword':
        return 'StringTypeAnnotation';
      case 'TSTypeLiteral':
        return 'ObjectTypeAnnotation';
      case 'TSUnknownKeyword':
        return 'MixedTypeAnnotation';
    }
    return keyword;
  }
  argumentForProp(prop) {
    return prop.expression;
  }
  nameForArgument(prop) {
    return prop.expression.name;
  }
  isOptionalProperty(property) {
    return property.optional || false;
  }
  getGetSchemaInfoFN() {
    return getSchemaInfo;
  }
  getTypeAnnotationFromProperty(property) {
    return property.typeAnnotation.typeAnnotation;
  }
  getGetTypeAnnotationFN() {
    return getTypeAnnotation;
  }
  getResolvedTypeAnnotation(
    // TODO(T108222691): Use flow-types for @babel/parser
    typeAnnotation,
    types,
    parser,
  ) {
    invariant(
      typeAnnotation != null,
      'resolveTypeAnnotation(): typeAnnotation cannot be null',
    );
    let node =
      typeAnnotation.type === 'TSTypeAnnotation'
        ? typeAnnotation.typeAnnotation
        : typeAnnotation;
    let nullable = false;
    let typeResolutionStatus = {
      successful: false,
    };
    for (;;) {
      const topLevelType = parseTopLevelType(node, parser);
      nullable = nullable || topLevelType.optional;
      node = topLevelType.type;
      if (node.type !== 'TSTypeReference') {
        break;
      }
      const typeAnnotationName = this.getTypeAnnotationName(node);
      const resolvedTypeAnnotation = types[typeAnnotationName];
      if (resolvedTypeAnnotation == null) {
        break;
      }
      const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} =
        handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this);
      typeResolutionStatus = status;
      node = typeAnnotationNode;
    }
    return {
      nullable: nullable,
      typeAnnotation: node,
      typeResolutionStatus,
    };
  }
  getResolveTypeAnnotationFN() {
    return (typeAnnotation, types, parser) => {
      return this.getResolvedTypeAnnotation(typeAnnotation, types, parser);
    };
  }
  isEvent(typeAnnotation) {
    if (typeAnnotation.type !== 'TSTypeReference') {
      return false;
    }
    const eventNames = new Set(['BubblingEventHandler', 'DirectEventHandler']);
    return eventNames.has(this.getTypeAnnotationName(typeAnnotation));
  }
  isProp(name, typeAnnotation) {
    if (typeAnnotation.type !== 'TSTypeReference') {
      return true;
    }
    const isStyle =
      name === 'style' &&
      typeAnnotation.type === 'GenericTypeAnnotation' &&
      this.getTypeAnnotationName(typeAnnotation) === 'ViewStyleProp';
    return !isStyle;
  }
  getProps(typeDefinition, types) {
    const extendsProps = [];
    const componentPropAsts = [];
    const remaining = [];
    for (const prop of typeDefinition) {
      // find extends
      if (prop.type === 'TSExpressionWithTypeArguments') {
        const extend = extendsForProp(prop, types, this);
        if (extend) {
          extendsProps.push(extend);
          continue;
        }
      }
      remaining.push(prop);
    }

    // find events and props
    for (const prop of flattenProperties(remaining, types, this)) {
      const topLevelType = parseTopLevelType(
        prop.typeAnnotation.typeAnnotation,
        this,
        types,
      );
      if (
        prop.type === 'TSPropertySignature' &&
        !this.isEvent(topLevelType.type) &&
        this.isProp(prop.key.name, prop)
      ) {
        componentPropAsts.push(prop);
      }
    }
    return {
      props: componentPropAsts
        .map(property => buildPropSchema(property, types, this))
        .filter(Boolean),
      extendsProps,
    };
  }
  getProperties(typeName, types) {
    const alias = types[typeName];
    if (!alias) {
      throw new Error(
        `Failed to find definition for "${typeName}", please check that you have a valid codegen typescript file`,
      );
    }
    const aliasKind =
      alias.type === 'TSInterfaceDeclaration' ? 'interface' : 'type';
    try {
      if (aliasKind === 'interface') {
        var _alias$extends;
        return [
          ...((_alias$extends = alias.extends) !== null &&
          _alias$extends !== void 0
            ? _alias$extends
            : []),
          ...alias.body.body,
        ];
      }
      return (
        alias.typeAnnotation.members ||
        alias.typeAnnotation.typeParameters.params[0].members ||
        alias.typeAnnotation.typeParameters.params
      );
    } catch (e) {
      throw new Error(
        `Failed to find ${aliasKind} definition for "${typeName}", please check that you have a valid codegen typescript file`,
      );
    }
  }
  nextNodeForTypeAlias(typeAnnotation) {
    return typeAnnotation.typeAnnotation;
  }
  nextNodeForEnum(typeAnnotation) {
    return typeAnnotation;
  }
  genericTypeAnnotationErrorMessage(typeAnnotation) {
    return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}'), an interface ('${this.interfaceDeclaration}'), or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`;
  }
  extractTypeFromTypeAnnotation(typeAnnotation) {
    return typeAnnotation.type === 'TSTypeReference'
      ? typeAnnotation.typeName.name
      : typeAnnotation.type;
  }
  getObjectProperties(typeAnnotation) {
    return typeAnnotation.members;
  }
  getLiteralValue(option) {
    return option.literal.value;
  }
  getPaperTopLevelNameDeprecated(typeAnnotation) {
    return typeAnnotation.typeParameters.params.length > 1
      ? typeAnnotation.typeParameters.params[1].literal.value
      : null;
  }
}
module.exports = {
  TypeScriptParser,
};
