#!/usr/bin/env node
/**
 * Copyright (c) 2025 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.
 */

import path from 'node:path';
import os from 'node:os';
import { Command } from 'commander';
import inquirer from 'inquirer';
import chalk from 'chalk';
import fs from 'fs-extra';
import JSON5 from 'json5';
import { getLoader, logger } from '@react-native-community/cli-tools';
import { RealCliExecutor } from '../io/CliExecutor';
import { AbsolutePath } from '../core/AbsolutePath';
import { DescriptiveError } from '../core/DescriptiveError';

const VALID_APP_NAME_REG = /^[A-Za-z][A-Za-z0-9_]*[A-Za-z0-9]$/;
const DEFAULT_PACKAGE_PREFIX = 'com.example.';
const HARMONY_DIR = 'harmony';
const JSON5_EXT = '.json5';
const DEFAULT_ENCODING = 'utf8';
const MODULE_DESC = 'module_desc';
const ENTRY_ABILITY_DESC = 'EntryAbility_desc';
const ENTRY_ABILITY_LABEL = 'EntryAbility_label';
const REACT_NATIVE = 'react-native';
const NODE_MODULES = 'node_modules';
const STRING_TYPE = 'string';
const PROJECT_NAME_INDEX = 0;
const JSON_INDENTATION_LENGTH = 2;
const GIT_EXE = 'git';
const NODE_EXE = 'node';
const NPM_EXE = 'npm';
const APP_JSON = 'app.json';
const APP_JSON5 = 'app.json5';
const STRING_JSON = 'string.json';
const PACKAGE_JSON = 'package.json';
const TEMPLATE_CONFIG_JS = 'template.config.js';
const METRO_CONFIG_JS = 'metro.config.js';
const DEFAULT_TEMPLATE_PKG = '@react-native-oh/template';
const REACT_NATIVE_OH_REACT_NATIVE_HARMONY = '@react-native-oh/react-native-harmony';
const REACT_NATIVE_OH_REACT_NATIVE_HARMONY_CLI = '@react-native-oh/react-native-harmony-cli';

interface CliOptions {
  version?: string;
  template?: string;
}

interface TemplateConfig {
  placeholderName: string;
  templateDir: string;
  postInitScript?: string;
  titlePlaceholder?: string;
}

interface UpdateConfig {
  destDir: string;
  templateRoot: string;
  templateConfig: TemplateConfig;
  projectName: string;
  packageName: string;
  isHarmonyTemplate: boolean;
  rnohVersion: string | undefined;
  rnohCliVersion: string | undefined,
  rnohTemplateConfig: TemplateConfig | undefined;
}

/**
 * Helper class for file operations (JSON and text file updates).
 */
class FileOperationHelper {
  static async updateJsonFile(filePath: string, updateFn: (data: any) => any, errorMessage: string): Promise<void> {
    if (!await fs.pathExists(filePath)) {
      return;
    }

    try {
      const content = await fs.readFile(filePath, DEFAULT_ENCODING);
      const data = JSON5.parse(content);
      const updatedData = updateFn(data);

      const outputContent = filePath.endsWith(JSON5_EXT)
        ? JSON5.stringify(updatedData, null, JSON_INDENTATION_LENGTH)
        : JSON.stringify(updatedData, null, JSON_INDENTATION_LENGTH);

      await fs.writeFile(filePath, outputContent);
    } catch (e: any) {
      throw new DescriptiveError({
        whatHappened: errorMessage,
        whatCanUserDo: ['Please check if the file format is correct.'],
        extraData: e,
      });
    }
  }

  static async updateTextFile(filePath: string, placeholder: string, replacement: string): Promise<void> {
    if (!await fs.pathExists(filePath)) {
      return;
    }

    try {
      let content = await fs.readFile(filePath, DEFAULT_ENCODING);
      if (content.includes(placeholder)) {
        content = content.replace(new RegExp(placeholder, 'g'), replacement);
        await fs.writeFile(filePath, content, DEFAULT_ENCODING);
      }
    } catch (e: any) {
      throw new DescriptiveError({
        whatHappened: `Failed to update ${path.basename(filePath)}.`,
        whatCanUserDo: ['Please check if the file is readable and writable.'],
        extraData: e,
      });
    }
  }
}

