/**
 * Copyright (c) 2026 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 { execSync } = require('node:child_process');
const fs = require('node:fs');
const fse = require('fs-extra');
const { glob } = require('glob');
const pathUtils = require('node:path');

const HARMONY_PATH = '../tester/harmony';
const RNOH_PATH = `${HARMONY_PATH}/react_native_openharmony`;
const MAIN_PATH = `${RNOH_PATH}/src/main`;
const GLOG_HEADER_DIR = `${RNOH_PATH}/.cxx/header-gen`;

/**
 * @param {string} directory
 * @param {string} filename
 */
async function findFirstFile(directory, filename) {
  try {
    const files = await glob(`${directory}/**/${filename}`, { nodir: true });
    return files.length > 0 ? files[0] : null;
  } catch (err) {
    console.error(`Error searching for ${filename}: ${err}`);
    return null;
  }
}

/**
 * Filters files in a specified directory based on their extensions.
 * @param {string} dir
 * @param {string[]} exts e.g., ['h', 'hpp']
 * @param {string} target
 */
function filterFileByExt(dir, exts = [], target) {
  const files = fs.readdirSync(dir);
  for (let i = 0, len = files.length; i < len; i++) {
    const item = files[i];
    const stat = fs.lstatSync(`${dir}/${item}`);
    if (stat.isDirectory()) {
      filterFileByExt(`${dir}/${item}`, exts, `${target}/${item}`);
      continue;
    }
    exts = exts.map(ext => ext.trim().toLowerCase());
    const ext = item.split('.').pop()?.toLowerCase();
    if (ext && exts.includes(ext)) {
      const source = `${dir}/${item}`;
      const dest = `${target}/${item}`;
      try {
        fse.copySync(source, dest);
      } catch (err) {
        console.error(`[failed!]: copy ${source} -> ${dest}\n\n${err}`);
      }
    }
  }
}

function toCMakePath(path) {
  return path.replace(/\\/g, '/');
}

async function generateGlogHeaders() {
  const glogSourceDir = `${MAIN_PATH}/cpp/third-party/glog`;
  fse.removeSync(GLOG_HEADER_DIR);
  fse.ensureDirSync(GLOG_HEADER_DIR);

  const DEVECO_SDK_HOME = process.env.DEVECO_SDK_HOME;
  const basePath = pathUtils.join(DEVECO_SDK_HOME, '.');
  const ohosArch = process.env.OHOS_GLOG_CMAKE_ARCH || 'arm64-v8a';
  const nativeRoot = pathUtils.join(
    DEVECO_SDK_HOME,
    'default',
    'openharmony',
    'native'
  );
  const toolchainFile = pathUtils.join(
    nativeRoot,
    'build',
    'cmake',
    'ohos.toolchain.cmake'
  );

  const cmakePath = await findFirstFile(
    basePath,
    'cmake' + (process.platform === 'win32' ? '.exe' : '')
  );
  const ninjaPath = await findFirstFile(
    basePath,
    'ninja' + (process.platform === 'win32' ? '.exe' : '')
  );

  if (!fs.existsSync(toolchainFile)) {
    console.error(
      `Missing OHOS CMake toolchain (expected at ${toolchainFile}). Check DEVECO_SDK_HOME and OpenHarmony native SDK install.`
    );
    process.exit(1);
  }

  // Use OHOS toolchain so CMake does not treat SDK clang as a Windows host compiler
  // (that mis-detection fails at link time with 'program not executable').
  const cmakeCommand = `"${cmakePath}" -S ${toCMakePath(glogSourceDir)} -B ${toCMakePath(GLOG_HEADER_DIR)} \
    -G "Ninja" \
    -DCMAKE_MAKE_PROGRAM="${toCMakePath(ninjaPath)}" \
    -DCMAKE_SYSTEM_NAME=OHOS \
    -DCMAKE_SYSTEM_VERSION=1 \
    -DCMAKE_TOOLCHAIN_FILE="${toCMakePath(toolchainFile)}" \
    -DOHOS_ARCH=${ohosArch} \
    -DOHOS_STL=c++_shared \
    -DCMAKE_CXX_STANDARD=17 \
    -DCMAKE_CXX_STANDARD_REQUIRED=ON \
    -DWITH_GFLAGS=OFF \
    -DWITH_THREADS=OFF \
    -DBUILD_TESTING=OFF`;

  execSync(cmakeCommand, { stdio: 'inherit' });
}

function prepareBuildHar() {
  // [1] Replace build-profile.
  fse.copySync('./scripts/resources/build-profile_project.json5', `${HARMONY_PATH}/build-profile.json5`);
  fse.copySync('./scripts/resources/build-profile_module.json5', `${RNOH_PATH}/build-profile.json5`);
  // [2] Extract all header files.
  filterFileByExt(`${MAIN_PATH}/cpp`, ['h', 'hpp', 'ipp'], `${MAIN_PATH}/include`);
  // [3] Copy other files.
  fse.copySync(
    `${MAIN_PATH}/cpp/third-party/folly/CMake/folly-config.h.cmake`,
    `${MAIN_PATH}/include/third-party/folly/CMake/folly-config.h.cmake`
  );
  fse.copySync('./scripts/resources/react-native-harmony.cmake', `${MAIN_PATH}/include/react-native-harmony.cmake`);
  ['logging.h', 'raw_logging.h', 'stl_logging.h', 'vlog_is_on.h'].forEach(filePath => {
    try {
      fse.copySync(
        `${GLOG_HEADER_DIR}/glog/${filePath}`,
        `${MAIN_PATH}/include/third-party/glog/src/glog/${filePath}`
      );
    } catch (err) {
      console.error(
        `[failed!]: copy ${GLOG_HEADER_DIR}/glog/${filePath}\n\n${err}`
      );
    }
  });
  // [4] Copy RNOHAppNapiBridge.cpp
  fse.copySync(`${MAIN_PATH}/cpp/RNOHAppNapiBridge.cpp`, `${MAIN_PATH}/include/RNOHAppNapiBridge.cpp`);
}

