/**
 * Copyright (c) 2024 Huawei Technologies Co., Ltd.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//@ts-check
const pathUtils = require('path');
const fs = require('fs');
const colors = require('colors/safe');

const HARMONY_PLATFORM_NAME = 'harmony';
const RNOH_FALLBACK_PLATFORM_NAME = 'ios';

/**
 * @param msg {string}
 */
function info(msg) {
  const infoPrefix = '[' + colors.bold(colors.cyan(`INFO`)) + ']';
  console.log(infoPrefix, msg);
}

/**
 * @type {string | null}
 */
let REQUEST_RESOLUTION_LATEST_PLATFORM = null;

/**
 * @param options {import("./metro.config").HarmonyMetroConfigOptions}
 * @returns {import("metro-config").InputConfigT}
 */
function createHarmonyMetroConfig(options) {
  /**
   * The default value needs to be changed to @react-native-oh/react-native-harmony but this is a breaking change.
   */
  const reactNativeHarmonyPackageName =
    options?.reactNativeHarmonyPackageName ?? 'react-native-harmony';
  const reactNativeHarmonyPattern =
    options?.__reactNativeHarmonyPattern ??
    pathUtils.sep +
      reactNativeHarmonyPackageName.replace('/', pathUtils.sep) +
      pathUtils.sep;
  const reactNativeInteropLibraryPackagePattern =
    options?.__reactNativeInteropLibraryPackagePattern;

  return {
    transformer: {
      assetRegistryPath: 'react-native/Libraries/Image/AssetRegistry',
      getTransformOptions: async () => ({
        transform: {
          experimentalImportSupport: false,
          inlineRequires: true,
        },
      }),
    },
    serializer: {
      getModulesRunBeforeMainModule: () => {
        if (REQUEST_RESOLUTION_LATEST_PLATFORM !== 'harmony') {
          return [];
        }
        return [require.resolve('./Libraries/Core/InitializeCore')];
      },
    },
    resolver: {
      blockList: [/\.cxx/],
      resolveRequest: (ctx, moduleName, platform) =>
        resolveRequestForPlatform(
          ctx,
          moduleName,
          platform,
          reactNativeHarmonyPackageName,
          reactNativeHarmonyPattern,
          reactNativeInteropLibraryPackagePattern
        ),
    },
  };
}

module.exports = {
  createHarmonyMetroConfig,
};

/**
 * @param ctx {Parameters<NonNullable<import("metro-config").ResolverConfigT["resolveRequest"]>>[0]}
 * @param moduleName {string}
 * @param nodeModulesPaths {string[]}
 * @param reactNativeHarmonyPackageName {string}
 * @param reactNativeInteropLibraryPackagePattern {string | undefined}
 */
function resolveIfInteropLibraryRequest(
  ctx,
  moduleName,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeInteropLibraryPackagePattern
) {
  const rnInteropLibraryPackageName =
    getHarmonyPackageByAliasMap(nodeModulesPaths)[reactNativeHarmonyPackageName]
      ?.name;

  /**
   * We have to check if the module is not resolved from interop package
   * to prevent an infinite resolution loop caused by a circular dependency.
   * e.g.
   * origin module: react-native-harmony/Libraries/Image/ImageSourceUtils.js
   * redirected to module: react-native-harmony-61-interop/Libraries/Image/ImageSourceUtils.js
   * and react-native-harmony-61-interop/Libraries/Image/ImageSourceUtils.js imports react-native-harmony/Libraries/Image/ImageSourceUtils.js
   * This creates a circular dependency, causing an infinite resolution loop.
   */
  if (
    rnInteropLibraryPackageName &&
    !ctx.originModulePath.includes(
      reactNativeInteropLibraryPackagePattern ??
        getRNInteropLibraryPackagePattern(rnInteropLibraryPackageName)
    )
  ) {
    try {
      const newModuleName = moduleName.replace(
        reactNativeHarmonyPackageName,
        rnInteropLibraryPackageName
      );
      return ctx.resolveRequest(ctx, newModuleName, HARMONY_PLATFORM_NAME);
    } catch {}
  }
  return null;
}

