#!/usr/bin/env node

/**
 * Copyright (c) 2026 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.
 */

'use strict';

const path = require('path');
const fs = require('fs');
const os = require('os');
const { spawnSync } = require('child_process');

const SCRIPT_DIR = __dirname;
const PACKAGE_ROOT = path.join(SCRIPT_DIR, '..');
const DEST_DIR = path.join(PACKAGE_ROOT, 'dest');

// Determine paths: if dest exists and contains assembled artifacts, use them
const useDest = fs.existsSync(DEST_DIR) && !process.env.USE_SOURCE;

function resolvePath(relativeFromRoot, relativeFromDest) {
  if (useDest && relativeFromDest) {
    const destPath = path.join(DEST_DIR, relativeFromDest);
    if (fs.existsSync(destPath)) {
      return destPath;
    }
    throw new Error(`Dest artifact not found: ${destPath}. Run 'npm run assemble' to build artifacts.`);
  }
  return path.join(PACKAGE_ROOT, relativeFromRoot);
}

// Path to es2panda.js
const ES2PANDA_PATH = resolvePath(
  '../../libarkts/lib/es2panda.js',
  'libarkts/lib/es2panda.js'
);

// Path to libarkts.js (for plugins)
const LIBARKTS_PATH = resolvePath(
  '../../libarkts/lib/libarkts.js',
  'libarkts/lib/libarkts.js'
);

// Panda SDK path
let PANDA_SDK_PATH = process.env.PANDA_SDK_PATH;
if (!PANDA_SDK_PATH) {
  if (useDest) {
    const destPanda = path.join(DEST_DIR, 'panda-sdk');
    if (fs.existsSync(destPanda)) {
      PANDA_SDK_PATH = destPanda;
    } else {
      throw new Error(`Dest Panda SDK not found: ${destPanda}. Run 'npm run assemble' to build artifacts.`);
    }
  } else {
    // Fallback to default location relative to workspace
    PANDA_SDK_PATH = path.join(SCRIPT_DIR, '../../../incremental/tools/panda/node_modules/@panda/sdk');
  }
}

const KOALA_WORKSPACE = process.env.KOALA_WORKSPACE || '0';

// Helper function to check if a file exists
function fileExists(filePath) {
  try {
    return fs.existsSync(filePath);
  } catch (err) {
    return false;
  }
}

// Helper function to parse config file
function parseConfig(configPath) {
  try {
    const configContent = fs.readFileSync(configPath, 'utf8');
    return JSON.parse(configContent);
  } catch (err) {
    throw new Error(`Failed to parse config file ${configPath}: ${err.message}`);
  }
}

