#!/usr/bin/env node
/**
 * Zipwire CLI Runtime Wrapper
 *
 * Purpose:
 *   This JavaScript wrapper serves as the NPM package entry point for the zw CLI.
 *   It detects the user's platform, locates the appropriate platform-specific
 *   Go binary, and executes it with all command-line arguments.
 *
 * How It Works:
 *   1. Detects platform using Node.js runtime variables (process.platform, process.arch)
 *   2. Maps platform to the corresponding NPM satellite package name
 *   3. Resolves the satellite package location using require.resolve()
 *   4. Constructs the path to the Go binary within the satellite package
 *   5. Spawns the binary process with stdio inheritance for seamless I/O
 *   6. Forwards all command-line arguments and propagates exit codes
 *
 * Platform Mapping:
 *   - linux + x64 → @zipwire/zw-linux-amd64
 *   - linux + arm64 → @zipwire/zw-linux-arm64
 *   - darwin + x64 → @zipwire/zw-darwin-amd64
 *   - darwin + arm64 → @zipwire/zw-darwin-arm64
 *   - win32 + x64 → @zipwire/zw-windows-amd64
 *
 * Error Handling:
 *   - Unsupported platforms: Clear error message with troubleshooting steps
 *   - Binary not found: Fallback search in common node_modules locations
 *   - Execution failures: Detailed error messages with actionable guidance
 *
 * Stdio Configuration:
 *   
 *   What is this?
 *   The wrapper can use two different methods to handle input/output streams between
 *   the Node.js wrapper and the Go binary: 'inherit' (default) or 'pipe' mode.
 *   
 *   Default Mode ('inherit'):
 *   - Directly connects the Go binary's stdin/stdout/stderr to your terminal
 *   - Works perfectly for 99% of users and scenarios
 *   - Provides seamless I/O with no overhead
 *   
 *   Pipe Mode ('pipe'):
 *   - Manually pipes data between the wrapper and binary through Node.js streams
 *   - Use ONLY if you encounter issues with the default mode
 *   - When to use:
 *     * Older Windows terminals (cmd.exe, older PowerShell) that handle inherited
 *       streams poorly, causing garbled output or hanging processes
 *     * Specialized terminal emulators with stream compatibility issues
 *     * CI/CD environments where inherited streams cause problems
 *   
 *   Usage:
 *     CLI_STDIO_MODE=pipe zw <command>
 *   
 *   Note: If you're not experiencing any issues, you don't need to set this variable.
 *         The default 'inherit' mode is faster and more efficient.
 *
 * Requirements:
 *   - Node.js >= 14.0.0
 *   - Platform-specific satellite package must be installed
 *
 * This file is executed automatically by NPM when users run the 'zw' command.
 * Users should not need to run this file directly.
 */