function resolveRequestForPlatform(
  ctx,
  moduleName,
  platform,
  reactNativeHarmonyPackageName,
  reactNativeHarmonyPattern,
  reactNativeInteropLibraryPackagePattern
) {
  REQUEST_RESOLUTION_LATEST_PLATFORM = platform;
  const nodeModulesPaths = [
    pathUtils.resolve('node_modules'),
    ...(ctx.nodeModulesPaths ?? []),
  ];
  if (platform !== HARMONY_PLATFORM_NAME) {
    return ctx.resolveRequest(ctx, moduleName, platform);
  }
  const harmonyResolutionResult = resolveHarmonyPlatformRequest(
    ctx,
    moduleName,
    nodeModulesPaths,
    reactNativeHarmonyPackageName,
    reactNativeHarmonyPattern,
    reactNativeInteropLibraryPackagePattern
  );
  if (harmonyResolutionResult) {
    return harmonyResolutionResult;
  }
  return ctx.resolveRequest(ctx, moduleName, platform);
}

function resolveHarmonyPlatformRequest(
  ctx,
  moduleName,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeHarmonyPattern,
  reactNativeInteropLibraryPackagePattern
) {
  return (
    resolveReactNativeRequestIfNeeded(
      ctx,
      moduleName,
      nodeModulesPaths,
      reactNativeHarmonyPackageName,
      reactNativeInteropLibraryPackagePattern
    ) ??
    resolveHarmonyPackageRequestIfNeeded(
      ctx,
      moduleName,
      nodeModulesPaths,
      reactNativeHarmonyPackageName,
      reactNativeInteropLibraryPackagePattern
    ) ??
    resolveImportFromHarmonyPackageDirectoryIfNeeded(
      ctx,
      moduleName,
      nodeModulesPaths,
      reactNativeHarmonyPackageName,
      reactNativeHarmonyPattern,
      reactNativeInteropLibraryPackagePattern
    ) ??
    resolveAliasedInternalImportIfNeeded(
      ctx,
      moduleName,
      nodeModulesPaths
    ) ??
    resolveVirtualizedListImportIfNeeded(
      ctx,
      moduleName,
      nodeModulesPaths,
      reactNativeHarmonyPackageName,
      reactNativeInteropLibraryPackagePattern
    ) ??
    resolveGenericHarmonyAliasImport(ctx, moduleName, nodeModulesPaths) ??
    null
  );
}

function resolveReactNativeRequestIfNeeded(
  ctx,
  moduleName,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeInteropLibraryPackagePattern
) {
  if (!isReactNativeRequest(moduleName)) {
    return null;
  }
  return resolveReactNativeImport(
    ctx,
    moduleName,
    nodeModulesPaths,
    reactNativeHarmonyPackageName,
    reactNativeInteropLibraryPackagePattern
  );
}

function resolveHarmonyPackageRequestIfNeeded(
  ctx,
  moduleName,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeInteropLibraryPackagePattern
) {
  if (!isHarmonyPackageRequest(moduleName, reactNativeHarmonyPackageName)) {
    return null;
  }
  return resolveHarmonyPackageImport(
    ctx,
    moduleName,
    nodeModulesPaths,
    reactNativeHarmonyPackageName,
    reactNativeInteropLibraryPackagePattern
  );
}

function resolveImportFromHarmonyPackageDirectoryIfNeeded(
  ctx,
  moduleName,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeHarmonyPattern,
  reactNativeInteropLibraryPackagePattern
) {
  if (!ctx.originModulePath.includes(reactNativeHarmonyPattern)) {
    return null;
  }
  return resolveImportFromHarmonyPackageDirectory(
    ctx,
    moduleName,
    nodeModulesPaths,
    reactNativeHarmonyPackageName,
    reactNativeHarmonyPattern,
    reactNativeInteropLibraryPackagePattern
  );
}

function resolveAliasedInternalImportIfNeeded(
  ctx,
  moduleName,
  nodeModulesPaths
) {
  if (
    !isHarmonyPackageInternalImport(
      nodeModulesPaths,
      ctx.originModulePath,
      moduleName
    )
  ) {
    return null;
  }
  return resolveAliasedInternalImport(
    ctx,
    moduleName,
    nodeModulesPaths
  );
}

function resolveVirtualizedListImportIfNeeded(
  ctx,
  moduleName,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeInteropLibraryPackagePattern
) {
  if (!isVirtualizedListImport(ctx.originModulePath, moduleName)) {
    return null;
  }
  return resolveVirtualizedListImport(
    ctx,
    nodeModulesPaths,
    reactNativeHarmonyPackageName,
    reactNativeInteropLibraryPackagePattern
  );
}