// Helper function to expand glob pattern to file list
function expandGlobPattern(pattern, baseDir) {
  const files = [];

  // Fast path: if pattern contains no wildcards, treat as direct file path
  if (!pattern.includes('*') && !pattern.includes('?') && !pattern.includes('**')) {
    const resolvedPath = path.resolve(baseDir, pattern);
    if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()) {
      return [resolvedPath];
    }
    // If not found, fall back to glob matching (maybe pattern contains directories that don't exist)
  }

  // Convert glob pattern to regex
  function globToRegex(glob) {
    // Escape regex special characters except * and ?
    let regex = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&');
    // Convert * to .* (non-greedy)
    regex = regex.replace(/\*/g, '.*');
    // Convert ? to .
    regex = regex.replace(/\?/g, '.');
    // Ensure it matches entire filename
    return new RegExp('^' + regex + '$');
  }

  // Recursive directory traversal
  function walk(dir, patternParts, index) {
    // If we've processed all pattern parts, nothing to do
    if (index >= patternParts.length) {
      return;
    }

    const currentPart = patternParts[index];
    const isLastPart = index === patternParts.length - 1;

    // Handle ** specially
    if (currentPart === '**') {
      // ** matches zero or more directories
      if (isLastPart) {
        // ** at the end means match all files recursively
        function collectAllFiles(currentDir) {
          const entries = fs.readdirSync(currentDir, { withFileTypes: true });
          for (const entry of entries) {
            const fullPath = path.join(currentDir, entry.name);
            if (entry.isDirectory()) {
              collectAllFiles(fullPath);
            } else if (entry.isFile() && entry.name.endsWith('.ets')) {
              files.push(fullPath);
            }
          }
        }
        collectAllFiles(dir);
      } else {
        // ** followed by more pattern parts
        // Option 1: skip current directory (zero directories)
        walk(dir, patternParts, index + 1);
        // Option 2: match one or more directories
        const entries = fs.readdirSync(dir, { withFileTypes: true });
        for (const entry of entries) {
          if (entry.isDirectory()) {
            const fullPath = path.join(dir, entry.name);
            // Continue matching ** (allow multiple levels)
            walk(fullPath, patternParts, index);
            // Also try to match next pattern part
            walk(fullPath, patternParts, index + 1);
          }
        }
      }
      return;
    }

    // Not **, read directory entries
    const entries = fs.readdirSync(dir, { withFileTypes: true });

    if (isLastPart) {
      // Last part: match files
      const fileRegex = globToRegex(currentPart);
      for (const entry of entries) {
        if (entry.isFile() && fileRegex.test(entry.name)) {
          files.push(path.join(dir, entry.name));
        }
      }
    } else {
      // Directory part: match directories
      const dirRegex = globToRegex(currentPart);
      for (const entry of entries) {
        if (entry.isDirectory() && dirRegex.test(entry.name)) {
          const fullPath = path.join(dir, entry.name);
          walk(fullPath, patternParts, index + 1);
        }
      }
    }
  }

  // Normalize pattern
  const normalizedPattern = pattern.replace(/\\/g, '/');
  // Split by / but keep empty parts for absolute paths
  const parts = normalizedPattern.split('/');

  // Remove leading '.' or empty parts from relative patterns
  let cleanParts = parts.filter(p => p !== '' && p !== '.');

  // If pattern starts with '/', we need to handle absolute paths
  // For now, assume relative patterns

  walk(baseDir, cleanParts, 0);
  return files;
}

// Helper function to find included files from config
function findIncludedFiles(config, configDir) {
  const files = new Set();

  if (config.include) {
    const includes = Array.isArray(config.include) ? config.include : [config.include];

    for (const pattern of includes) {
      const resolvedPattern = path.isAbsolute(pattern) ? pattern : (pattern.startsWith('.') ? pattern : `./${pattern}`);
      const matched = expandGlobPattern(resolvedPattern, configDir);
      matched.forEach(file => files.add(file));
    }
  }

  return Array.from(files);
}