/**
 * Helper class for validation operations.
 */
class ValidationHelper {
  static validateProjectName(projectName: string): string | true {
    if (!projectName.trim()) {
      return 'Project name cannot be empty.';
    }
    if (!VALID_APP_NAME_REG.test(projectName.trim())) {
      return 'Project name must start with a letter and can only contain letters, numbers, _.';
    }
    return true;
  }

  static validateOptions(options: CliOptions): void {
    if (options.version && options.template) {
      throw new DescriptiveError({
        whatHappened: 'Cannot specify both --version and --template.',
        whatCanUserDo: ['Please specify only one of --version or --template.'],
      });
    }
  }

  static async validateReactNativeVersion(destDir: string): Promise<string> {
    const packageJsonPath = path.join(destDir, PACKAGE_JSON);
    if (await fs.pathExists(packageJsonPath)) {
      const packageJson = JSON.parse(await fs.readFile(packageJsonPath, DEFAULT_ENCODING));
      const reactNativeVersion = packageJson.dependencies?.[REACT_NATIVE];
      if (!reactNativeVersion || !/^\^?0\.(72)|(77)\./.test(reactNativeVersion)) {
        throw new DescriptiveError({
          whatHappened: `Unsupported RN community template. Please ensure react-native version starts with "0.77.".`,
          whatCanUserDo: ['Please use a template with react-native version 0.77.x.'],
        });
      }
      return reactNativeVersion;
    }
    return "0.77";
  }
}

/**
 * Main entry point for the RNOH init command.
 * Initializes a new RNOH project with proper template and configuration.
 */
export async function rnohInit() {
  const program = new Command();
  program
    .command('init [projectName]')
    .description('Initialize a new RNOH project')
    .option('-v, --version <version>', 'Harmonized RN version')
    .option('-t, --template <template>', 'Template npm package')
    .action(async (projectName: string | undefined, options: CliOptions) => {
      try {
        await runInit(projectName, options);
      } catch (err) {
        handleError(err);
        process.exit(1);
      }
    });

  await program.parseAsync(process.argv);
}

function showWelcomeMessage(version: string) {
  const rnohLogoArray = [
    '                                                              ',
    '                 ____  _   ______  __  __                     ',
    '                / __ \\/ | / / __ \\/ / / /                   ',
    '               / /_/ /  |/ / / / / /_/ /                      ',
    '              / _, _/ /|  / /_/ / __  /                       ',
    '             /_/ |_/_/ |_/\\____/_/ /_/                       ',
    '                                                              ',
  ];
  const versionInfo = version && version !== 'undefined'
    ? `              Welcome to RNOH ${version}!`
    : '                  Welcome to RNOH!';
  logger.log(`${chalk.cyan(rnohLogoArray.join('\n'))}\n${chalk.cyanBright.bold(versionInfo)}\n`);
}

async function runInit(projectName: string | undefined, options: CliOptions) {
  projectName = await getValidatedProjectName(projectName);

  ValidationHelper.validateOptions(options);

  const packageName = `${DEFAULT_PACKAGE_PREFIX}${projectName}`;

  const destDir = path.resolve(process.cwd(), projectName);
  await checkDestinationDirectory(destDir, projectName);

  showWelcomeMessage(options.version as string);
  const tempDir = await createTempDir();

  try {
    await executeInitializationSteps(tempDir, destDir, projectName, packageName, options);
  } catch (error) {
    await cleanupOnError(destDir);
    throw error;
  } finally {
    await cleanupTempDir(tempDir);
  }
}

async function getValidatedProjectName(projectName: string | undefined): Promise<string> {
  if (!projectName) {
    const { name } = await inquirer.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Please input your project name:',
        validate: ValidationHelper.validateProjectName,
      },
    ]);
    return name.trim();
  }

  const validationResult = ValidationHelper.validateProjectName(projectName);
  if (validationResult !== true) {
    throw new DescriptiveError({
      whatHappened: 'Invalid project name.',
      whatCanUserDo: [validationResult],
    });
  }

  return projectName;
}

