/**
 * 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 _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('../../parsers/parsers-commons'),
  unwrapNullable = _require.unwrapNullable;
const _require2 = require('./Utils'),
  createAliasResolver = _require2.createAliasResolver,
  getModules = _require2.getModules;
const HostFunctionTemplate = ({
  hasteModuleName,
  propertyName,
  jniSignature,
  jsReturnType,
}) => {
  return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
  static jmethodID cachedMethodId = nullptr;
  return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
}`;
};
const ModuleClassConstructorTemplate = ({
  hasteModuleName,
  eventEmitters,
  methods,
}) => {
  return `
${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params)
  : JavaTurboModule(params) {
${methods
  .map(({propertyName, argCount}) => {
    return `  methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
  })
  .join('\n')}${
    eventEmitters.length > 0
      ? eventEmitters
          .map(eventEmitter => {
            return `
  eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<folly::dynamic>>();`;
          })
          .join('')
      : ''
  }${
    eventEmitters.length > 0
      ? `
  setEventEmitterCallback(params.instance);`
      : ''
  }
}`.trim();
};
const ModuleLookupTemplate = ({moduleName, hasteModuleName}) => {
  return `  if (moduleName == "${moduleName}") {
    return std::make_shared<${hasteModuleName}SpecJSI>(params);
  }`;
};
const FileTemplate = ({libraryName, include, modules, moduleLookups}) => {
  return `
/**
 * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
 *
 * Do not edit this file as changes may cause incorrect behavior and will be lost
 * once the code is regenerated.
 *
 * ${'@'}generated by codegen project: GenerateModuleJniCpp.js
 */

#include ${include}