// Helper function to rewrite plugin paths for standalone SDK
function rewritePluginPathsForStandalone(config, sdkDestDir, verbose = false) {
  const modifiedConfig = JSON.parse(JSON.stringify(config)); // Deep copy

  // Map external package names to local dest paths
  const pluginMappings = {
    '@koalaui/ui-plugins-ng': path.join(sdkDestDir, 'ui-plugins-ng', 'lib', 'entry.js'),
    '@koalaui/memo-plugin-ng': path.join(sdkDestDir, 'memo-plugin-ng', 'lib', 'entry.js'),
    // Add other @koalaui plugins if needed
    // @ohos/arkui-plugins mappings for plugins_joint section
    '@ohos/arkui-plugins/ui-syntax': path.join(sdkDestDir, 'arkui-plugins', 'lib', 'ui-syntax-plugin.js'),
    '@ohos/arkui-plugins/ui': path.join(sdkDestDir, 'arkui-plugins', 'lib', 'ui-plugin.js'),
    '@ohos/arkui-plugins/memo': path.join(sdkDestDir, 'arkui-plugins', 'lib', 'memo-plugin.js'),
    '@ohos/arkui-plugins/interop': path.join(sdkDestDir, 'arkui-plugins', 'lib', 'interop-plugin.js'),
  };

  // Helper to rewrite plugin entries
  function rewritePluginEntry(pluginPath) {
    // If it's an external package we have locally, replace with local path
    if (pluginMappings[pluginPath]) {
      const localPath = pluginMappings[pluginPath];
      if (verbose) {
        console.log(`  Rewriting plugin path: ${pluginPath} -> ${localPath}`);
      }
      return localPath;
    }
    // If it's a relative or absolute path, keep as-is
    if (pluginPath.startsWith('.') || pluginPath.startsWith('/')) {
      return pluginPath;
    }
    // Could be other external package (like @ohos/arkui-plugins)
    // Leave unchanged - will be resolved via node_modules if available
    return pluginPath;
  }

  // Process plugins section
  if (modifiedConfig.plugins && typeof modifiedConfig.plugins === 'object') {
    if (verbose) {
      console.log('Rewriting plugin paths in "plugins" section...');
    }
    for (const [pluginName, pluginPath] of Object.entries(modifiedConfig.plugins)) {
      if (typeof pluginPath === 'string') {
        modifiedConfig.plugins[pluginName] = rewritePluginEntry(pluginPath);
      }
    }
  }

  // Process plugins_joint section
  if (modifiedConfig.plugins_joint && typeof modifiedConfig.plugins_joint === 'object') {
    if (verbose) {
      console.log('Rewriting plugin paths in "plugins_joint" section...');
    }
    for (const [pluginName, pluginPath] of Object.entries(modifiedConfig.plugins_joint)) {
      if (typeof pluginPath === 'string') {
        modifiedConfig.plugins_joint[pluginName] = rewritePluginEntry(pluginPath);
      }
    }
  }

  return modifiedConfig;
}

// Helper function to rewrite SDK paths for standalone SDK
// Rewrites paths containing 'dest/ohos_sdk' to absolute paths pointing to local SDK
function rewriteSdkPaths(config, sdkDestDir, configDir, verbose = false) {
  const modifiedConfig = JSON.parse(JSON.stringify(config)); // Deep copy

  // SDK root directory inside dest
  const sdkRoot = path.join(sdkDestDir, 'ohos_sdk', 'api');
  if (!fs.existsSync(sdkRoot)) {
    if (verbose) {
      console.log(`SDK root not found at ${sdkRoot}, skipping SDK path rewriting`);
    }
    return modifiedConfig;
  }

  // Compute relative path from config directory to SDK root
  const relativeSdkPath = path.relative(configDir, sdkRoot);
  // Ensure relative path starts with './' or '../' (not absolute)
  const relativeSdkPathNormalized = relativeSdkPath.startsWith('.') ? relativeSdkPath : './' + relativeSdkPath;

  if (verbose) {
    console.log(`Rewriting SDK paths: configDir=${configDir}, sdkRoot=${sdkRoot}, relative=${relativeSdkPathNormalized}`);
  }

  // Process compilerOptions.paths
  if (modifiedConfig.compilerOptions && modifiedConfig.compilerOptions.paths) {
    const paths = modifiedConfig.compilerOptions.paths;
    for (const [key, pathArray] of Object.entries(paths)) {
      if (Array.isArray(pathArray)) {
        const newPaths = pathArray.map(p => {
          // Check if path contains dest/ohos_sdk substring (handle both forward and back slashes)
          const pNormalized = p.replace(/\\/g, '/');
          if (pNormalized.includes('dest/ohos_sdk')) {
            // Replace the prefix up to dest/ohos_sdk/api with correct path relative to config
            const destMarker = 'dest/ohos_sdk/api';
            const idx = pNormalized.indexOf(destMarker);
            if (idx !== -1) {
              // Extract suffix from original path using same index (since both are single chars)
              let suffix = p.substring(idx + destMarker.length);
              // Remove leading slash if present (could be / or \)
              if (suffix.startsWith('/') || suffix.startsWith('\\')) {
                suffix = suffix.substring(1);
              }

              // Compute the new absolute path to the SDK file
              const newAbsolutePath = suffix ? path.join(sdkRoot, suffix) : sdkRoot;

              // Use absolute path (not relative)
              // Ensure forward slashes for consistency
              const normalizedNewPath = newAbsolutePath.replace(/\\/g, '/');
              if (verbose) {
                console.log(`  Rewriting SDK path: ${p} -> ${normalizedNewPath}`);
                console.log(`    Details: sdkRoot=${sdkRoot}, configDir=${configDir}, suffix=${suffix}`);
              }
              return normalizedNewPath;
            }
          }
          return p;
        });
        paths[key] = newPaths;
      }
    }
  }

  return modifiedConfig;
}