async function checkDestinationDirectory(destDir: string, projectName: string): Promise<void> {
  if (await fs.pathExists(destDir)) {
    const { overwrite } = await inquirer.prompt([
      {
        type: 'confirm',
        name: 'overwrite',
        message: `Directory '${projectName}' already exists. Do you want to overwrite it?`,
        default: false,
      },
    ]);

    if (!overwrite) {
      process.exit(0);
    }

    try {
      await fs.remove(destDir);
    } catch (err) {
      throw new DescriptiveError({
        whatHappened: `Failed to remove existing directory ${destDir}`,
        whatCanUserDo: ['Check permissions for the directory.', 'Ensure the directory is not in use.'],
      });
    }
  }
}

async function executeInitializationSteps(
  tempDir: string,
  destDir: string,
  projectName: string,
  packageName: string,
  options: CliOptions
): Promise<void> {
  const loader = getLoader();

  loader.start(`${chalk.bold('Downloading template...')}`);
  try {
    await npmInitAndInstall(tempDir, options.template, options.version);
    loader.succeed('Template downloaded successfully!');
  } catch (err) {
    loader.fail('Failed to download template');
    if (err instanceof DescriptiveError) {
      throw err;
    }
    throw new DescriptiveError({
      whatHappened: 'Template download failed',
      whatCanUserDo: ['Check your internet connection.', 'Ensure the template package exists.'],
    });
  }

  loader.start(`${chalk.bold('Copying template...')}`);
  const { templateConfig, templateRoot } = await getTemplateConfigFromPkg(tempDir);
  let isHarmonyTemplate = true;
  let rnohTemplateConfig: TemplateConfig | undefined = undefined;
  let rnohVersion: string | undefined = undefined;
  let rnohCliVersion: string | undefined = undefined;
  try {
    await copyAndReplaceTemplate(templateConfig, templateRoot, destDir);

    const rnVersion = await ValidationHelper.validateReactNativeVersion(destDir);

    if (!(await fs.pathExists(path.join(destDir, HARMONY_DIR)))) {
      isHarmonyTemplate = false;
      ({ rnohTemplateConfig, rnohVersion, rnohCliVersion } = await handleMissingHarmonyDir(destDir, rnVersion));
    }
    loader.succeed('Template copied successfully!');
  } catch (err) {
    loader.fail('Failed to copy template');
    if (err instanceof DescriptiveError) {
      throw err;
    }
    throw new DescriptiveError({
      whatHappened: 'Template copy failed',
      whatCanUserDo: ['Ensure you have write permissions for the destination directory.'],
    });
  }

  loader.start(`${chalk.bold('Processing template...')}`);
  try {
    const updateConfig: UpdateConfig = {
      destDir,
      templateRoot,
      templateConfig,
      projectName,
      packageName,
      isHarmonyTemplate,
      rnohVersion,
      rnohCliVersion,
      rnohTemplateConfig,
    };
    await processTemplate(updateConfig);
    loader.succeed('Template processed successfully!');
  } catch (err) {
    loader.fail('Failed to process template');
    if (err instanceof DescriptiveError) {
      throw err;
    }
    throw new DescriptiveError({
      whatHappened: 'Template processing failed',
      whatCanUserDo: ['Check if the template files are corrupted.'],
    });
  }

  loader.start(`${chalk.bold('Installing dependencies...')}`);
  try {
    await runCliCommand(NPM_EXE, ['install', '--legacy-peer-deps'], destDir);
    loader.succeed('Dependencies installed successfully!');
  } catch (err) {
    loader.fail('Failed to install dependencies');
    if (err instanceof DescriptiveError) {
      throw err;
    }
    throw new DescriptiveError({
      whatHappened: 'Dependency installation failed',
      whatCanUserDo: ['Check your internet connection.'],
    });
  }

  loader.start(`${chalk.bold('Initializing git repository...')}`);
  const isSuccess = await initGitRepo(destDir);
  if (isSuccess) {
    loader.succeed('Git repository initialized successfully!');
  } else {
    loader.fail('Failed to initialize git repository.');
  }
}