const { spawn, execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const platform = process.platform;
const arch = process.arch;

function getPackageName(platform, arch) {
  const platformMap = {
    'linux': {
      'x64': '@zipwire/zw-linux-amd64',
      'arm64': '@zipwire/zw-linux-arm64'
    },
    'darwin': {
      'x64': '@zipwire/zw-darwin-amd64',
      'arm64': '@zipwire/zw-darwin-arm64'
    },
    'win32': {
      'x64': '@zipwire/zw-windows-amd64'
    }
  };

  if (!platformMap[platform]) {
    return null;
  }

  return platformMap[platform][arch] || null;
}

function getBinaryName(platform, arch) {
  if (platform === 'win32') {
    return 'zw-windows-amd64.exe';
  }

  const archMap = {
    'x64': 'amd64',
    'arm64': 'arm64'
  };

  const goArch = archMap[arch] || arch;
  const osMap = {
    'linux': 'linux',
    'darwin': 'darwin'
  };

  const goOs = osMap[platform] || platform;
  return `zw-${goOs}-${goArch}`;
}

function getNpmGlobalPrefix() {
  try {
    return execSync('npm config get prefix', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
  } catch (error) {
    return null;
  }
}

function findBinaryPath(packageName, binaryName) {
  try {
    const packagePath = require.resolve(packageName);
    const packageDir = path.dirname(packagePath);
    const packageJsonPath = path.join(packageDir, 'package.json');

    if (fs.existsSync(packageJsonPath)) {
      const packageJson = require(packageJsonPath);
      if (packageJson.bin && packageJson.bin.zw) {
        const binPath = path.resolve(packageDir, packageJson.bin.zw);
        if (fs.existsSync(binPath)) {
          return binPath;
        }
      }
    }

    const binPath = path.join(packageDir, 'bin', binaryName);
    if (fs.existsSync(binPath)) {
      return binPath;
    }
  } catch (error) {
  }

  const npmPrefix = getNpmGlobalPrefix();
  const fallbackPaths = [
    path.join(process.cwd(), 'node_modules', packageName, 'bin', binaryName),
    path.join(__dirname, '..', 'node_modules', packageName, 'bin', binaryName),
    path.join(process.env.HOME || process.env.USERPROFILE, '.npm-global', 'lib', 'node_modules', packageName, 'bin', binaryName),
    path.join(process.env.APPDATA || '', 'npm', 'node_modules', packageName, 'bin', binaryName)
  ];

  if (npmPrefix) {
    fallbackPaths.push(path.join(npmPrefix, 'lib', 'node_modules', packageName, 'bin', binaryName));
  }

  for (const fallbackPath of fallbackPaths) {
    if (fs.existsSync(fallbackPath)) {
      return fallbackPath;
    }
  }

  if (npmPrefix) {
    try {
      const Module = require('module');
      const nodeModulesPath = path.join(npmPrefix, 'lib', 'node_modules');
      const packagePath = Module._resolveFilename(packageName, { paths: [nodeModulesPath] }, false);
      const packageDir = path.dirname(packagePath);
      const packageJsonPath = path.join(packageDir, 'package.json');

      if (fs.existsSync(packageJsonPath)) {
        const packageJson = require(packageJsonPath);
        if (packageJson.bin && packageJson.bin.zw) {
          const binPath = path.resolve(packageDir, packageJson.bin.zw);
          if (fs.existsSync(binPath)) {
            return binPath;
          }
        }
      }

      const binPath = path.join(packageDir, 'bin', binaryName);
      if (fs.existsSync(binPath)) {
        return binPath;
      }
    } catch (error) {
    }
  }

  return null;
}

function ensureExecutable(binaryPath) {
  if (process.platform === 'win32') {
    return;
  }

  try {
    fs.accessSync(binaryPath, fs.constants.X_OK);
  } catch (error) {
    try {
      fs.chmodSync(binaryPath, 0o755);
    } catch (chmodError) {
      console.error(`Warning: Could not set executable permissions on ${binaryPath}`);
    }
  }
}

function getStdioMode() {
  const mode = process.env.CLI_STDIO_MODE;
  if (mode === 'pipe') {
    return 'pipe';
  }
  return 'inherit';
}

function setupPipeMode(child) {
  process.stdin.pipe(child.stdin);
  child.stdout.pipe(process.stdout);
  child.stderr.pipe(process.stderr);

  process.stdin.on('error', () => { });
  child.stdout.on('error', () => { });
  child.stderr.on('error', () => { });

  process.stdin.on('end', () => {
    if (child.stdin && !child.stdin.destroyed) {
      child.stdin.end();
    }
  });
}

function main() {
  const packageName = getPackageName(platform, arch);

  if (!packageName) {
    console.error(`Error: Unsupported platform: ${platform} ${arch}`);
    console.error('');
    console.error('Supported platforms:');
    console.error('  - Linux (x64, arm64)');
    console.error('  - macOS (x64, arm64)');
    console.error('  - Windows (x64)');
    console.error('');
    console.error('For manual installation or other platforms, please visit:');
    console.error('  https://github.com/lukepuplett/zwcli');
    process.exit(1);
  }

  const binaryName = getBinaryName(platform, arch);
  const binaryPath = findBinaryPath(packageName, binaryName);

  if (!binaryPath) {
    console.error(`Error: Could not find binary for ${packageName}`);
    console.error('');
    console.error('The platform-specific package may not have been installed correctly.');
    console.error('Try reinstalling:');
    console.error('  npm install -g @zipwire/zw');
    console.error('');
    console.error('If the problem persists, please check:');
    console.error('  1. Node.js version is >= 14.0.0');
    console.error('  2. NPM installation completed without errors');
    console.error('  3. Platform-specific package exists in node_modules');
    process.exit(1);
  }

  if (!fs.existsSync(binaryPath)) {
    console.error(`Error: Binary not found at ${binaryPath}`);
    process.exit(1);
  }

  ensureExecutable(binaryPath);

  const args = process.argv.slice(2);
  const stdioMode = getStdioMode();
  const child = spawn(binaryPath, args, {
    stdio: stdioMode,
    shell: false
  });

  if (stdioMode === 'pipe') {
    setupPipeMode(child);
  }

  child.on('error', (error) => {
    console.error(`Error executing binary: ${error.message}`);
    if (error.code === 'ENOENT') {
      console.error('The binary file was not found. It may have been deleted or moved.');
    } else if (error.code === 'EACCES') {
      console.error('Permission denied. Try running with appropriate permissions.');
    }
    process.exit(1);
  });

  child.on('exit', (code) => {
    process.exit(code || 0);
  });
}

main();