// Helper function to detect and process arguments for project compilation
function processArgs(args, verbose = false, tempFiles = null, noSimultaneous = false) {
  // Skip processing for help flag
  if (args.includes('--help') || args.includes('-h')) {
    return args;
  }

  // If --arktsconfig is already specified, don't modify
  const hasArktsConfig = args.some(arg => arg === '--arktsconfig' || arg.startsWith('--arktsconfig='));
  if (hasArktsConfig) {
    return args;
  }

  // Check if first argument is a config file (ends with .json and exists)
  if (args.length === 0) {
    return args;
  }

  const firstArg = args[0];
  const configExtensions = ['.json', 'ui2abcconfig.json'];
  const isConfigFile = configExtensions.some(ext => firstArg.endsWith(ext)) && fileExists(firstArg);

  if (!isConfigFile) {
    // If it looks like a config file but doesn't exist, warn the user
    const looksLikeConfig = configExtensions.some(ext => firstArg.endsWith(ext));
    if (looksLikeConfig && !fileExists(firstArg)) {
      console.warn(`Warning: Config file '${firstArg}' not found.`);
      console.warn(`If you intend to compile a project, ensure the ui2abcconfig.json file exists.`);
      console.warn(`Otherwise, use direct compilation: ui2abc [options] file.ets`);
    }
    return args;
  }

  if (verbose) {
    console.log(`Detected config file: ${firstArg}`);
  }
  const configPath = firstArg;
  const configDir = path.dirname(configPath);

  // Parse config
  let config;
  try {
    config = parseConfig(configPath);
  } catch (err) {
    console.error(`Error: Failed to parse config file '${configPath}': ${err.message}`);
    console.error(`Ensure the file is valid JSON and follows ui2abcconfig.json format.`);
    return args; // Fall back to original args
  }

  // Parse args to separate flags and positional arguments
  const parsed = parseArgList(args.slice(1)); // exclude config file
  const flags = parsed.flags;
  let positional = parsed.positional;

  // Add --arktsconfig flag with config path
  flags.push('--arktsconfig', configPath);

  // Add --simultaneous if not already present and noSimultaneous is false
  if (!noSimultaneous && !flags.some(arg => arg === '--simultaneous')) {
    flags.push('--simultaneous');
  }

  // Determine default output if --output not specified
  const hasOutput = flags.some(arg => arg === '--output' || arg.startsWith('--output='));
  if (!hasOutput) {
    let defaultOutput = 'build.abc';
    if (config.compilerOptions && config.compilerOptions.package) {
      const packageName = config.compilerOptions.package.replace('@', '').replace('/', '-');
      defaultOutput = `${packageName}.abc`;
    }

    if (config.compilerOptions && config.compilerOptions.outDir) {
      const outDir = path.resolve(configDir, config.compilerOptions.outDir);
      defaultOutput = path.join(outDir, defaultOutput);
      if (!fs.existsSync(outDir)) {
        fs.mkdirSync(outDir, { recursive: true });
        if (verbose) {
          console.log(`Created output directory: ${outDir}`);
        }
      }
    } else {
      defaultOutput = path.join(configDir, defaultOutput);
    }
    flags.push('--output', defaultOutput);
    if (verbose) {
      console.log(`Output will be written to: ${defaultOutput}`);
    }
  }

  // If no positional arguments (files), find from include patterns
  if (positional.length === 0) {
    try {
      const includedFiles = findIncludedFiles(config, configDir);
      if (includedFiles.length > 0) {
        if (verbose) {
          console.log(`Found ${includedFiles.length} file(s) from include patterns`);
        }
        positional.push(...includedFiles);
      } else {
        if (!config.include || config.include.length === 0) {
          console.warn('Warning: No include patterns specified in config');
        } else {
          console.warn('Warning: No files found matching include patterns:', config.include);
        }
        // Try to find any .ets file in config directory
        const etsFiles = expandGlobPattern('./**/*.ets', configDir);
        if (etsFiles.length > 0) {
          if (verbose) {
            console.log(`Using ${etsFiles[0]} as input file`);
          }
          positional.push(etsFiles[0]);
        } else {
          console.error('Error: No .ets files found to compile');
          return args; // Fall back
        }
      }
    } catch (err) {
      console.warn(`Warning: Could not find included files: ${err.message}`);
      return args; // Fall back
    }
  }

  // For multiple files, use response file to avoid command line length limits
  // Temporarily disabled: es2panda doesn't support @responsefile syntax
  if (false) {
    try {
      // Create response file with all file paths
      const now = new Date();
      const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}-${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
      const responseFileName = `ui2abc-response-${timestamp}-${Math.random().toString(36).slice(2)}.txt`;
      const responseFilePath = path.join(configDir, responseFileName);

      // Write file paths separated by spaces
      // Quote paths that contain spaces
      const fileLines = positional.map(filePath => {
        if (filePath.includes(' ')) {
          return `'${filePath}'`;
        }
        return filePath;
      });
      const responseContent = fileLines.join(' ');

      fs.writeFileSync(responseFilePath, responseContent);
      if (tempFiles) {
        tempFiles.push(responseFilePath);
      }

      if (verbose) {
        console.log(`Created response file with ${positional.length} files: ${responseFilePath}`);
      }

      // Replace positional files with response file reference
      positional = ['@' + responseFilePath];
    } catch (err) {
      console.warn(`Warning: Failed to create response file: ${err.message}`);
      // Continue with original files on command line
    }
  }

  // If multiple files, join them with spaces into a single argument
  // es2panda expects a single [input file] argument that can contain
  // multiple file paths separated by spaces or colons
  // Temporarily disabled: may cause issues with simultaneous compilation
  if (positional.length > 1) {
    // Quote paths that contain spaces
    const fileArgs = positional.map(filePath => {
      if (filePath.includes(' ')) {
        return `'${filePath}'`;
      }
      return filePath;
    });
    positional = [fileArgs.join(' ')];
    if (verbose) {
      console.log(`Joined ${fileArgs.length} files into single argument`);
    }
  }

  // Combine flags and positional arguments
  const newArgs = [...flags, ...positional];
  if (verbose) {
    console.log(`processArgs returning ${newArgs.length} arguments:`);
    console.log(`  Flags: ${flags.length}`);
    console.log(`  Positional files: ${positional.length}`);
    if (positional.length > 0) {
      console.log(`  First positional: ${positional[0]}`);
      if (positional.length > 1) {
        console.log(`  Last positional: ${positional[positional.length - 1]}`);
      }
    }
  }
  return newArgs;
}