async function createTempDir(): Promise<string> {
  const tempDir = path.join(os.tmpdir(), `rnoh-init-${Date.now()}-${Math.random().toString(36).slice(2)}`);
  await fs.ensureDir(tempDir);
  return tempDir;
}

async function npmInitAndInstall(tempDir: string, customTemplate: string | undefined, version?: string): Promise<void> {
  await runCliCommand(NPM_EXE, ['init', '-y'], tempDir);
  const installTarget = customTemplate ? customTemplate : version ? `${DEFAULT_TEMPLATE_PKG}@${version}` : DEFAULT_TEMPLATE_PKG;
  await runCliCommand(NPM_EXE, ['install', installTarget], tempDir);
}

async function getTemplateConfigFromPkg(tempDir: string): Promise<{ templateConfig: TemplateConfig; templateRoot: string }> {
  const pkgJsonPath = path.join(tempDir, PACKAGE_JSON);
  const packageJson = JSON.parse(await fs.readFile(pkgJsonPath, DEFAULT_ENCODING));
  const dependencies = packageJson.dependencies;
  const templateName = Object.keys(dependencies)[0];
  const templateRoot = path.join(tempDir, NODE_MODULES, ...templateName.split('/'));
  if (!await fs.pathExists(templateRoot)) {
    throw new DescriptiveError({
      whatHappened: 'Cannot find installed template package.',
      whatCanUserDo: ['Check npm install output.'],
    });
  }

  const configPath = path.join(templateRoot, TEMPLATE_CONFIG_JS);

  if (!await fs.pathExists(configPath)) {
    throw new DescriptiveError({
      whatHappened: `${TEMPLATE_CONFIG_JS} not found in template package.`,
      whatCanUserDo: ['Check the template package.'],
    });
  }
  const templateConfig: TemplateConfig = require(configPath);

  validateTemplateConfig(templateConfig);

  return { templateConfig, templateRoot };
}

function validateTemplateConfig(templateConfig: TemplateConfig): void {
  if (!templateConfig.placeholderName || !templateConfig.templateDir) {
    throw new DescriptiveError({
      whatHappened: `${TEMPLATE_CONFIG_JS} missing required fields (placeholderName, templateDir).`,
      whatCanUserDo: [`Check ${TEMPLATE_CONFIG_JS} format.`],
    });
  }

  if (typeof templateConfig.placeholderName !== STRING_TYPE || typeof templateConfig.templateDir !== STRING_TYPE) {
    throw new DescriptiveError({
      whatHappened: `${TEMPLATE_CONFIG_JS} fields must be strings.`,
      whatCanUserDo: [`Check ${TEMPLATE_CONFIG_JS} format.`],
    });
  }
}

async function copyAndReplaceTemplate(templateConfig: TemplateConfig, templateRoot: string, destDir: string): Promise<void> {
  const srcDir = path.join(templateRoot, templateConfig.templateDir);

  if (!await fs.pathExists(srcDir)) {
    throw new DescriptiveError({
      whatHappened: 'Template directory not found.',
      whatCanUserDo: [`Check templateDir in ${TEMPLATE_CONFIG_JS}.`],
    });
  }

  await fs.copy(srcDir, destDir);
}

async function updateRNConfigFiles(updateConfig: UpdateConfig): Promise<void> {
  const updates = [
    FileOperationHelper.updateJsonFile(
      path.join(updateConfig.destDir, APP_JSON),
      (data) => ({ ...data, name: updateConfig.projectName, displayName: updateConfig.projectName }),
      `Failed to update ${APP_JSON}.`
    ),
    FileOperationHelper.updateJsonFile(
      path.join(updateConfig.destDir, PACKAGE_JSON),
      (data) => {
        if (!updateConfig.isHarmonyTemplate) {
          data.dependencies = { ...data.dependencies, [REACT_NATIVE_OH_REACT_NATIVE_HARMONY]: updateConfig.rnohVersion };
          data.devDependencies = { ...data.devDependencies, [REACT_NATIVE_OH_REACT_NATIVE_HARMONY_CLI]: updateConfig.rnohCliVersion };
        }
        return { ...data, name: updateConfig.packageName };
      },
      `Failed to update ${PACKAGE_JSON}.`
    ),
  ];

  await Promise.all(updates);
}