function isReactNativeRequest(moduleName) {
  return moduleName === 'react-native' || moduleName.startsWith('react-native/');
}

function isHarmonyPackageRequest(moduleName, reactNativeHarmonyPackageName) {
  return (
    moduleName === reactNativeHarmonyPackageName ||
    moduleName.startsWith(`${reactNativeHarmonyPackageName}/`)
  );
}

function resolveReactNativeImport(
  ctx,
  moduleName,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeInteropLibraryPackagePattern
) {
  const newModuleName = moduleName.replace(
    'react-native',
    reactNativeHarmonyPackageName
  );
  const maybeInteropLibraryResult = resolveIfInteropLibraryRequest(
    ctx,
    newModuleName,
    nodeModulesPaths,
    reactNativeHarmonyPackageName,
    reactNativeInteropLibraryPackagePattern
  );
  if (maybeInteropLibraryResult) {
    return maybeInteropLibraryResult;
  }
  return resolveWithHarmonyFallback(ctx, newModuleName);
}

function resolveHarmonyPackageImport(
  ctx,
  moduleName,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeInteropLibraryPackagePattern
) {
  const result = resolveIfInteropLibraryRequest(
    ctx,
    moduleName,
    nodeModulesPaths,
    reactNativeHarmonyPackageName,
    reactNativeInteropLibraryPackagePattern
  );
  if (result) {
    return result;
  }
  const maybeResult = resolveRequestOnlyForHarmony(ctx, moduleName);
  if (maybeResult) {
    return maybeResult;
  }
  return resolveWithHarmonyFallback(ctx, moduleName);
}

function resolveImportFromHarmonyPackageDirectory(
  ctx,
  moduleName,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeHarmonyPattern,
  reactNativeInteropLibraryPackagePattern
) {
  const rnInteropLibraryPackage =
    getHarmonyPackageByAliasMap(nodeModulesPaths)[reactNativeHarmonyPackageName];
  if (rnInteropLibraryPackage && moduleName.startsWith('.')) {
    const redirectInternalImports =
      rnInteropLibraryPackage.redirectInternalImports;
    if (redirectInternalImports) {
      const moduleAbsPath = pathUtils.resolve(
        pathUtils.dirname(ctx.originModulePath),
        moduleName
      );
      try {
        const newModuleName = moduleAbsPath.replace(
          reactNativeHarmonyPattern,
          reactNativeInteropLibraryPackagePattern ??
            getRNInteropLibraryPackagePattern(rnInteropLibraryPackage.name)
        );
        return ctx.resolveRequest(ctx, newModuleName, HARMONY_PLATFORM_NAME);
      } catch {}
    }
  }
  const maybeResult = resolveRequestOnlyForHarmony(ctx, moduleName);
  if (maybeResult) {
    return maybeResult;
  }
  return ctx.resolveRequest(ctx, moduleName, RNOH_FALLBACK_PLATFORM_NAME);
}

function resolveAliasedInternalImport(ctx, moduleName, nodeModulesPaths) {
  const alias = getPackageNameFromOriginModulePath(ctx.originModulePath);
  if (!alias) {
    return null;
  }
  const harmonyPackage = getHarmonyPackageByAliasMap(nodeModulesPaths);
  const harmonyPackageName = harmonyPackage[alias]?.name;
  const redirectInternalImports = harmonyPackage[alias]?.redirectInternalImports;
  if (
    !harmonyPackageName ||
    isRequestFromHarmonyPackage(ctx.originModulePath, harmonyPackageName) ||
    !redirectInternalImports
  ) {
    return null;
  }
  const moduleAbsPath = pathUtils.resolve(
    pathUtils.dirname(ctx.originModulePath),
    moduleName
  );
  const modulePathRelativeToOriginalPackage = extractPackageRelativePath(
    moduleAbsPath,
    alias
  );
  if (!modulePathRelativeToOriginalPackage) {
    return null;
  }
  const newModuleName = `${harmonyPackageName}/${modulePathRelativeToOriginalPackage}`;
  try {
    return ctx.resolveRequest(ctx, newModuleName, HARMONY_PLATFORM_NAME);
  } catch {
    return null;
  }
}