// Parse argument list into flags (with their values) and positional arguments
function parseArgList(args) {
  const flags = [];
  const positional = [];
  let i = 0;
  while (i < args.length) {
    const arg = args[i];
    if (arg.startsWith('-')) {
      if (arg.includes('=')) {
        // --flag=value form
        flags.push(arg);
        i++;
      } else {
        // --flag value form
        flags.push(arg);
        i++;
        // Check if next argument exists and is not a flag (value for flag)
        if (i < args.length && !args[i].startsWith('-')) {
          flags.push(args[i]);
          i++;
        }
      }
    } else {
      positional.push(arg);
      i++;
    }
  }
  return { flags, positional };
}

// Process arguments for project compilation mode
let userArgs = process.argv.slice(2);

// Parse --keep flag (keep temporary config files)
let keepTempFiles = false;
let verbose = false;
let noRewrite = false;
let noSimultaneous = false;
let joint = false;
const filteredArgs = [];
for (let i = 0; i < userArgs.length; i++) {
  const arg = userArgs[i];
  if (arg === '--keep') {
    keepTempFiles = true;
    // Skip adding to filteredArgs
  } else if (arg === '--verbose') {
    verbose = true;
    // Skip adding to filteredArgs (ui2abc-only flag)
    // Don't pass to es2panda
  } else if (arg === '--no-rewrite') {
    noRewrite = true;
    // Skip adding to filteredArgs (ui2abc-only flag)
  } else if (arg === '--no-simultaneous') {
    noSimultaneous = true;
    // Skip adding to filteredArgs (ui2abc-only flag)
  } else if (arg === '--simultaneous') {
    // Skip adding to filteredArgs (ui2abc-only flag)
    // Simultaneous is added by default unless --no-simultaneous is specified
  } else if (arg === '--joint') {
    joint = true;
    // Skip adding to filteredArgs (ui2abc-only flag)
  } else {
    filteredArgs.push(arg);
  }
}
userArgs = filteredArgs;