async function renameDotFiles(destDir: string): Promise<void> {
  const dotMap: Record<string, string> = {
    '_bundle': '.bundle',
    '_eslintrc.js': '.eslintrc.js',
    '_gitignore': '.gitignore',
    '_prettierrc.js': '.prettierrc.js',
    '_watchmanconfig': '.watchmanconfig',
    '_buckconfig': '.buckconfig',
    '_flowconfig': '.flowconfig',
    '_gitattributes': '.gitattributes',
    '_editorconfig': '.editorconfig',
    '_ruby-version': '.ruby-version',
    '_node-version': '.node-version',
    '_xcode.env': '.xcode.env',
  };

  const renameOperations = Object.entries(dotMap).map(async ([src, dest]) => {
    const srcPath = path.join(destDir, src);
    if (await fs.pathExists(srcPath)) {
      await fs.move(srcPath, path.join(destDir, dest), { overwrite: true });
    }
  });

  await Promise.all(renameOperations);
}

async function runPostInitScript(scriptPath: string, templateRoot: string, cwd: string, isHarmonyTemplate: boolean): Promise<void> {
  const absScriptPath = path.join(templateRoot, scriptPath);

  if (!await fs.pathExists(absScriptPath)) {
    throw new DescriptiveError({
      whatHappened: 'postInitScript not found.',
      whatCanUserDo: [`Check postInitScript path in ${TEMPLATE_CONFIG_JS}.`],
    });
  }

  if (!isHarmonyTemplate) {
    try {
      await runCliCommand(NODE_EXE, [absScriptPath], cwd);
    } catch (err) {
      logger.error(`Failed to execute postInitScript in RN community template, please execute manually.`);
    }
  } else {
    await runCliCommand(NODE_EXE, [absScriptPath], cwd);
  }
}

async function runCliCommand(cmd: string, args: string[], cwd: string, silent = true, stdOutFunc?: (data: any) => any): Promise<void> {
  const cli = new RealCliExecutor();
  try {
    await cli.run(cmd, {
      args,
      cwd: new AbsolutePath(cwd),
      shell: true,
      onStdout: silent ? undefined : stdOutFunc ? (data) => stdOutFunc(data) : (data) => logger.info(data),
      onStderr: silent ? undefined : (data) => logger.error(data),
    });
  } catch (err) {
    throw new DescriptiveError({
      whatHappened: `Failed to run command: ${cmd} ${args.join(' ')}`,
      extraData: err,
    });
  }
}

async function initGitRepo(destDir: string): Promise<boolean> {
  const gitDir = path.join(destDir, '.git');
  if (await fs.pathExists(gitDir)) {
    logger.warn('Git repository already exists, skipping git init.');
    return false;
  }

  try {
    await runCliCommand(GIT_EXE, ['init'], destDir);
    await runCliCommand(GIT_EXE, ['add', '.'], destDir);
    await runCliCommand(GIT_EXE, ['commit', '-m', `"Initial RNOH by ${REACT_NATIVE_OH_REACT_NATIVE_HARMONY_CLI}"`], destDir);
  } catch (error) {
    logger.warn('Failed to initialize git repository. You can initialize it manually later.');
    if (await fs.pathExists(gitDir)) {
      await fs.remove(gitDir);
    }
    return false;
  }
  return true;
}

function handleError(err: unknown): void {
  if (err instanceof DescriptiveError) {
    logger.error(chalk.red(err.getMessage()));
    for (const suggestion of err.getSuggestions()) {
      logger.info(chalk.yellow('Tip: ' + suggestion));
    }
    if (err.getDetails()) {
      logger.info(chalk.gray(err.getDetails()));
    }
  } else {
    const message = err instanceof Error ? err.message : String(err);
    logger.error(chalk.red('Unexpected error: ' + message));
  }
}