function isVirtualizedListImport(originModulePath, moduleName) {
  return (
    originModulePath.includes('@react-native/virtualized-lists') &&
    moduleName === './VirtualizedList'
  );
}

function resolveVirtualizedListImport(
  ctx,
  nodeModulesPaths,
  reactNativeHarmonyPackageName,
  reactNativeInteropLibraryPackagePattern
) {
  const rnInteropLibraryPackage =
    getHarmonyPackageByAliasMap(nodeModulesPaths)[reactNativeHarmonyPackageName];
  if (!rnInteropLibraryPackage) {
    return null;
  }
  const newModuleName =
    `@react-native-oh` +
    (reactNativeInteropLibraryPackagePattern ??
      getRNInteropLibraryPackagePattern(rnInteropLibraryPackage.name)) +
    `patched-virtualized-list${pathUtils.sep}VirtualizedList`;
  try {
    return ctx.resolveRequest(ctx, newModuleName, HARMONY_PLATFORM_NAME);
  } catch {
    return null;
  }
}

function resolveGenericHarmonyAliasImport(ctx, moduleName, nodeModulesPaths) {
  const harmonyPackageByAlias = getHarmonyPackageByAliasMap(nodeModulesPaths);
  const alias = getPackageName(moduleName);
  if (!alias) {
    return null;
  }
  const harmonyPackageName = harmonyPackageByAlias[alias]?.name;
  if (
    !harmonyPackageName ||
    isRequestFromHarmonyPackage(ctx.originModulePath, harmonyPackageName)
  ) {
    return null;
  }
  return ctx.resolveRequest(
    ctx,
    moduleName.replace(alias, harmonyPackageName),
    HARMONY_PLATFORM_NAME
  );
}

function resolveWithHarmonyFallback(ctx, moduleName) {
  try {
    return ctx.resolveRequest(ctx, moduleName, HARMONY_PLATFORM_NAME);
  } catch {
    return ctx.resolveRequest(ctx, moduleName, RNOH_FALLBACK_PLATFORM_NAME);
  }
}