// Show usage if no arguments
if (userArgs.length === 0) {
  console.log(`Usage: ui2abc [ui2abcconfig.json] [options] [files...]

Project compilation mode:
  ui2abc ui2abcconfig.json            # Compile project using config file
  ui2abc ui2abcconfig.json --output build.abc  # Specify output file
  ui2abc ui2abcconfig.json --keep     # Keep temporary config file after compilation

Direct compilation mode:
  ui2abc [options] file.ets           # Direct file compilation (passed to es2panda)
  ui2abc --help                       # Show es2panda help

Options:
  --keep                              Keep temporary config file (useful for debugging)
                                      Temporary config files are created when using assembled SDK
                                      to rewrite plugin paths for standalone operation.
  --verbose                           Show detailed logging about plugin path rewriting and config processing
  --no-rewrite                        Skip plugin and SDK path rewriting, use config file as-is
  --no-simultaneous                   Disable simultaneous compilation mode (default is simultaneous)
  --joint                             Use plugins_joint section instead of plugins section
                                      Copies plugins_joint entries to plugins before compilation

Examples:
  ui2abc ./project/ui2abcconfig.json
  ui2abc --arktsconfig ./config.json --output out.abc main.ets
  ui2abc --version
`);
  process.exit(0);
}

// Track temporary files for cleanup
const tempFiles = [];