async function cleanupOnError(destDir: string): Promise<void> {
  if (await fs.pathExists(destDir)) {
    await fs.remove(destDir);
  }
}

async function cleanupTempDir(tempDir: string): Promise<void> {
  try {
    await fs.remove(tempDir);
  } catch (cleanupError) {
    logger.warn('Failed to clean up temporary directory:', tempDir);
  }
}

async function getRNOHVersion(rnVersion: string): Promise<string> {
  const versionParts = rnVersion.split('.');
  const majorMinorVersion = `${versionParts[0]}.${versionParts[1]}`;
  let versionInfos = "";
  await runCliCommand(NPM_EXE, ['view', DEFAULT_TEMPLATE_PKG, 'versions', '--json'], process.cwd(), false,
  (data) => {
    versionInfos += data.toString();
  }
  );
  const versions = JSON.parse(versionInfos);
  const matchingVersions = versions.filter((v: string) => v.startsWith(majorMinorVersion));
  if (matchingVersions.length === 0) {
    throw new DescriptiveError({
      whatHappened: `No matching versions found for ${DEFAULT_TEMPLATE_PKG} with major version ${majorMinorVersion}.`,
      whatCanUserDo: ['Please check the available versions of the template package.'],
    });
  }
  return matchingVersions.pop();
}

async function handleMissingHarmonyDir(destDir: string, rnVersion: string): Promise<{ rnohTemplateConfig: TemplateConfig, rnohVersion: string, rnohCliVersion: string }> {
  const lastestVersion = await getRNOHVersion(rnVersion);
  const defaultTempDir = await createTempDir();
  try {
    await npmInitAndInstall(defaultTempDir, `${DEFAULT_TEMPLATE_PKG}@${lastestVersion}`);
    const defaultTemplateConfig = await getTemplateConfigFromPkg(defaultTempDir);

    const sourceHarmonyDir = path.join(defaultTemplateConfig.templateRoot, defaultTemplateConfig.templateConfig.templateDir, HARMONY_DIR);
    const targetHarmonyDir = path.join(destDir, HARMONY_DIR);

    if (!await fs.pathExists(sourceHarmonyDir)) {
      throw new DescriptiveError({
        whatHappened: `Default template does not contain harmony directory at ${sourceHarmonyDir}`,
        whatCanUserDo: ['Please use a template that contains harmony directory.', 'Check if the default template package is correct.'],
      });
    }

    await fs.copy(sourceHarmonyDir, targetHarmonyDir);

    const sourceMetroConfigPath = path.join(defaultTemplateConfig.templateRoot, defaultTemplateConfig.templateConfig.templateDir, METRO_CONFIG_JS);
    const targetMetroConfigPath = path.join(destDir, METRO_CONFIG_JS);
    if (await fs.pathExists(sourceMetroConfigPath)) {
      await fs.copyFile(sourceMetroConfigPath, targetMetroConfigPath);
    } else {
      throw new DescriptiveError({
        whatHappened: `Default template does not contain ${METRO_CONFIG_JS} at ${sourceMetroConfigPath}`,
        whatCanUserDo: [`Please use a template that contains ${METRO_CONFIG_JS}.`, 'Check if the default template package is correct.'],
      });
    }

    const pkgJsonPath = path.join(defaultTemplateConfig.templateRoot, defaultTemplateConfig.templateConfig.templateDir, PACKAGE_JSON);
    const pkgContent = JSON.parse(await fs.readFile(pkgJsonPath, DEFAULT_ENCODING));
    const rnohVersion = pkgContent.dependencies?.[REACT_NATIVE_OH_REACT_NATIVE_HARMONY];
    const rnohCliVersion = pkgContent.devDependencies?.[REACT_NATIVE_OH_REACT_NATIVE_HARMONY_CLI];

    const androidDir = path.join(destDir, 'android');
    const iosDir = path.join(destDir, 'ios');

    if (await fs.pathExists(androidDir)) {
      await fs.remove(androidDir);
    }
    if (await fs.pathExists(iosDir)) {
      await fs.remove(iosDir);
    }
    return { rnohTemplateConfig: defaultTemplateConfig.templateConfig, rnohVersion, rnohCliVersion };
  } catch (error) {
    if (error instanceof DescriptiveError) {
      throw error;
    }
    throw new DescriptiveError({
      whatHappened: 'Failed to handle missing harmony directory from default template.',
      whatCanUserDo: [
        'Check your internet connection.',
        'Ensure you have write permissions for the destination directory.',
        'Verify that the default template package exists and is accessible.',
      ],
      extraData: error,
    });
  } finally {
    await cleanupTempDir(defaultTempDir);
  }
}