/**
 * 复制符号文件
 * @param {string} symDirPath 符号文件所在目录路径
 * @param {string} outputDir 输出目录路径
 */
function copySymFiles(symDirPath, outputDir) {
  try {
    console.log(`Copying symbol files to: ${outputDir}`);

    if (!fs.existsSync(symDirPath)) {
      console.log(`Symbol directory not found: ${symDirPath}`);
      return;
    }

    const symFiles = fs.readdirSync(symDirPath).filter(file => file.endsWith('.sym'));

    if (symFiles.length === 0) {
      console.log('No .sym files found in symbol directory');
      return;
    }

    if (!fs.existsSync(outputDir)) {
      fs.mkdirSync(outputDir, { recursive: true });
    }

    console.log(`Found ${symFiles.length} .sym files`);

    symFiles.forEach(symFile => {
      const srcPath = pathUtils.join(symDirPath, symFile);
      const destPath = pathUtils.join(outputDir, symFile);
      fs.copyFileSync(srcPath, destPath);
      console.log(`Copied ${symFile}`);
    });
  } catch (err) {
    console.error(`Failed to copy symbol files: ${err}`);
  }
}

(async () => {
  const DEVECO_SDK_HOME = process.env.DEVECO_SDK_HOME;

  if (!DEVECO_SDK_HOME) {
    console.error('DEVECO_SDK_HOME is undefined');
    process.exit(1);
  }

  const basePath = pathUtils.join(DEVECO_SDK_HOME, '..');
  const nodePath = await findFirstFile(
    basePath,
    'node' + (process.platform === 'win32' ? '.exe' : '')
  );
  const hvigorwPath = await findFirstFile(basePath, 'hvigorw.js');

  try {
    // generate glog header files and copy them to include/third-party/glog/src/glog
    await generateGlogHeaders();
    prepareBuildHar();
    // build source har
    const buildSourceHarCommand = `"${nodePath}" "${hvigorwPath}" --mode module -p product=default -p module=react_native_openharmony@default -p buildMode=debug assembleHar --analyze=false --parallel --incremental --no-daemon`;
    execSync(buildSourceHarCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
    fs.copyFileSync(
      `${RNOH_PATH}/build/default/outputs/default/react_native_openharmony.har`,
      './react_native_openharmony.har'
    );
    copySymFiles(`${RNOH_PATH}/src/main/cpp/third-party/prebuilt/debug/arm64-v8a`, './symbols/debug');
    // clean build directory
    const cleanBuildCommand = `"${nodePath}" "${hvigorwPath}" clean --mode module -p module=react_native_openharmony`;
    execSync(cleanBuildCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
    // build release har
    const buildReleaseHarCommand = `"${nodePath}" "${hvigorwPath}" --mode module -p product=default -p module=react_native_openharmony@default -p buildMode=release -p debuggable=false assembleHar --analyze=false --parallel --incremental --no-daemon`;
    execSync(buildReleaseHarCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
    fs.copyFileSync(
      `${RNOH_PATH}/build/default/outputs/default/react_native_openharmony.har`,
      './react_native_openharmony_release.har'
    );
    copySymFiles(`${RNOH_PATH}/build/default/intermediates/cmake/default/obj/arm64-v8a`, './symbols/release');
    // copy hermes sym
    copySymFiles(`${RNOH_PATH}/src/main/cpp/third-party/prebuilt/release/arm64-v8a`, './symbols/release');

    // clean build directory
    execSync(cleanBuildCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
    // build bytecode har
    const buildByteCodeHarCommand = `"${nodePath}" "${hvigorwPath}" --mode module -p product=release2 -p module=react_native_openharmony@default -p buildMode=release -p debuggable=false assembleHar --analyze=false --parallel --incremental --no-daemon`;
    execSync(buildByteCodeHarCommand, { stdio: 'inherit', cwd: HARMONY_PATH });
    fs.copyFileSync(
      `${RNOH_PATH}/build/release2/outputs/default/react_native_openharmony.har`,
      './react_native_openharmony_release2.har'
    );
    copySymFiles(`${RNOH_PATH}/build/release2/intermediates/cmake/default/obj/arm64-v8a`, './symbols/release2');
    // copy hermes sym
    copySymFiles(`${RNOH_PATH}/src/main/cpp/third-party/prebuilt/release/arm64-v8a`, './symbols/release2');
    process.exit(0);
  } catch (error) {
    console.error('Command execution failed:', error);
    process.exit(1);
  }
})();