namespace facebook::react {

${modules}

std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
${moduleLookups.map(ModuleLookupTemplate).join('\n')}
  return nullptr;
}

} // namespace facebook::react
`;
};
function translateReturnTypeToKind(nullableTypeAnnotation, resolveAlias) {
  const _unwrapNullable = unwrapNullable(nullableTypeAnnotation),
    _unwrapNullable2 = _slicedToArray(_unwrapNullable, 1),
    typeAnnotation = _unwrapNullable2[0];
  let realTypeAnnotation = typeAnnotation;
  if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
    realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
  }
  switch (realTypeAnnotation.type) {
    case 'ReservedTypeAnnotation':
      switch (realTypeAnnotation.name) {
        case 'RootTag':
          return 'NumberKind';
        default:
          realTypeAnnotation.name;
          throw new Error(
            `Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
          );
      }
    case 'VoidTypeAnnotation':
      return 'VoidKind';
    case 'StringTypeAnnotation':
      return 'StringKind';
    case 'StringLiteralTypeAnnotation':
      return 'StringKind';
    case 'StringLiteralUnionTypeAnnotation':
      return 'StringKind';
    case 'BooleanTypeAnnotation':
      return 'BooleanKind';
    case 'EnumDeclaration':
      switch (typeAnnotation.memberType) {
        case 'NumberTypeAnnotation':
          return 'NumberKind';
        case 'StringTypeAnnotation':
          return 'StringKind';
        default:
          throw new Error(
            `Unknown enum prop type for returning value, found: ${realTypeAnnotation.type}"`,
          );
      }
    case 'UnionTypeAnnotation':
      switch (typeAnnotation.memberType) {
        case 'NumberTypeAnnotation':
          return 'NumberKind';
        case 'ObjectTypeAnnotation':
          return 'ObjectKind';
        case 'StringTypeAnnotation':
          return 'StringKind';
        default:
          throw new Error(
            `Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
          );
      }
    case 'NumberTypeAnnotation':
      return 'NumberKind';
    case 'NumberLiteralTypeAnnotation':
      return 'NumberKind';
    case 'DoubleTypeAnnotation':
      return 'NumberKind';
    case 'FloatTypeAnnotation':
      return 'NumberKind';
    case 'Int32TypeAnnotation':
      return 'NumberKind';
    case 'PromiseTypeAnnotation':
      return 'PromiseKind';
    case 'GenericObjectTypeAnnotation':
      return 'ObjectKind';
    case 'ObjectTypeAnnotation':
      return 'ObjectKind';
    case 'ArrayTypeAnnotation':
      return 'ArrayKind';
    default:
      realTypeAnnotation.type;
      throw new Error(
        `Unknown prop type for returning value, found: ${realTypeAnnotation.type}"`,
      );
  }
}
function translateParamTypeToJniType(param, resolveAlias) {
  const optional = param.optional,
    nullableTypeAnnotation = param.typeAnnotation;
  const _unwrapNullable3 = unwrapNullable(nullableTypeAnnotation),
    _unwrapNullable4 = _slicedToArray(_unwrapNullable3, 2),
    typeAnnotation = _unwrapNullable4[0],
    nullable = _unwrapNullable4[1];
  const isRequired = !optional && !nullable;
  let realTypeAnnotation = typeAnnotation;
  if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
    realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
  }
  switch (realTypeAnnotation.type) {
    case 'ReservedTypeAnnotation':
      switch (realTypeAnnotation.name) {
        case 'RootTag':
          return !isRequired ? 'Ljava/lang/Double;' : 'D';
        default:
          realTypeAnnotation.name;
          throw new Error(
            `Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
          );
      }
    case 'StringTypeAnnotation':
      return 'Ljava/lang/String;';
    case 'StringLiteralTypeAnnotation':
      return 'Ljava/lang/String;';
    case 'StringLiteralUnionTypeAnnotation':
      return 'Ljava/lang/String;';
    case 'BooleanTypeAnnotation':
      return !isRequired ? 'Ljava/lang/Boolean;' : 'Z';
    case 'EnumDeclaration':
      switch (typeAnnotation.memberType) {
        case 'NumberTypeAnnotation':
          return !isRequired ? 'Ljava/lang/Double;' : 'D';
        case 'StringTypeAnnotation':
          return 'Ljava/lang/String;';
        default:
          throw new Error(
            `Unknown enum prop type for method arg, found: ${realTypeAnnotation.type}"`,
          );
      }
    case 'UnionTypeAnnotation':
      switch (typeAnnotation.memberType) {
        case 'NumberTypeAnnotation':
          return !isRequired ? 'Ljava/lang/Double;' : 'D';
        case 'ObjectTypeAnnotation':
          return 'Lcom/facebook/react/bridge/ReadableMap;';
        case 'StringTypeAnnotation':
          return 'Ljava/lang/String;';
        default:
          throw new Error(
            `Unsupported union prop value, found: ${realTypeAnnotation.memberType}"`,
          );
      }
    case 'NumberTypeAnnotation':
      return !isRequired ? 'Ljava/lang/Double;' : 'D';
    case 'NumberLiteralTypeAnnotation':
      return !isRequired ? 'Ljava/lang/Double;' : 'D';
    case 'DoubleTypeAnnotation':
      return !isRequired ? 'Ljava/lang/Double;' : 'D';
    case 'FloatTypeAnnotation':
      return !isRequired ? 'Ljava/lang/Double;' : 'D';
    case 'Int32TypeAnnotation':
      return !isRequired ? 'Ljava/lang/Double;' : 'D';
    case 'GenericObjectTypeAnnotation':
      return 'Lcom/facebook/react/bridge/ReadableMap;';
    case 'ObjectTypeAnnotation':
      return 'Lcom/facebook/react/bridge/ReadableMap;';
    case 'ArrayTypeAnnotation':
      return 'Lcom/facebook/react/bridge/ReadableArray;';
    case 'FunctionTypeAnnotation':
      return 'Lcom/facebook/react/bridge/Callback;';
    default:
      realTypeAnnotation.type;
      throw new Error(
        `Unknown prop type for method arg, found: ${realTypeAnnotation.type}"`,
      );
  }
}
function translateReturnTypeToJniType(nullableTypeAnnotation, resolveAlias) {
  const _unwrapNullable5 = unwrapNullable(nullableTypeAnnotation),
    _unwrapNullable6 = _slicedToArray(_unwrapNullable5, 2),
    typeAnnotation = _unwrapNullable6[0],
    nullable = _unwrapNullable6[1];
  let realTypeAnnotation = typeAnnotation;
  if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
    realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
  }
  switch (realTypeAnnotation.type) {
    case 'ReservedTypeAnnotation':
      switch (realTypeAnnotation.name) {
        case 'RootTag':
          return nullable ? 'Ljava/lang/Double;' : 'D';
        default:
          realTypeAnnotation.name;
          throw new Error(
            `Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
          );
      }
    case 'VoidTypeAnnotation':
      return 'V';
    case 'StringTypeAnnotation':
      return 'Ljava/lang/String;';
    case 'StringLiteralTypeAnnotation':
      return 'Ljava/lang/String;';
    case 'StringLiteralUnionTypeAnnotation':
      return 'Ljava/lang/String;';
    case 'BooleanTypeAnnotation':
      return nullable ? 'Ljava/lang/Boolean;' : 'Z';
    case 'EnumDeclaration':
      switch (typeAnnotation.memberType) {
        case 'NumberTypeAnnotation':
          return nullable ? 'Ljava/lang/Double;' : 'D';
        case 'StringTypeAnnotation':
          return 'Ljava/lang/String;';
        default:
          throw new Error(
            `Unknown enum prop type for method return type, found: ${realTypeAnnotation.type}"`,
          );
      }
    case 'UnionTypeAnnotation':
      switch (typeAnnotation.memberType) {
        case 'NumberTypeAnnotation':
          return nullable ? 'Ljava/lang/Double;' : 'D';
        case 'ObjectTypeAnnotation':
          return 'Lcom/facebook/react/bridge/WritableMap;';
        case 'StringTypeAnnotation':
          return 'Ljava/lang/String;';
        default:
          throw new Error(
            `Unsupported union member type, found: ${realTypeAnnotation.memberType}"`,
          );
      }
    case 'NumberTypeAnnotation':
      return nullable ? 'Ljava/lang/Double;' : 'D';
    case 'NumberLiteralTypeAnnotation':
      return nullable ? 'Ljava/lang/Double;' : 'D';
    case 'DoubleTypeAnnotation':
      return nullable ? 'Ljava/lang/Double;' : 'D';
    case 'FloatTypeAnnotation':
      return nullable ? 'Ljava/lang/Double;' : 'D';
    case 'Int32TypeAnnotation':
      return nullable ? 'Ljava/lang/Double;' : 'D';
    case 'PromiseTypeAnnotation':
      return 'Lcom/facebook/react/bridge/Promise;';
    case 'GenericObjectTypeAnnotation':
      return 'Lcom/facebook/react/bridge/WritableMap;';
    case 'ObjectTypeAnnotation':
      return 'Lcom/facebook/react/bridge/WritableMap;';
    case 'ArrayTypeAnnotation':
      return 'Lcom/facebook/react/bridge/WritableArray;';
    default:
      realTypeAnnotation.type;
      throw new Error(
        `Unknown prop type for method return type, found: ${realTypeAnnotation.type}"`,
      );
  }
}
function translateMethodTypeToJniSignature(property, resolveAlias) {
  const name = property.name,
    typeAnnotation = property.typeAnnotation;
  let _unwrapNullable7 = unwrapNullable(typeAnnotation),
    _unwrapNullable8 = _slicedToArray(_unwrapNullable7, 1),
    _unwrapNullable8$ = _unwrapNullable8[0],
    returnTypeAnnotation = _unwrapNullable8$.returnTypeAnnotation,
    params = _unwrapNullable8$.params;
  params = [...params];
  let processedReturnTypeAnnotation = returnTypeAnnotation;
  const isPromiseReturn = returnTypeAnnotation.type === 'PromiseTypeAnnotation';
  if (isPromiseReturn) {
    processedReturnTypeAnnotation = {
      type: 'VoidTypeAnnotation',
    };
  }
  const argsSignatureParts = params.map(t => {
    return translateParamTypeToJniType(t, resolveAlias);
  });
  if (isPromiseReturn) {
    // Additional promise arg for this case.
    argsSignatureParts.push(
      translateReturnTypeToJniType(returnTypeAnnotation, resolveAlias),
    );
  }
  const argsSignature = argsSignatureParts.join('');
  const returnSignature =
    name === 'getConstants'
      ? 'Ljava/util/Map;'
      : translateReturnTypeToJniType(
          processedReturnTypeAnnotation,
          resolveAlias,
        );
  return `(${argsSignature})${returnSignature}`;
}
function translateMethodForImplementation(
  hasteModuleName,
  property,
  resolveAlias,
) {
  const _unwrapNullable9 = unwrapNullable(property.typeAnnotation),
    _unwrapNullable10 = _slicedToArray(_unwrapNullable9, 1),
    propertyTypeAnnotation = _unwrapNullable10[0];
  const returnTypeAnnotation = propertyTypeAnnotation.returnTypeAnnotation;
  if (
    property.name === 'getConstants' &&
    returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
    returnTypeAnnotation.properties.length === 0
  ) {
    return '';
  }
  return HostFunctionTemplate({
    hasteModuleName,
    propertyName: property.name,
    jniSignature: translateMethodTypeToJniSignature(property, resolveAlias),
    jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias),
  });
}
module.exports = {
  generate(
    libraryName,
    schema,
    packageName,
    assumeNonnull = false,
    headerPrefix,
  ) {
    const nativeModules = getModules(schema);
    const modules = Object.keys(nativeModules)
      .filter(hasteModuleName => {
        const module = nativeModules[hasteModuleName];
        return !(
          module.excludedPlatforms != null &&
          module.excludedPlatforms.includes('android')
        );
      })
      .sort()
      .map(hasteModuleName => {
        const _nativeModules$hasteM = nativeModules[hasteModuleName],
          aliasMap = _nativeModules$hasteM.aliasMap,
          _nativeModules$hasteM2 = _nativeModules$hasteM.spec,
          eventEmitters = _nativeModules$hasteM2.eventEmitters,
          methods = _nativeModules$hasteM2.methods;
        const resolveAlias = createAliasResolver(aliasMap);
        const translatedMethods = methods
          .map(property =>
            translateMethodForImplementation(
              hasteModuleName,
              property,
              resolveAlias,
            ),
          )
          .join('\n\n');
        return (
          translatedMethods +
          '\n\n' +
          ModuleClassConstructorTemplate({
            hasteModuleName,
            eventEmitters,
            methods: methods
              .map(({name: propertyName, typeAnnotation}) => {
                const _unwrapNullable11 = unwrapNullable(typeAnnotation),
                  _unwrapNullable12 = _slicedToArray(_unwrapNullable11, 1),
                  _unwrapNullable12$ = _unwrapNullable12[0],
                  returnTypeAnnotation =
                    _unwrapNullable12$.returnTypeAnnotation,
                  params = _unwrapNullable12$.params;
                if (
                  propertyName === 'getConstants' &&
                  returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
                  returnTypeAnnotation.properties &&
                  returnTypeAnnotation.properties.length === 0
                ) {
                  return null;
                }
                return {
                  propertyName,
                  argCount: params.length,
                };
              })
              .filter(Boolean),
          })
        );
      })
      .join('\n');
    const moduleLookups = Object.keys(nativeModules)
      .filter(hasteModuleName => {
        const module = nativeModules[hasteModuleName];
        return !(
          module.excludedPlatforms != null &&
          module.excludedPlatforms.includes('android')
        );
      })
      .sort((a, b) => {
        const nameA = nativeModules[a].moduleName;
        const nameB = nativeModules[b].moduleName;
        if (nameA < nameB) {
          return -1;
        } else if (nameA > nameB) {
          return 1;
        }
        return 0;
      })
      .map(hasteModuleName => ({
        moduleName: nativeModules[hasteModuleName].moduleName,
        hasteModuleName,
      }));
    const fileName = `${libraryName}-generated.cpp`;
    const replacedTemplate = FileTemplate({
      modules: modules,
      libraryName: libraryName.replace(/-/g, '_'),
      moduleLookups,
      include: `"${libraryName}.h"`,
    });
    return new Map([[`jni/${fileName}`, replacedTemplate]]);
  },
};