function runCompilation(userArgs, verbose = false, noRewrite = false, noSimultaneous = false, joint = false) {
  // Check if first argument is a config file that needs plugin path rewriting
  if (userArgs.length > 0) {
    const firstArg = userArgs[0];
    const configExtensions = ['.json', 'ui2abcconfig.json'];
    const isConfigFile = configExtensions.some(ext => firstArg.endsWith(ext)) && fileExists(firstArg);

    if (isConfigFile && (useDest && !noRewrite || joint)) {
      if (verbose) {
        if (useDest && !noRewrite) {
          console.log(`Using assembled SDK, rewriting plugin paths in config...`);
        }
        if (joint) {
          console.log(`Joint mode enabled, will use plugins_joint section...`);
        }
      }

      // Parse original config
      let config;
      try {
        config = parseConfig(firstArg);
      } catch (err) {
        console.error(`Error: Failed to parse config file '${firstArg}': ${err.message}`);
        return 1;
      }

      const configDir = path.dirname(firstArg);

      // Start with original config
      let modifiedConfig = JSON.parse(JSON.stringify(config));

      // Apply rewrites if using assembled SDK
      if (useDest && !noRewrite) {
        // Rewrite plugin paths for standalone SDK
        modifiedConfig = rewritePluginPathsForStandalone(modifiedConfig, DEST_DIR, verbose);
        // Rewrite SDK paths for standalone SDK
        modifiedConfig = rewriteSdkPaths(modifiedConfig, DEST_DIR, configDir, verbose);
      }

      // Apply joint mode if requested
      if (joint && modifiedConfig.plugins_joint && typeof modifiedConfig.plugins_joint === 'object') {
        if (verbose) {
          console.log('Joint mode enabled: replacing plugins with plugins_joint');
        }
        // Replace entire plugins object with plugins_joint
        modifiedConfig.plugins = JSON.parse(JSON.stringify(modifiedConfig.plugins_joint));
        if (verbose) {
          console.log(`  Replaced plugins with ${Object.keys(modifiedConfig.plugins).length} plugin(s) from plugins_joint`);
        }
      }

      // Create temporary file with modified config (in same directory to preserve relative paths)
      const now = new Date();
      const year = now.getFullYear();
      const month = String(now.getMonth() + 1).padStart(2, '0');
      const day = String(now.getDate()).padStart(2, '0');
      const hours = String(now.getHours()).padStart(2, '0');
      const minutes = String(now.getMinutes()).padStart(2, '0');
      const seconds = String(now.getSeconds()).padStart(2, '0');
      const timestamp = `${year}${month}${day}-${hours}${minutes}${seconds}`;
      const tempFileName = `ui2abcconfig-${timestamp}-${Math.random().toString(36).slice(2)}.json`;
      const tempFilePath = path.join(configDir, tempFileName);

      fs.writeFileSync(tempFilePath, JSON.stringify(modifiedConfig, null, 2));
      tempFiles.push(tempFilePath);

      if (verbose) {
        console.log(`Created temporary config with modifications: ${tempFilePath}`);
      }

      // Replace original config path with temporary one
      userArgs[0] = tempFilePath;
    }
  }

  const processedArgs = processArgs(userArgs, verbose, tempFiles, noSimultaneous);

  if (verbose) {
    // Debug output removed
  }

  // Set environment variables for child process
  const env = {
    ...process.env,
    KOALA_WORKSPACE,
    PANDA_SDK_PATH,
    LIBARKTS_PATH,
  };

  // Forward all arguments to es2panda.js
  const args = [ES2PANDA_PATH, ...processedArgs];

  if (verbose) {
    console.log(`Running es2panda with ${args.length} arguments:`);
    console.log(`  Command: node ${ES2PANDA_PATH} ...`);
    console.log(`  First few args: ${args.slice(0, 5).join(' ')}`);
    if (processedArgs.length > 10) {
      console.log(`  (${processedArgs.length - 5} more args)`);
    }
  }

  if (process.env.DEBUG) {
    console.error(`DEBUG: Running node with es2panda at ${ES2PANDA_PATH}`);
    console.error(`DEBUG: PANDA_SDK_PATH=${PANDA_SDK_PATH}`);
    console.error(`DEBUG: Using dest: ${useDest}`);
  }

  let result;
  try {
    result = spawnSync('node', args, {
      env,
      stdio: 'inherit',
      cwd: process.cwd(),
    });
  } catch (err) {
    console.error(`Error: Failed to spawn child process: ${err.message}`);
    return 1;
  }

  // Handle child process exit status, signals, and errors
  if (result.signal) {
    console.error(`Error: es2panda terminated by signal ${result.signal}`);
    return 1;
  } else if (result.status === null) {
    console.error(`Error: es2panda exited with null status`);
    return 1;
  } else if (result.status !== 0) {
    return result.status;
  } else {
    return 0;
  }
}

// Clean up temporary files on exit
process.on('exit', () => {
  if (keepTempFiles) {
    console.log(`Temporary files kept (--keep flag specified):`);
    for (const tempFile of tempFiles) {
      console.log(`  ${tempFile}`);
    }
    return;
  }
  for (const tempFile of tempFiles) {
    try {
      fs.unlinkSync(tempFile);
      if (verbose) {
        console.log(`Cleaned up temporary file: ${tempFile}`);
      }
    } catch (err) {
      // Ignore errors during cleanup
    }
  }
});

const exitCode = runCompilation(userArgs, verbose, noRewrite, noSimultaneous, joint);
process.exit(exitCode);