function extractPackageRelativePath(moduleAbsPath, alias) {
  const normalizedAlias = alias.replace(/\//g, pathUtils.sep);
  const expectedPrefix = `${pathUtils.sep}node_modules${pathUtils.sep}${normalizedAlias}${pathUtils.sep}`;
  const parts = moduleAbsPath.split(expectedPrefix);
  if (parts.length < 2 || !parts[1]) {
    return null;
  }
  return parts[1].replace(/\\/g, '/');
}

/**
 * Let's say we have following files:
 * foo.js
 * foo.harmony.tsx
 *
 * By default, in that situation foo.js will be resolved. This function however chooses foo.harmony.tsx.
 * In the past, RNOH redirected imports back to the RN package, and RNOH used different extensions than original files.
 *
 * @param ctx {Parameters<NonNullable<import("metro-config").ResolverConfigT["resolveRequest"]>>[0]}
 * @param moduleName {string}
 */
function resolveRequestOnlyForHarmony(ctx, moduleName) {
  for (const sourceExt of ctx.sourceExts) {
    const result = resolveHarmonyRequestForSourceExt(ctx, moduleName, sourceExt);
    if (isHarmonySourceFileResult(result)) {
      return result;
    }
  }
  return null;
}

function resolveHarmonyRequestForSourceExt(ctx, moduleName, sourceExt) {
  try {
    return ctx.resolveRequest(
      { ...ctx, sourceExts: [sourceExt] },
      moduleName,
      HARMONY_PLATFORM_NAME
    );
  } catch {
    return null;
  }
}

function isHarmonySourceFileResult(result) {
  if (!result || result.type !== 'sourceFile') {
    return false;
  }
  const lastDotIndex = result.filePath.lastIndexOf('.');
  const beforeLastDot = result.filePath.substring(0, lastDotIndex);
  return beforeLastDot.endsWith('.' + HARMONY_PLATFORM_NAME);
}

/**
 * @param moduleName {string}
 * @returns {string | null}
 */
function getPackageName(moduleName) {
  if (moduleName.startsWith('.')) {
    return null;
  }
  if (moduleName.startsWith('@')) {
    const segments = moduleName.split('/', 3);
    if (segments.length === 2) {
      return moduleName;
    }
    if (segments.length > 2) {
      return `${segments[0]}/${segments[1]}`;
    }
    return null;
  }
  if (moduleName.includes('/')) {
    return moduleName.split('/')[0];
  }
  return moduleName;
}

/**
 * @param originModulePath {string}
 * @returns {string}
 */
function getPackageNameFromOriginModulePath(originModulePath) {
  const nodeModulesPosition = originModulePath.search('node_modules');
  if (nodeModulesPosition === -1) {
    return null;
  }
  const pathRelativeToNodeModules =
    originModulePath.substring(nodeModulesPosition);
  const pathSegments = pathRelativeToNodeModules.split(pathUtils.sep);
  const module = pathSegments[1];
  if (!module) {
    return null;
  }
  if (module.startsWith('@')) {
    if (!pathSegments[2]) {
      return null;
    }
    return `${pathSegments[1]}/${pathSegments[2]}`;
  }
  return pathSegments[1];
}

/**
 * @param nodeModulesPaths {readonly string[]}
 * @param originModulePath {string}
 * @param moduleName {string}
 * @returns {boolean}
 */
function isHarmonyPackageInternalImport(
  nodeModulesPaths,
  originModulePath,
  moduleName
) {
  if (moduleName.startsWith('.')) {
    const alias = getPackageNameFromOriginModulePath(originModulePath);
    const slashes = new RegExp('/', 'g');
    if (alias && originModulePath.includes(`${pathUtils.sep}node_modules${pathUtils.sep}${alias.replace(slashes, pathUtils.sep)}${pathUtils.sep}`)) {
      const harmonyPackage = getHarmonyPackageByAliasMap(nodeModulesPaths);
      const harmonyPackageName = harmonyPackage[alias]?.name;
      if (
        harmonyPackageName &&
        !isRequestFromHarmonyPackage(originModulePath, harmonyPackageName)
      ) {
        return true;
      }
    }
  }

  return false;
}

/**
 * @param originModulePath {string}
 * @param harmonyPackageName {string}
 * @returns {boolean}
 */
function isRequestFromHarmonyPackage(originModulePath, harmonyPackageName) {
  const slashes = new RegExp('/', 'g');
  const packagePath = harmonyPackageName.replace(slashes, pathUtils.sep);

  return originModulePath.includes(
    `${pathUtils.sep}node_modules${pathUtils.sep}${packagePath}${pathUtils.sep}`
  );
}

/**
 * @type {Record<string, {name: string, redirectInternalImports: boolean}> | undefined}
 */
let cachedHarmonyPackageByAliasMap = undefined;

/**
 * @param nodeModulesPaths {readonly string[]}
 */
function getHarmonyPackageByAliasMap(nodeModulesPaths) {
  /**
   * @type {Record<string, {name: string, redirectInternalImports: boolean}>}
   */
  const initialAcc = {};
  if (cachedHarmonyPackageByAliasMap) {
    return cachedHarmonyPackageByAliasMap;
  }
  const harmonyNodeModules = findHarmonyNodeModulePaths(
    findHarmonyNodeModuleSearchPaths(nodeModulesPaths)
  );
  cachedHarmonyPackageByAliasMap = buildHarmonyPackageByAliasMap(
    harmonyNodeModules,
    initialAcc
  );
  const harmonyPackagesCount = Object.keys(
    cachedHarmonyPackageByAliasMap
  ).length;
  if (harmonyPackagesCount > 0) {
    const prettyHarmonyPackagesCount = colors.bold(
      harmonyPackagesCount > 0
        ? colors.green(harmonyPackagesCount.toString())
        : harmonyPackagesCount.toString()
    );
    info(
      `Redirected imports to ${prettyHarmonyPackagesCount} harmony-specific third-party package(s):`
    );
    if (harmonyPackagesCount > 0) {
      Object.entries(cachedHarmonyPackageByAliasMap).forEach(
        ([original, { name: alias }]) => {
          info(
            `• ${colors.bold(colors.gray(original))}${colors.bold(alias)}`
          );
        }
      );
    }
  } else {
    info('No harmony-specific third-party packages have been detected');
  }
  console.log('');
  return cachedHarmonyPackageByAliasMap;
}

function buildHarmonyPackageByAliasMap(harmonyNodeModules, initialAcc) {
  return harmonyNodeModules.reduce((acc, nodeModule) => {
    const harmonyNodeModuleName = getHarmonyNodeModuleName(nodeModule);
    if (!harmonyNodeModuleName) {
      return acc;
    }
    const packageJsonPath = `${nodeModule.resolvedPath}${pathUtils.sep}package.json`;
    const packageJson = readHarmonyModulePackageJSON(packageJsonPath);
    const alias = packageJson.harmony?.alias;
    if (!alias) {
      return acc;
    }
    acc[alias] = {
      name: harmonyNodeModuleName,
      redirectInternalImports: packageJson?.harmony?.redirectInternalImports ?? false,
    };
    return acc;
  }, initialAcc);
}

function getHarmonyNodeModuleName({ resolvedPath, unresolvedPath }) {
  const harmonyNodeModulePath = resolvedPath.includes('node_modules')
    ? resolvedPath
    : unresolvedPath;
  const segments = harmonyNodeModulePath.split(pathUtils.sep);
  if (segments.length === 0) {
    return null;
  }
  let harmonyNodeModuleName = segments[segments.length - 1];
  if (segments.length > 1) {
    const parentDir = segments[segments.length - 2];
    if (parentDir.startsWith('@')) {
      harmonyNodeModuleName = `${parentDir}/${harmonyNodeModuleName}`;
    }
  }
  return harmonyNodeModuleName;
}

/**
 * @param nodeModulesPaths {readonly string[]}
 * @returns {string[]}
 */
function findHarmonyNodeModuleSearchPaths(nodeModulesPaths) {
  /**
   * @type string[]
   */
  let searchPaths = [];
  for (const nodeModulesPath of nodeModulesPaths) {
    if (fs.existsSync(nodeModulesPath)) {
      fs.readdirSync(nodeModulesPath)
        .filter((dirName) => dirName.startsWith('@'))
        .forEach((dirName) => {
          searchPaths.push(`${nodeModulesPath}${pathUtils.sep}${dirName}`);
        });
      searchPaths.push(nodeModulesPath);
    }
  }
  return searchPaths;
}

/**
 * @param searchPaths {string[]}
 * @returns {{resolvedPath:string, unresolvedPath: string}[]}
 */
function findHarmonyNodeModulePaths(searchPaths) {
  return searchPaths
    .map((searchPath) =>
      fs
        .readdirSync(searchPath, { withFileTypes: true })
        .map((dirent) => createHarmonyNodeModulePaths(searchPath, dirent))
        .filter(({ resolvedPath }) => hasPackageJSON(resolvedPath))
    )
    .flat();
}

function createHarmonyNodeModulePaths(searchPath, dirent) {
  const direntPath =
    (dirent.parentPath ?? dirent.path) + pathUtils.sep + dirent.name;
  if (dirent.isSymbolicLink()) {
    return {
      resolvedPath: pathUtils.resolve(
        dirent.parentPath ?? dirent.path,
        fs.readlinkSync(direntPath)
      ),
      unresolvedPath: pathUtils.resolve(dirent.parentPath ?? dirent.path, direntPath),
    };
  }
  return { resolvedPath: direntPath, unresolvedPath: direntPath };
}

/**
 * @param nodeModulePath {string}
 * @returns {boolean}
 */
function hasPackageJSON(nodeModulePath) {
  if (!fs.existsSync(nodeModulePath)) {
    return false;
  }
  if (!fs.lstatSync(nodeModulePath).isDirectory()) {
    return false;
  }
  const nodeModuleContentNames = fs.readdirSync(nodeModulePath);
  return nodeModuleContentNames.includes('package.json');
}

/**
 * @param packageJSONPath {string}
 * @returns {{name: string, harmony?: {alias?: string, redirectInternalImports?: boolean}}}
 */
function readHarmonyModulePackageJSON(packageJSONPath) {
  return JSON.parse(fs.readFileSync(packageJSONPath).toString());
}

/**
 * @param rnInteropLibraryPackageName {string}
 * @param isInMonorepo {boolean}
 * @returns {string} - Either package name without the scope or the full package name with platform specific separator
 */
function getRNInteropLibraryPackagePattern(rnInteropLibraryPackageName) {
  return `${pathUtils.sep}${rnInteropLibraryPackageName.replace('/', pathUtils.sep)}${pathUtils.sep}`;
}