async function updateHarmonyFiles(updateConfig: UpdateConfig): Promise<void> {
  const harmonyDir = path.join(updateConfig.destDir, HARMONY_DIR);
  const appScopeDir = path.join(harmonyDir, 'AppScope');
  const entryDir = path.join(harmonyDir, 'entry');

  const updateOperations = [
    updateAppScopeStringJson(appScopeDir, updateConfig.projectName),
    updateEntryIndexEts(entryDir, updateConfig),
    updateAppScopeAppJson5(appScopeDir, updateConfig.packageName),
    updateEntryStringJson(entryDir, updateConfig.projectName),
  ];

  await Promise.all(updateOperations);
}

async function updateAppScopeStringJson(appScopeDir: string, projectName: string): Promise<void> {
  const appScopeStringJson = path.join(appScopeDir, 'resources', 'base', 'element', STRING_JSON);

  await FileOperationHelper.updateJsonFile(
    appScopeStringJson,
    (data) => {
      if (Array.isArray(data.string) && data.string.length > 0) {
        data.string[PROJECT_NAME_INDEX].value = projectName;
      }
      return data;
    },
    `Failed to update AppScope ${STRING_JSON}.`
  );
}

async function updateEntryIndexEts(entryDir: string, updateConfig: UpdateConfig): Promise<void> {
  const entryIndexEts = path.join(entryDir, 'src', 'main', 'ets', 'pages', 'Index.ets');
  let replacedText = updateConfig.templateConfig.placeholderName;
  if (!updateConfig.isHarmonyTemplate && updateConfig.rnohTemplateConfig) {
    replacedText = updateConfig.rnohTemplateConfig.placeholderName;
  }
  await FileOperationHelper.updateTextFile(entryIndexEts, replacedText, updateConfig.projectName);
}

async function updateAppScopeAppJson5(appScopeDir: string, packageName: string): Promise<void> {
  const appScopeAppJson5 = path.join(appScopeDir, APP_JSON5);

  await FileOperationHelper.updateJsonFile(
    appScopeAppJson5,
    (data) => ({
      ...data,
      app: { ...data.app, bundleName: packageName },
    }),
    `Failed to update AppScope ${APP_JSON5}.`
  );
}

async function updateEntryStringJson(entryDir: string, projectName: string): Promise<void> {
  const entryStringJson = path.join(entryDir, 'src', 'main', 'resources', 'base', 'element', STRING_JSON);

  await FileOperationHelper.updateJsonFile(
    entryStringJson,
    (data) => {
      if (Array.isArray(data.string)) {
        data.string.forEach((item: any) => {
          if (item.name === MODULE_DESC || item.name === ENTRY_ABILITY_DESC) {
            item.value = '';
          }
          if (item.name === ENTRY_ABILITY_LABEL) {
            item.value = projectName;
          }
        });
      }
      return data;
    },
    `Failed to update entry ${STRING_JSON}.`
  );
}

async function processTemplate(
  updateConfig: UpdateConfig
): Promise<void> {
  await Promise.all([
    updateRNConfigFiles(updateConfig),
    updateHarmonyFiles(updateConfig),
    renameDotFiles(updateConfig.destDir),
  ]);

  if (updateConfig.templateConfig.postInitScript) {
    await runPostInitScript(
      updateConfig.templateConfig.postInitScript,
      updateConfig.templateRoot,
      updateConfig.destDir,
      updateConfig.isHarmonyTemplate);
  }
}

rnohInit();