/*
 * Copyright (c) 2020 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const JSON5 = require('json5');

const {
  readFile,
  writeFileSync,
  resourcesRawfile,
  storedFileInfo
} = require('./lib/utils');

const {
  WORKERS_DIR,
  TEST_RUNNER_DIR_SET,
  TS2ABC,
  ES2ABC,
  FAIL
} = require('./lib/pre_define');

const {
  checkAotConfig
} = require('./lib/gen_aot');

const {
  configure,
  getLogger
} = require('log4js');

configure({
  appenders: { 'ETS': {type: 'stderr', layout: {type: 'messagePassThrough'}}},
  categories: {'default': {appenders: ['ETS'], level: 'info'}}
});
const logger = getLogger('ETS');

const staticPreviewPage = process.env.aceStaticPreview;
const aceCompileMode = process.env.aceCompileMode || 'page';
const abilityConfig = {
  abilityType: process.env.abilityType || 'page',
  abilityEntryFile: null,
  projectAbilityPath: [],
  testRunnerFile: []
};
const projectConfig = {};
const resources = {
  app: {},
  sys: {}
};
const systemModules = [];
const abilityPagesFullPath = [];
const globalModulePaths = [];
let sdkConfigs = [];
let defaultSdkConfigs = [];
const extendSdkConfigs = [];
let sdkConfigPrefix = 'ohos|system';
let ohosSystemModulePaths = [];

function initProjectConfig(projectConfig) {
  projectConfig.entryObj = {};
  projectConfig.cardObj = {};
  projectConfig.projectPath = projectConfig.projectPath || process.env.aceModuleRoot ||
    path.join(process.cwd(), 'sample');
  projectConfig.buildPath = projectConfig.buildPath || process.env.aceModuleBuild ||
    path.resolve(projectConfig.projectPath, 'build');
  projectConfig.aceModuleBuild = projectConfig.buildPath; // To be compatible with both webpack and rollup
  projectConfig.manifestFilePath = projectConfig.manifestFilePath || process.env.aceManifestPath ||
    path.join(projectConfig.projectPath, 'manifest.json');
  projectConfig.aceProfilePath = projectConfig.aceProfilePath || process.env.aceProfilePath;
  projectConfig.aceModuleJsonPath = projectConfig.aceModuleJsonPath || process.env.aceModuleJsonPath;
  projectConfig.aceSuperVisualPath = projectConfig.aceSuperVisualPath ||
    process.env.aceSuperVisualPath;
  projectConfig.hashProjectPath = projectConfig.hashProjectPath ||
    hashProjectPath(projectConfig.projectPath);
  projectConfig.aceBuildJson = projectConfig.aceBuildJson || process.env.aceBuildJson;
  projectConfig.cachePath = projectConfig.cachePath || process.env.cachePath ||
    path.resolve(__dirname, 'node_modules/.cache');
  projectConfig.aceSoPath = projectConfig.aceSoPath || process.env.aceSoPath;
  projectConfig.xtsMode = /ets_loader_ark$/.test(__dirname);
  projectConfig.localPropertiesPath = projectConfig.localPropertiesPath || process.env.localPropertiesPath;
  projectConfig.projectProfilePath = projectConfig.projectProfilePath || process.env.projectProfilePath;
  projectConfig.isPreview = projectConfig.isPreview || process.env.isPreview === 'true';
  projectConfig.compileMode = projectConfig.compileMode || 'jsbundle';
  projectConfig.runtimeOS = projectConfig.runtimeOS || process.env.runtimeOS || 'default';
  projectConfig.sdkInfo = projectConfig.sdkInfo || process.env.sdkInfo || 'default';
  projectConfig.compileHar = false;
  projectConfig.compileShared = false;
  projectConfig.splitCommon = false;
  projectConfig.checkEntry = projectConfig.checkEntry || process.env.checkEntry;
  projectConfig.obfuscateHarType = projectConfig.obfuscateHarType || process.env.obfuscate;
  projectConfig.packageDir = 'node_modules';
  projectConfig.packageJson = 'package.json';
  projectConfig.packageManagerType = 'npm';
  projectConfig.cardEntryObj = {};
  projectConfig.compilerTypes = [];
  projectConfig.isCrossplatform = projectConfig.isCrossplatform || false;
}

function loadEntryObj(projectConfig) {
  let manifest = {};
  initProjectConfig(projectConfig);
  loadBuildJson();
  if (process.env.aceManifestPath && aceCompileMode === 'page') {
    setEntryFile(projectConfig);
    setFaTestRunnerFile(projectConfig);
  }
  if (process.env.aceModuleJsonPath) {
    setAbilityPages(projectConfig);
    setStageTestRunnerFile(projectConfig);
  }

  if (staticPreviewPage) {
    projectConfig.entryObj['./' + staticPreviewPage] = projectConfig.projectPath + path.sep +
      staticPreviewPage + '.ets?entry';
  } else if (abilityConfig.abilityType === 'page') {
    if (fs.existsSync(projectConfig.manifestFilePath)) {
      const jsonString = fs.readFileSync(projectConfig.manifestFilePath).toString();
      manifest = JSON.parse(jsonString);
      if (manifest && manifest.minPlatformVersion) {
        process.env.minPlatformVersion = manifest.minPlatformVersion;
        partialUpdateController(manifest.minPlatformVersion);
      }
      projectConfig.pagesJsonFileName = 'config.json';
    } else if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) {
      process.env.compileMode = 'moduleJson';
      buildManifest(manifest, projectConfig.aceModuleJsonPath);
    } else {
      throw Error('\u001b[31m ERROR: the manifest file ' + projectConfig.manifestFilePath.replace(/\\/g, '/') +
        ' or module.json is lost or format is invalid. \u001b[39m').message;
    }
    if (!projectConfig.compileHar) {
      if (manifest.pages) {
        const pages = manifest.pages;
        pages.forEach((element) => {
          const sourcePath = element.replace(/^\.\/ets\//, '');
          const fileName = path.resolve(projectConfig.projectPath, sourcePath + '.ets');
          if (fs.existsSync(fileName)) {
            projectConfig.entryObj['./' + sourcePath] = fileName + '?entry';
          } else {
            throw Error(`\u001b[31m ERROR: page '${fileName.replace(/\\/g, '/')}' does not exist. \u001b[39m`)
              .message;
          }
        });
      } else {
        throw Error('\u001b[31m ERROR: missing pages attribute in ' +
          projectConfig.manifestFilePath.replace(/\\/g, '/') +
          '. \u001b[39m').message;
      }
    }
  }
}

function buildManifest(manifest, aceConfigPath) {
  try {
    const moduleConfigJson = JSON.parse(fs.readFileSync(aceConfigPath).toString());
    manifest.type = process.env.abilityType;
    if (moduleConfigJson && moduleConfigJson.app && moduleConfigJson.app.minAPIVersion) {
      if (moduleConfigJson.module && moduleConfigJson.module.metadata) {
        partialUpdateController(moduleConfigJson.app.minAPIVersion, moduleConfigJson.module.metadata);
        stageOptimization(moduleConfigJson.module.metadata);
      } else {
        partialUpdateController(moduleConfigJson.app.minAPIVersion);
      }
    }
    if (moduleConfigJson.module) {
      switch (moduleConfigJson.module.type) {
        case 'har':
          projectConfig.compileHar = true;
          getPackageJsonEntryPath();
          break;
        case 'shared':
          projectConfig.compileShared = true;
          getPackageJsonEntryPath();
          manifest.pages = getPages(moduleConfigJson);
          break;
        default:
          manifest.pages = getPages(moduleConfigJson);
          break;
      }
    } else {
      throw Error('\u001b[31m' +
        'BUIDERROR: the config.json file miss key word module || module[abilities].' +
        '\u001b[39m').message;
    }
  } catch (e) {
    if (/BUIDERROR/.test(e)) {
      throw Error(e.replace('BUIDERROR', 'ERROR')).message;
    } else {
      throw Error('\x1B[31m' + 'ERROR: the module.json file is lost or format is invalid.' +
        '\x1B[39m').message;
    }
  }
}

function getPackageJsonEntryPath() {
  const rootPackageJsonPath = path.resolve(projectConfig.projectPath, '../../../' + projectConfig.packageJson);
  if (fs.existsSync(rootPackageJsonPath)) {
    let rootPackageJsonContent;
    try {
      rootPackageJsonContent = (projectConfig.packageManagerType === 'npm' ?
        JSON : JSON5).parse(fs.readFileSync(rootPackageJsonPath, 'utf-8'));
    } catch (e) {
      throw Error('\u001b[31m' + 'BUIDERROR: ' + rootPackageJsonPath + ' format is invalid.' + '\u001b[39m').message;
    }
    if (rootPackageJsonContent) {
      if (rootPackageJsonContent.module) {
        getEntryPath(rootPackageJsonContent.module, rootPackageJsonPath);
      } else if (rootPackageJsonContent.main) {
        getEntryPath(rootPackageJsonContent.main, rootPackageJsonPath);
      } else {
        getEntryPath('', rootPackageJsonPath);
      }
    } else if (projectConfig.compileHar) {
      throw Error('\u001b[31m' + 'BUIDERROR: lack message in ' + projectConfig.packageJson + '.' +
        '\u001b[39m').message;
    }
  }
}

function supportSuffix(mainEntryPath) {
  if (fs.existsSync(path.join(mainEntryPath, 'index.ets'))) {
    mainEntryPath = path.join(mainEntryPath, 'index.ets');
  } else if (fs.existsSync(path.join(mainEntryPath, 'index.ts'))) {
    mainEntryPath = path.join(mainEntryPath, 'index.ts');
  } else if (fs.existsSync(path.join(mainEntryPath, 'index.js'))) {
    mainEntryPath = path.join(mainEntryPath, 'index.js');
  } else if (projectConfig.compileHar) {
    throw Error('\u001b[31m' + 'BUIDERROR: not find entry file in ' + projectConfig.packageJson +
      '.' + '\u001b[39m').message;
  }
  return mainEntryPath;
}

function supportExtName(mainEntryPath) {
  if (path.extname(mainEntryPath) === '') {
    if (fs.existsSync(mainEntryPath + '.ets')) {
      mainEntryPath = mainEntryPath + '.ets';
    } else if (fs.existsSync(mainEntryPath + '.ts')) {
      mainEntryPath = mainEntryPath + '.ts';
    } else if (fs.existsSync(mainEntryPath + '.js')) {
      mainEntryPath = mainEntryPath + '.js';
    }
  }
  return mainEntryPath;
}

function getEntryPath(entryPath, rootPackageJsonPath) {
  let mainEntryPath = path.resolve(rootPackageJsonPath, '../', entryPath);
  if (fs.existsSync(mainEntryPath) && fs.statSync(mainEntryPath).isDirectory()) {
    mainEntryPath = supportSuffix(mainEntryPath);
  } else {
    mainEntryPath = supportExtName(mainEntryPath);
  }
  if (fs.existsSync(mainEntryPath) && fs.statSync(mainEntryPath).isFile()) {
    const entryKey = path.relative(projectConfig.projectPath, mainEntryPath);
    projectConfig.entryObj[entryKey] = mainEntryPath;
    abilityPagesFullPath.push(path.resolve(mainEntryPath).toLowerCase());
  } else if (projectConfig.compileHar) {
    throw Error('\u001b[31m' + `BUIDERROR: not find entry file in ${rootPackageJsonPath}.` + '\u001b[39m').message;
  }
}

function stageOptimization(metadata) {
  if (Array.isArray(metadata) && metadata.length) {
    metadata.some(item => {
      if (item.name && item.name === 'USE_COMMON_CHUNK' &&
        item.value && item.value === 'true') {
        projectConfig.splitCommon = true;
        return true;
      }
    });
  }
}

function getPages(configJson) {
  const pages = [];
  let pagesJsonFileName = '';
  // pages is not necessary in stage
  if (process.env.compileMode === 'moduleJson' && configJson.module && configJson.module.pages) {
    pagesJsonFileName = `${configJson.module.pages.replace(/\$profile\:/, '')}.json`;
  } else {
    return pages;
  }
  const modulePagePath = path.resolve(projectConfig.aceProfilePath, pagesJsonFileName);
  if (fs.existsSync(modulePagePath)) {
    try {
      const pagesConfig = JSON.parse(fs.readFileSync(modulePagePath, 'utf-8'));
      if (pagesConfig && pagesConfig.src) {
        projectConfig.pagesJsonFileName = pagesJsonFileName;
        return pagesConfig.src;
      }
    } catch (e) {
      throw Error("\x1B[31m" + `BUIDERROR: the ${modulePagePath} file format is invalid.` +
        "\x1B[39m").message;
    }
  }
  return pages;
}

function setEntryFile(projectConfig) {
  const entryFileName = abilityConfig.abilityType === 'page' ? 'app' : abilityConfig.abilityType;
  const extendFile = entryFileName === 'app' ? '.ets' : '.ts';
  const entryFileRealPath = entryFileName + extendFile;
  const entryFilePath = path.resolve(projectConfig.projectPath, entryFileRealPath);
  abilityConfig.abilityEntryFile = entryFilePath;
  if (!fs.existsSync(entryFilePath) && aceCompileMode === 'page') {
    throw Error(`\u001b[31m ERROR: missing ${entryFilePath.replace(/\\/g, '/')}. \u001b[39m`).message;
  }
  projectConfig.entryObj[`./${entryFileName}`] = entryFilePath + '?entry';
}

function setAbilityPages(projectConfig) {
  let abilityPages = [];
  if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) {
    const moduleJson = JSON.parse(fs.readFileSync(projectConfig.aceModuleJsonPath).toString());
    abilityPages = readAbilityEntrance(moduleJson);
    setAbilityFile(projectConfig, abilityPages);
    setBundleModuleInfo(projectConfig, moduleJson);
  }
}

function setTestRunnerFile(projectConfig, isStageBased) {
  const index = projectConfig.projectPath.split(path.sep).join('/').lastIndexOf('\/');
  TEST_RUNNER_DIR_SET.forEach((dir) => {
    const projectPath = isStageBased ? projectConfig.projectPath : projectConfig.projectPath.substring(0, index + 1);
    const testRunnerPath = path.resolve(projectPath, dir);
    if (fs.existsSync(testRunnerPath)) {
      const testRunnerFiles = [];
      readFile(testRunnerPath, testRunnerFiles);
      testRunnerFiles.forEach((item) => {
        if (/\.(ts|js|ets)$/.test(item)) {
          if (/\.ets$/.test(item)) {
            abilityPagesFullPath.push(path.resolve(item).toLowerCase());
          }
          const relativePath = path.relative(testRunnerPath, item).replace(/\.(ts|js|ets)$/, '');
          if (isStageBased) {
            projectConfig.entryObj[`./${dir}/${relativePath}`] = item;
          } else {
            projectConfig.entryObj[`../${dir}/${relativePath}`] = item;
          }
          abilityConfig.testRunnerFile.push(item);
        }
      });
    }
  });
}

function setFaTestRunnerFile(projectConfig) {
  setTestRunnerFile(projectConfig, false);
}

function setStageTestRunnerFile(projectConfig) {
  setTestRunnerFile(projectConfig, true);
}

function setBundleModuleInfo(projectConfig, moduleJson) {
  if (moduleJson.module) {
    projectConfig.moduleName = moduleJson.module.name;
  }
  if (moduleJson.app) {
    projectConfig.bundleName = moduleJson.app.bundleName;
  }
}

function setAbilityFile(projectConfig, abilityPages) {
  abilityPages.forEach(abilityPath => {
    const projectAbilityPath = path.resolve(projectConfig.projectPath, '../', abilityPath);
    if (path.isAbsolute(abilityPath)) {
      abilityPath = '.' + abilityPath.slice(projectConfig.projectPath.length);
    }
    const entryPageKey = abilityPath.replace(/^\.\/ets\//, './').replace(/\.ts$/, '').replace(/\.ets$/, '');
    if (fs.existsSync(projectAbilityPath)) {
      abilityConfig.projectAbilityPath.push(projectAbilityPath);
      projectConfig.entryObj[entryPageKey] = projectAbilityPath + '?entry';
    } else {
      throw Error(
        `\u001b[31m ERROR: srcEntry file '${projectAbilityPath.replace(/\\/g, '/')}' does not exist. \u001b[39m`
      ).message;
    }
  });
}

function readAbilityEntrance(moduleJson) {
  const abilityPages = [];
  if (moduleJson.module) {
    const moduleSrcEntrance = moduleJson.module.srcEntrance;
    const moduleSrcEntry = moduleJson.module.srcEntry;
    if (moduleSrcEntry) {
      abilityPages.push(moduleSrcEntry);
      abilityPagesFullPath.push(getAbilityFullPath(projectConfig.projectPath, moduleSrcEntry));
    } else if (moduleSrcEntrance) {
      abilityPages.push(moduleSrcEntrance);
      abilityPagesFullPath.push(getAbilityFullPath(projectConfig.projectPath, moduleSrcEntrance));
    }
    if (moduleJson.module.abilities && moduleJson.module.abilities.length > 0) {
      setEntrance(moduleJson.module.abilities, abilityPages);
    }
    if (moduleJson.module.extensionAbilities && moduleJson.module.extensionAbilities.length > 0) {
      setEntrance(moduleJson.module.extensionAbilities, abilityPages);
      setCardPages(moduleJson.module.extensionAbilities);
    }
  }
  return abilityPages;
}

function setEntrance(abilityConfig, abilityPages) {
  if (abilityConfig && abilityConfig.length > 0) {
    abilityConfig.forEach(ability => {
      if (ability.srcEntry) {
        abilityPages.push(ability.srcEntry)
        abilityPagesFullPath.push(getAbilityFullPath(projectConfig.projectPath, ability.srcEntry))
      } else if (ability.srcEntrance) {
        abilityPages.push(ability.srcEntrance);
        abilityPagesFullPath.push(getAbilityFullPath(projectConfig.projectPath, ability.srcEntrance));
      }
    });
  }
}

function setCardPages(extensionAbilities) {
  if (extensionAbilities && extensionAbilities.length > 0) {
    extensionAbilities.forEach(extensionAbility => {
      if (extensionAbility.metadata) {
        extensionAbility.metadata.forEach(metadata => {
          if (metadata.resource) {
            readCardResource(metadata.resource);
          }
        });
      }
    });
  }
}

function readCardResource(resource) {
  const cardJsonFileName = `${resource.replace(/\$profile\:/, '')}.json`;
  const modulePagePath = path.resolve(projectConfig.aceProfilePath, cardJsonFileName);
  if (fs.existsSync(modulePagePath)) {
    const cardConfig = JSON.parse(fs.readFileSync(modulePagePath, 'utf-8'));
    if (cardConfig.forms) {
      cardConfig.forms.forEach(form => {
        readCardForm(form);
      });
    }
  }
}

function readCardForm(form) {
  if ((form.type && form.type === 'eTS') ||
    (form.uiSyntax && form.uiSyntax === 'arkts')) {
    const sourcePath = form.src.replace(/\.ets$/, '');
    const cardPath = path.resolve(projectConfig.projectPath, '..', sourcePath + '.ets');
    if (cardPath && fs.existsSync(cardPath)) {
      projectConfig.entryObj['../' + sourcePath] = cardPath + '?entry';
      projectConfig.cardEntryObj['../' + sourcePath] = cardPath;
      projectConfig.cardObj[cardPath] = sourcePath.replace(/^\.\//, '');
    }
  }
}

function getAbilityFullPath(projectPath, abilityPath) {
  const finalPath = path.resolve(path.resolve(projectPath, '../'), abilityPath);
  if (fs.existsSync(finalPath)) {
    return finalPath.toLowerCase();
  } else {
    return path.resolve(abilityPath).toLowerCase();
  }
}

function loadWorker(projectConfig, workerFileEntry) {
  if (workerFileEntry) {
    projectConfig.entryObj = Object.assign(projectConfig.entryObj, workerFileEntry);
  } else {
    const workerPath = path.resolve(projectConfig.projectPath, WORKERS_DIR);
    if (fs.existsSync(workerPath)) {
      const workerFiles = [];
      readFile(workerPath, workerFiles);
      workerFiles.forEach((item) => {
        if (/\.(ts|js|ets)$/.test(item)) {
          const relativePath = path.relative(workerPath, item)
            .replace(/\.(ts|js|ets)$/, '').replace(/\\/g, '/');
          projectConfig.entryObj[`./${WORKERS_DIR}/` + relativePath] = item;
          abilityPagesFullPath.push(path.resolve(item).toLowerCase());
        }
      });
    }
  }
}

let aceBuildJson = {};
function loadBuildJson() {
  if (projectConfig.aceBuildJson && fs.existsSync(projectConfig.aceBuildJson)) {
    aceBuildJson = JSON.parse(fs.readFileSync(projectConfig.aceBuildJson).toString());
  }
  if (aceBuildJson.packageManagerType === 'ohpm') {
    projectConfig.packageManagerType = 'ohpm';
    projectConfig.packageDir = 'oh_modules';
    projectConfig.packageJson = 'oh-package.json5';
  }
}

function initBuildInfo() {
  projectConfig.projectRootPath = aceBuildJson.projectRootPath;
  if (projectConfig.compileHar && aceBuildJson.moduleName &&
    aceBuildJson.modulePathMap[aceBuildJson.moduleName]) {
    projectConfig.moduleRootPath = aceBuildJson.modulePathMap[aceBuildJson.moduleName];
  }
}

function readWorkerFile() {
  const workerFileEntry = {};
  if (aceBuildJson.workers) {
    aceBuildJson.workers.forEach(worker => {
      if (!/\.(ts|js|ets)$/.test(worker)) {
        throw Error(
          '\u001b[31mArkTS:ERROR: File: ' + worker + '.' + '\n' +
          "  The worker file can only be an '.ets', '.ts', or '.js' file.\u001b[39m"
        ).message;
      }
      const relativePath = path.relative(projectConfig.projectPath, worker);
      if (filterWorker(relativePath)) {
        const workerKey = relativePath.replace(/\.(ts|js)$/, '').replace(/\\/g, '/');
        if (workerFileEntry[workerKey]) {
          throw Error(
            '\u001b[31m ERROR: The worker file cannot use the same file name: \n' +
            workerFileEntry[workerKey] + '\n' + worker + '\u001b[39m'
          ).message;
        } else {
          workerFileEntry[workerKey] = worker;
          abilityPagesFullPath.push(path.resolve(workerFileEntry[workerKey]).toLowerCase());
        }
      }
    });
    return workerFileEntry;
  }
  return null;
}

function readPatchConfig() {
  if (aceBuildJson.patchConfig) {
    projectConfig.hotReload = process.env.watchMode === 'true' && !projectConfig.isPreview;
    projectConfig.patchAbcPath = aceBuildJson.patchConfig.patchAbcPath;
    projectConfig.changedFileList = aceBuildJson.patchConfig.changedFileList ?
      aceBuildJson.patchConfig.changedFileList : path.join(projectConfig.cachePath, 'changedFileList.json');
    if (projectConfig.hotReload) {
      writeFileSync(projectConfig.changedFileList, JSON.stringify({
        modifiedFiles: [],
        removedFiles: []
      }));
    }
  }
}

function filterWorker(workerPath) {
  return /\.(ts|js|ets)$/.test(workerPath);
}

;(function initSystemResource() {
  const sysResourcePath = path.resolve(__dirname, './sysResource.js');
  if (fs.existsSync(sysResourcePath)) {
    resources.sys = require(sysResourcePath).sys;
  }
})();

;(function readSystemModules() {
  const systemModulesPath = path.resolve(__dirname, '../../api');
  if (fs.existsSync(systemModulesPath)) {
    globalModulePaths.push(systemModulesPath);
    const modulePaths = [];
    readFile(systemModulesPath, modulePaths);
    systemModules.push(...fs.readdirSync(systemModulesPath));
    ohosSystemModulePaths.push(...modulePaths);
    defaultSdkConfigs = [
      {
        'apiPath': systemModulesPath,
        'prefix': '@ohos'
      }, {
        'apiPath': systemModulesPath,
        'prefix': '@system'
      }
    ];
  }
  const externalApiPathStr = process.env.externalApiPaths || '';
  const externalApiPaths = externalApiPathStr.split(path.delimiter);
  externalApiPaths.forEach(sdkPath => {
    const sdkConfigPath = path.resolve(sdkPath, 'sdkConfig.json');
    if (fs.existsSync(sdkConfigPath)) {
      const sdkConfig = JSON.parse(fs.readFileSync(sdkConfigPath, "utf-8"));
      sdkConfig.apiPath = path.resolve(sdkPath, sdkConfig.apiPath);
      if (fs.existsSync(sdkConfig.apiPath)) {
        globalModulePaths.push(sdkConfig.apiPath);
        systemModules.push(...fs.readdirSync(sdkConfig.apiPath));
        sdkConfigPrefix += `|${sdkConfig.prefix.replace(/^@/, '')}`;
        extendSdkConfigs.push(sdkConfig);
      }
    }
  });
  sdkConfigs = [...defaultSdkConfigs, ...extendSdkConfigs];
})()

function readAppResource(filePath) {
  if (fs.existsSync(filePath)) {
    const appResource = fs.readFileSync(filePath, 'utf-8');
    const resourceArr = appResource.split(/\n/);
    const resourceMap = new Map();
    processResourceArr(resourceArr, resourceMap, filePath);
    for (let [key, value] of resourceMap) {
      resources.app[key] = value;
    }
  }
  if (process.env.rawFileResource && process.env.compileMode === 'moduleJson') {
    resourcesRawfile(process.env.rawFileResource, storedFileInfo.resourcesArr);
  }
}

function processResourceArr(resourceArr, resourceMap, filePath) {
  for (let i = 0; i < resourceArr.length; i++) {
    if (!resourceArr[i].length) {
      continue;
    }
    const resourceData = resourceArr[i].split(/\s/);
    if (resourceData.length === 3 && !isNaN(Number(resourceData[2]))) {
      if (resourceMap.get(resourceData[0])) {
        const resourceKeys = resourceMap.get(resourceData[0]);
        if (!resourceKeys[resourceData[1]] || resourceKeys[resourceData[1]] !== Number(resourceData[2])) {
          resourceKeys[resourceData[1]] = Number(resourceData[2]);
        }
      } else {
        let obj = {};
        obj[resourceData[1]] = Number(resourceData[2]);
        resourceMap.set(resourceData[0], obj);
      }
      if (process.env.compileTool === 'rollup' && process.env.compileMode === 'moduleJson') {
        storedFileInfo.updateResourceList(resourceData[0] + '_' + resourceData[1]);
      }
    } else {
      logger.warn(`\u001b[31m ArkTS:WARN The format of file '${filePath}' is incorrect. \u001b[39m`);
      break;
    }
  }
}

function hashProjectPath(projectPath) {
  const hash = crypto.createHash('sha256');
  hash.update(projectPath.toString());
  process.env.hashProjectPath = "_" + hash.digest('hex');
  return process.env.hashProjectPath;
}

function loadModuleInfo(projectConfig, envArgs) {
  if (projectConfig.aceBuildJson && fs.existsSync(projectConfig.aceBuildJson)) {
    const buildJsonInfo = JSON.parse(fs.readFileSync(projectConfig.aceBuildJson).toString());
    if (buildJsonInfo.compileMode) {
      projectConfig.compileMode = buildJsonInfo.compileMode;
    }
    projectConfig.projectRootPath = buildJsonInfo.projectRootPath;
    projectConfig.modulePathMap = buildJsonInfo.modulePathMap;
    projectConfig.isOhosTest = buildJsonInfo.isOhosTest;
    let faultHandler = function (error) {
      // rollup's error will be handled in fast build
      if (process.env.compileTool === 'rollup') {
        return;
      }
      logger.error(error);
      process.exit(FAIL);
    }
    projectConfig.es2abcCompileTsInAotMode = true;
    projectConfig.es2abcCompileTsInNonAotMode = false;
    const compileMode = process.env.compileTool === 'rollup' ? projectConfig.compileMode : buildJsonInfo.compileMode;
    if (checkAotConfig(compileMode, buildJsonInfo, faultHandler)) {
      projectConfig.processTs = true;
      projectConfig.pandaMode = TS2ABC;
      projectConfig.anBuildOutPut = buildJsonInfo.anBuildOutPut;
      projectConfig.anBuildMode = buildJsonInfo.anBuildMode;
      projectConfig.apPath = buildJsonInfo.apPath;
      if (projectConfig.es2abcCompileTsInAotMode) {
        projectConfig.pandaMode = ES2ABC;
      }
    } else {
      projectConfig.processTs = false;
      projectConfig.pandaMode = buildJsonInfo.pandaMode;
      if (projectConfig.es2abcCompileTsInNonAotMode) {
        projectConfig.pandaMode = ES2ABC;
        projectConfig.processTs = true;
      }
    }
    if (envArgs !== undefined) {
      projectConfig.buildArkMode = envArgs.buildMode;
    }
    if (compileMode === 'esmodule') {
      projectConfig.nodeModulesPath = buildJsonInfo.nodeModulesPath;
      projectConfig.harNameOhmMap = buildJsonInfo.harNameOhmMap;
    }
    if (projectConfig.compileHar && buildJsonInfo.moduleName &&
      buildJsonInfo.modulePathMap[buildJsonInfo.moduleName]) {
      projectConfig.moduleRootPath = buildJsonInfo.modulePathMap[buildJsonInfo.moduleName];
    }
  }
}

function checkAppResourcePath(appResourcePath, config) {
  if (appResourcePath) {
    readAppResource(appResourcePath);
    if (fs.existsSync(appResourcePath) && config.cache) {
      config.cache.buildDependencies.config.push(appResourcePath);
    }
    if (!projectConfig.xtsMode) {
      const appResourcePathSavePath = path.resolve(projectConfig.cachePath, 'resource_path.txt');
      saveAppResourcePath(appResourcePath, appResourcePathSavePath);
      if (fs.existsSync(appResourcePathSavePath) && config.cache) {
        config.cache.buildDependencies.config.push(appResourcePathSavePath);
      }
    }
  }
}

function saveAppResourcePath(appResourcePath, appResourcePathSavePath) {
  let isSave = false;
  if (fs.existsSync(appResourcePathSavePath)) {
    const saveContent = fs.readFileSync(appResourcePathSavePath);
    if (appResourcePath !== saveContent) {
      isSave = true;
    }
  } else {
    isSave = true;
  }
  if (isSave) {
    fs.writeFileSync(appResourcePathSavePath, appResourcePath);
  }
}

function addSDKBuildDependencies(config) {
  if (projectConfig.localPropertiesPath &&
    fs.existsSync(projectConfig.localPropertiesPath) && config.cache) {
    config.cache.buildDependencies.config.push(projectConfig.localPropertiesPath)
  }
  if (projectConfig.projectProfilePath &&
    fs.existsSync(projectConfig.projectProfilePath) && config.cache) {
    config.cache.buildDependencies.config.push(projectConfig.projectProfilePath)
  }
}

function getCleanConfig(workerFile) {
  const cleanPath = [];
  if (projectConfig.compileMode === 'esmodule') {
    return cleanPath;
  }
  cleanPath.push(projectConfig.buildPath);
  if (workerFile) {
    const workerFilesPath = Object.keys(workerFile);
    for (const workerFilePath of workerFilesPath) {
      cleanPath.push(path.join(projectConfig.buildPath, workerFilePath, '..'));
    }
  }
  return cleanPath;
}

function isPartialUpdate(metadata) {
  if (!Array.isArray(metadata) || !metadata.length) {
    return;
  }
  metadata.some(item => {
    if (item.name && item.value) {
      if (item.name === 'ArkTSPartialUpdate' && item.value === 'false') {
        partialUpdateConfig.partialUpdateMode = false;
        if (projectConfig.aceModuleJsonPath) {
          logger.warn('\u001b[33m ArkTS:WARN File: ' + projectConfig.aceModuleJsonPath + '.' + '\n' +
          " The 'ArkTSPartialUpdate' field will no longer be supported in the future. \u001b[39m");
        }
      }
      if (item.name === 'ArkTSBuilderCheck' && item.value === 'false') {
        partialUpdateConfig.builderCheck = false;
      }
      if (item.name === 'ArkTSCheck' && item.value === 'SkipArkTSCheck') {
        partialUpdateConfig.executeArkTSLinter = false;
      }
      if (item.name === 'ArkTSCheckMode' && item.value === 'DoArkTSCheckInCompatibleMode') {
        partialUpdateConfig.standardArkTSLinter = false;
      }
      if (item.name === 'SkipTscOhModuleCheck' && item.value === 'true') {
        partialUpdateConfig.skipTscOhModuleCheck = true;
      }
    }
    return !partialUpdateConfig.partialUpdateMode && !partialUpdateConfig.builderCheck &&
      !partialUpdateConfig.executeArkTSLinter && !partialUpdateConfig.standardArkTSLinter &&
      partialUpdateConfig.skipTscOhModuleCheck;
  });
}

function applicationConfig() {
  const localProperties = path.resolve(aceBuildJson.projectRootPath, 'local.properties');
  if (fs.existsSync(localProperties)) {
    try {
      const localPropertiesFile = fs.readFileSync(localProperties, {encoding: 'utf-8'}).split(/\r?\n/);
      localPropertiesFile.some((item) => {
        const builderCheckValue = item.replace(/\s+|;/g, '');
        if (builderCheckValue === 'ArkTSConfig.ArkTSBuilderCheck=false') {
          partialUpdateConfig.builderCheck = false;
          return true;
        }
      });
    } catch (err) {
    }
  }
}

function partialUpdateController(minAPIVersion, metadata = null) {
  projectConfig.minAPIVersion = minAPIVersion;
  if (minAPIVersion >= 9) {
    partialUpdateConfig.partialUpdateMode = true;
  }
  if (minAPIVersion < 10) {
    partialUpdateConfig.optimizeComponent = false;
  }
  if (metadata) {
    isPartialUpdate(metadata);
  }
  if (aceBuildJson.projectRootPath) {
    applicationConfig();
  }
}

const globalProgram = {
  program: null,
  watchProgram: null,
  checker: null,
};

const partialUpdateConfig = {
  partialUpdateMode: false,
  builderCheck: true,
  executeArkTSLinter: true,
  standardArkTSLinter: true,
  optimizeComponent: true,
  skipTscOhModuleCheck: false,
};

exports.globalProgram = globalProgram;
exports.projectConfig = projectConfig;
exports.loadEntryObj = loadEntryObj;
exports.readAppResource = readAppResource;
exports.resources = resources;
exports.loadWorker = loadWorker;
exports.abilityConfig = abilityConfig;
exports.readWorkerFile = readWorkerFile;
exports.abilityPagesFullPath = abilityPagesFullPath;
exports.loadModuleInfo = loadModuleInfo;
exports.systemModules = systemModules;
exports.checkAppResourcePath = checkAppResourcePath;
exports.addSDKBuildDependencies = addSDKBuildDependencies;
exports.partialUpdateConfig = partialUpdateConfig;
exports.readPatchConfig = readPatchConfig;
exports.initBuildInfo = initBuildInfo;
exports.getCleanConfig = getCleanConfig;
exports.globalModulePaths = globalModulePaths;
exports.defaultSdkConfigs = defaultSdkConfigs;
exports.extendSdkConfigs = extendSdkConfigs;
exports.sdkConfigs = sdkConfigs;
exports.sdkConfigPrefix = sdkConfigPrefix;
exports.ohosSystemModulePaths = ohosSystemModulePaths;