#!/usr/bin/env node
'use strict';

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

const BOLD = '\x1b[1m';
const DIM = '\x1b[2m';
const GREEN = '\x1b[32m';
const YELLOW = '\x1b[33m';
const RED = '\x1b[31m';
const CYAN = '\x1b[36m';
const RESET = '\x1b[0m';

const { injectMcpConfigs } = require('./mcp-inject');
const TEMPLATE_DIR = path.join(__dirname, '..', 'template');
const LABEL = 'com.walle.server';
const INSTALL_PATH_FILE = path.join(process.env.HOME, '.walle', 'install-path');
const TELEMETRY_DATA_DIR = process.env.WALL_E_DATA_DIR || path.join(process.env.HOME || '/tmp', '.walle', 'data');
const CLI_LIFECYCLE_FILE = path.join(TELEMETRY_DATA_DIR, '.cli-lifecycle.jsonl');
const MANAGED_PACKAGE_DIRS = ['claude-task-manager', 'wall-e'];
const NATIVE_DEPENDENCIES = new Set([
  'better-sqlite3',
  'node-pty',
  'sqlite-vec',
  'tree-sitter-bash',
]);

// Files to preserve during update (user config, not code)
const PRESERVE_ON_UPDATE = ['.env', 'wall-e/wall-e-config.json'];

function writeCliLifecycleEvent(event, meta = {}) {
  if (process.env.WALLE_TELEMETRY === '0' || process.env.WALLE_TELEMETRY === 'false') return;
  try {
    fs.mkdirSync(TELEMETRY_DATA_DIR, { recursive: true });
    const pkg = require('../package.json');
    const entry = {
      event,
      meta: {
        command: meta.command || '',
        package_version: pkg.version || 'unknown',
        elapsed_ms: Number.isFinite(meta.elapsed_ms) ? meta.elapsed_ms : undefined,
      },
      t: Date.now(),
    };
    fs.appendFileSync(CLI_LIFECYCLE_FILE, JSON.stringify(entry) + '\n', { mode: 0o600 });
  } catch {}
}

// ── CLI Router ──

if (require.main === module) {
  main(process.argv.slice(2));
}

function main(argv = []) {
  const command = argv[0] || '';

  if (command === 'install') {
    install(argv[1]);
  } else if (command === 'update' || command === 'upgrade') {
    update();
  } else if (command === 'start') {
    start();
  } else if (command === 'stop') {
    stop();
  } else if (command === 'status') {
    status();
  } else if (command === 'logs') {
    logs();
  } else if (command === 'uninstall') {
    uninstall();
  } else if (command === '--help' || command === '-h' || command === 'help') {
    usage();
  } else if (command === '--version' || command === '-v') {
    const pkg = require('../package.json');
    console.log(pkg.version);
  } else if (command && !command.startsWith('-')) {
    install(command);
  } else {
    usage();
  }
}

// ── Commands ──

function usage() {
  console.log(`
${BOLD}${CYAN}  Wall-E${RESET} — Your personal digital twin

  ${BOLD}Usage:${RESET}

    ${BOLD}npx create-walle install <dir>${RESET}   Install (or detect existing install and update)
    ${BOLD}npx create-walle update${RESET}          Update code to latest, keep your config
    ${BOLD}npx create-walle start${RESET}           Start Wall-E (auto-restarts on reboot)
    ${BOLD}npx create-walle stop${RESET}            Stop Wall-E
    ${BOLD}npx create-walle status${RESET}          Check if Wall-E is running
    ${BOLD}npx create-walle logs${RESET}            Tail the service logs
    ${BOLD}npx create-walle uninstall${RESET}       Remove the auto-start service

  ${DIM}Commands like start/stop/update auto-detect your install directory.
  Or set WALLE_DIR to point to it.${RESET}
`);
}

function printMcpResults(wallePort) {
  try {
    const results = injectMcpConfigs(wallePort);
    const hasAny = results.some(r => r.action !== 'not_installed');
    if (!hasAny) return;

    console.log(`  ${BOLD}MCP Integrations:${RESET}`);
    for (const r of results) {
      if (r.action === 'not_installed') {
        console.log(`     ${DIM}-  ${r.tool} -- not installed${RESET}`);
      } else if (r.action === 'already_configured') {
        console.log(`     ${DIM}=  ${r.tool} -- already configured${RESET}`);
      } else if (r.action === 'updated') {
        const label = r.kind === 'agent_instructions' ? 'updated memory routing in' : 'updated port in';
        console.log(`     ${GREEN}~  ${r.tool}${RESET} -- ${label} ${DIM}${r.configPath}${RESET}`);
      } else {
        const label = r.kind === 'agent_instructions' ? 'added memory routing to' : 'added Wall-E to';
        console.log(`     ${GREEN}+  ${r.tool}${RESET} -- ${label} ${DIM}${r.configPath}${RESET}`);
      }
    }
    console.log(`\n  ${DIM}Your AI coding tools can now access Wall-E's memory and knowledge.`);
    console.log(`  To undo: remove the "wall-e" entry from the config files above.${RESET}\n`);
  } catch {}
}

function install(targetDir) {
  // If no target dir given, check for existing install
  if (!targetDir) {
    const existing = findWalleDirSilent();
    if (existing) {
      console.log(`\n  Found existing install at ${DIM}${existing}${RESET}`);
      console.log(`  Running update instead...\n`);
      update();
      return;
    }
    console.log(`\n  ${RED}Missing directory name.${RESET} Usage: npx create-walle install ./my-agent\n`);
    process.exit(1);
  }

  // If target dir exists and has Wall-E, treat as update
  if (fs.existsSync(targetDir) && fs.existsSync(path.join(targetDir, 'claude-task-manager', 'server.js'))) {
    console.log(`\n  ${CYAN}Wall-E already installed at ${targetDir}.${RESET} Updating...\n`);
    saveWalleDir(path.resolve(targetDir));
    update();
    return;
  }

  // Fresh install
  console.log(`\n${BOLD}${CYAN}  Wall-E${RESET} — Installing...\n`);

  if (fs.existsSync(targetDir)) {
    console.log(`  ${RED}Directory "${targetDir}" exists but is not a Wall-E install.${RESET}\n`);
    process.exit(1);
  }
  if (!fs.existsSync(TEMPLATE_DIR)) {
    console.error('  Template not found. Try: npm cache clean --force && npx create-walle@latest');
    process.exit(1);
  }
  const installStartedAt = Date.now();
  writeCliLifecycleEvent('cli_install_started', { command: 'install' });

  const ownerName = detectName().replace(/[\r\n=]/g, '').trim().slice(0, 200);
  const timezone = detectTimezone();
  const nameParts = ownerName.split(/\s+/);
  const port = process.env.CTM_PORT || '3456';
  const wallePort = String(parseInt(port) + 1);

  console.log(`  ${DIM}Owner:    ${ownerName}${RESET}`);
  console.log(`  ${DIM}Timezone: ${timezone}${RESET}`);
  console.log(`  ${DIM}Port:     ${port}${RESET}`);

  console.log(`\n  Copying files...`);
  copyRecursive(TEMPLATE_DIR, targetDir);

  console.log(`  Installing dependencies...\n`);
  npmInstall(targetDir);

  compileHotkeyDaemon(targetDir);

  // .env
  const envLines = [
    `# Wall-E configuration — generated ${new Date().toISOString().split('T')[0]}`,
    `# Finish setup at http://localhost:${port}/setup.html`,
    '',
    `WALLE_OWNER_NAME="${ownerName.replace(/"/g, '\\"')}"`,
    '# ANTHROPIC_API_KEY=',
    '',
    '# AI Provider: anthropic, openai, google, ollama',
    '# WALLE_PROVIDER=anthropic',
    '# WALLE_MODEL=',
    '',
    `CTM_PORT=${port}`,
    `WALL_E_PORT=${wallePort}`,
    '',
    '# SLACK_TOKEN=',
    '# SLACK_OWNER_USER_ID=',
    '# SLACK_OWNER_HANDLE=',
  ];
  fs.writeFileSync(path.join(targetDir, '.env'), envLines.join('\n') + '\n', { mode: 0o600 });

  // wall-e-config.json
  fs.writeFileSync(
    path.join(targetDir, 'wall-e', 'wall-e-config.json'),
    JSON.stringify({
      owner: { name: ownerName, first_name: nameParts[0], last_name: nameParts.slice(1).join(' '), timezone },
      adapters: { ctm: { enabled: true }, slack: { enabled: false } },
      intervals: { ingest_ms: 60000, think_ms: 120000 },
    }, null, 2) + '\n',
    { mode: 0o600 }
  );

  fs.mkdirSync(path.join(process.env.HOME, '.walle', 'data'), { recursive: true });
  try { execFileSync('git', ['init', '-q'], { cwd: targetDir, stdio: 'ignore' }); } catch {}

  // Stamp version into root package.json so the settings page shows it
  stampVersion(targetDir);

  saveWalleDir(path.resolve(targetDir));

  // Start the service
  console.log(`  Starting Wall-E...`);
  startForegroundOrService(path.resolve(targetDir), port);

  console.log(`
  ${GREEN}${BOLD}Wall-E is running!${RESET}

  ${BOLD}Open:${RESET}  http://localhost:${port}

  ${BOLD}Commands:${RESET}
    npx create-walle update      ${DIM}Update to latest version${RESET}
    npx create-walle stop        ${DIM}Stop the service${RESET}
    npx create-walle start       ${DIM}Restart${RESET}
    npx create-walle status      ${DIM}Check status${RESET}
    npx create-walle logs        ${DIM}View logs${RESET}
`);
  printMcpResults(parseInt(wallePort));
  writeCliLifecycleEvent('cli_install_completed', {
    command: 'install',
    elapsed_ms: Date.now() - installStartedAt,
  });
}

function update() {
  const dir = findWalleDir();
  const port = readPort(dir);
  const pkg = require('../package.json');
  const updateStartedAt = Date.now();
  writeCliLifecycleEvent('cli_update_started', { command: 'update' });

  console.log(`${BOLD}${CYAN}  Wall-E${RESET} — Updating to v${pkg.version}...\n`);
  console.log(`  ${DIM}Directory: ${dir}${RESET}`);

  if (!fs.existsSync(TEMPLATE_DIR)) {
    console.error('  Template not found. Try: npm cache clean --force && npx create-walle@latest update');
    process.exit(1);
  }

  // 1. Stop if running
  console.log(`  Stopping Wall-E...`);
  stopQuiet(dir, port);

  // 2. Backup preserved files
  const backups = {};
  for (const relPath of PRESERVE_ON_UPDATE) {
    const fullPath = path.join(dir, relPath);
    if (fs.existsSync(fullPath)) {
      backups[relPath] = fs.readFileSync(fullPath);
    }
  }

  // 3. Copy new code (overwrite everything except node_modules, .git, data)
  console.log(`  Updating code...`);
  copyRecursive(TEMPLATE_DIR, dir);

  // 4. Restore preserved files
  for (const [relPath, content] of Object.entries(backups)) {
    const fullPath = path.join(dir, relPath);
    fs.mkdirSync(path.dirname(fullPath), { recursive: true });
    fs.writeFileSync(fullPath, content);
  }

  // 5. Stamp version
  stampVersion(dir);

  // 6. Reinstall deps (in case package.json changed)
  console.log(`  Installing dependencies...\n`);
  npmInstall(dir);

  compileHotkeyDaemon(dir);

  // 7. Start again
  console.log(`\n  Starting Wall-E...`);
  startForegroundOrService(dir, port);

  console.log(`
  ${GREEN}${BOLD}Updated to v${pkg.version}!${RESET}  http://localhost:${port}

  ${DIM}Your .env and config were preserved.${RESET}
`);
  printMcpResults(parseInt(readWallePort(dir)));
  writeCliLifecycleEvent('cli_update_completed', {
    command: 'update',
    elapsed_ms: Date.now() - updateStartedAt,
  });
}

function start() {
  const dir = findWalleDir();
  const port = readPort(dir);
  console.log(`\n  Starting Wall-E from ${DIM}${dir}${RESET} on port ${port}...`);
  try {
    repairNativeDependencies(dir, { phase: 'start' });
  } catch (err) {
    console.error(`\n  ${RED}Native module repair failed.${RESET}`);
    console.error(`  ${DIM}${err.message}${RESET}`);
    console.error(`  Try: ${BOLD}cd ${dir}/claude-task-manager && npm rebuild better-sqlite3 node-pty${RESET}`);
    console.error(`       ${BOLD}cd ${dir}/wall-e && npm rebuild better-sqlite3 sqlite-vec tree-sitter-bash${RESET}\n`);
    process.exit(1);
  }
  installService(dir, port);
  console.log(`  ${GREEN}Running!${RESET}  http://localhost:${port}\n`);
  printMcpResults(parseInt(readWallePort(dir)));
}

function stop() {
  const dir = findWalleDirSilent();
  const port = dir ? readPort(dir) : '3456';
  stopQuiet(dir, port);
  console.log(`\n  ${GREEN}Wall-E stopped.${RESET}`);
  console.log(`  Run ${BOLD}npx create-walle start${RESET} to restart.\n`);
}

function status() {
  const dir = findWalleDir();
  const port = readPort(dir);
  try {
    const result = execFileSync('curl', ['-s', `http://localhost:${port}/api/services/status`], { encoding: 'utf8', timeout: 3000 });
    const data = JSON.parse(result);
    console.log(`\n  ${GREEN}${BOLD}Wall-E is running${RESET} on port ${port}`);
    console.log(`  ${DIM}CTM:    PID ${data.ctm?.pid || '?'}, uptime ${Math.round((data.ctm?.uptime || 0) / 60)}m${RESET}`);
    console.log(`  ${DIM}Wall-E: PID ${data.walle?.pid || '?'}${RESET}`);
    console.log(`  ${DIM}Dir:    ${dir}${RESET}\n`);
  } catch {
    console.log(`\n  ${RED}Wall-E is not running${RESET} (port ${port})`);
    console.log(`  Run ${BOLD}npx create-walle start${RESET} to start.\n`);
  }
}

function logs() {
  const logFile = path.join(process.env.HOME, '.walle', 'logs', 'walle.log');
  if (!fs.existsSync(logFile)) {
    console.log(`\n  No logs yet at ${logFile}\n`);
    return;
  }
  const child = spawn('tail', ['-f', logFile], { stdio: 'inherit' });
  child.on('error', () => console.log(`  Could not tail ${logFile}`));
}

function uninstall() {
  const plist = path.join(process.env.HOME, 'Library', 'LaunchAgents', `${LABEL}.plist`);
  try { execFileSync('launchctl', ['unload', plist], { stdio: 'ignore' }); } catch {}
  try { fs.unlinkSync(plist); } catch {}
  try { fs.unlinkSync(INSTALL_PATH_FILE); } catch {}
  console.log(`\n  ${GREEN}Service uninstalled.${RESET} Your data in ~/.walle/data is preserved.\n`);
}

// ── Service Management ──

function stopQuiet(dir, port) {
  // Try launchctl first (macOS)
  const plist = path.join(process.env.HOME, 'Library', 'LaunchAgents', `${LABEL}.plist`);
  if (process.platform === 'darwin' && fs.existsSync(plist)) {
    try { execFileSync('launchctl', ['unload', plist], { stdio: 'ignore' }); } catch {}
  }
  // Kill CTM and Wall-E processes
  const wallePort = dir ? readWallePort(dir) : String(parseInt(port) + 1);
  for (const p of [port, wallePort]) {
    try {
      const pids = execFileSync('lsof', ['-ti', '-sTCP:LISTEN', ':' + p], { encoding: 'utf8', timeout: 3000 }).trim().split('\n').filter(Boolean);
      for (const pid of pids) { try { process.kill(parseInt(pid), 'SIGTERM'); } catch {} }
    } catch {}
  }
  // Brief wait for port to free
  const deadline = Date.now() + 5000;
  while (Date.now() < deadline) {
    try {
      execFileSync('curl', ['-s', '--max-time', '1', `http://localhost:${port}/api/services/status`], { encoding: 'utf8', timeout: 2000 });
      // Still running — sleep briefly (avoid CPU spin)
      try { execFileSync('sleep', ['0.5'], { timeout: 2000 }); } catch {}
    } catch {
      break; // Port is free
    }
  }
}

function startForegroundOrService(dir, port) {
  // If launchd plist exists, reload it. Otherwise start in background.
  const plist = path.join(process.env.HOME, 'Library', 'LaunchAgents', `${LABEL}.plist`);
  if (process.platform === 'darwin' && fs.existsSync(plist)) {
    // Update plist with latest paths, then reload
    installService(dir, port);
  } else {
    // Start in background without launchd
    const logDir = path.join(process.env.HOME, '.walle', 'logs');
    fs.mkdirSync(logDir, { recursive: true });
    const child = spawn(process.execPath, ['claude-task-manager/server.js'], {
      cwd: dir,
      detached: true,
      stdio: ['ignore', fs.openSync(path.join(logDir, 'walle.log'), 'a'), fs.openSync(path.join(logDir, 'walle.err'), 'a')],
      env: { ...process.env, CTM_PORT: port, WALL_E_PORT: String(parseInt(port) + 1) },
    });
    child.unref();
  }
}

function installService(walleDir, port) {
  const logDir = path.join(process.env.HOME, '.walle', 'logs');
  fs.mkdirSync(logDir, { recursive: true });

  if (process.platform === 'darwin') {
    const nodePath = process.execPath;
    const plistDir = path.join(process.env.HOME, 'Library', 'LaunchAgents');
    fs.mkdirSync(plistDir, { recursive: true });
    const plistPath = path.join(plistDir, `${LABEL}.plist`);

    const xmlEsc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    const nodeBinDir = path.dirname(nodePath);
    let envDict = `    <key>PATH</key>\n    <string>${xmlEsc(nodeBinDir)}:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>\n`;
    envDict += `    <key>HOME</key>\n    <string>${xmlEsc(process.env.HOME)}</string>\n`;
    envDict += `    <key>CTM_PORT</key>\n    <string>${port}</string>\n`;
    envDict += `    <key>CTM_MANAGED_BY_LAUNCHD</key>\n    <string>1</string>\n`;
    try {
      const envFile = fs.readFileSync(path.join(walleDir, '.env'), 'utf8');
      for (const line of envFile.split('\n')) {
        const m = line.match(/^\s*([^#=\s]+)\s*=\s*(.+?)\s*$/);
        if (m && m[1] !== 'CTM_PORT' && m[1] !== 'PATH' && m[1] !== 'HOME') {
          envDict += `    <key>${xmlEsc(m[1])}</key>\n    <string>${xmlEsc(m[2])}</string>\n`;
        }
      }
    } catch {}

    const plist = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>${LABEL}</string>
  <key>ProgramArguments</key>
  <array>
    <string>${nodePath}</string>
    <string>${walleDir}/claude-task-manager/server.js</string>
  </array>
  <key>WorkingDirectory</key>
  <string>${walleDir}</string>
  <key>EnvironmentVariables</key>
  <dict>
${envDict}  </dict>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <dict>
    <key>SuccessfulExit</key>
    <false/>
  </dict>
  <key>ThrottleInterval</key>
  <integer>5</integer>
  <key>StandardOutPath</key>
  <string>${logDir}/ctm.log</string>
  <key>StandardErrorPath</key>
  <string>${logDir}/ctm.err</string>
</dict>
</plist>`;

    fs.writeFileSync(plistPath, plist);
    try { execFileSync('launchctl', ['unload', plistPath], { stdio: 'ignore' }); } catch {}
    execFileSync('launchctl', ['load', plistPath]);
  } else {
    // Linux/other: spawn with a restart-on-crash wrapper shell script
    const watchdogScript = path.join(walleDir, 'bin', 'watchdog.sh');
    const watchdogContent = `#!/bin/bash
# Auto-restart CTM on crash. Clean exit (code 0) stops the watchdog.
CRASHES=0
while true; do
  "${process.execPath}" claude-task-manager/server.js >> "${logDir}/ctm.log" 2>> "${logDir}/ctm.err"
  EXIT_CODE=$?
  if [ $EXIT_CODE -eq 0 ]; then
    echo "[watchdog] Clean exit (code 0), stopping." >> "${logDir}/ctm.log"
    break
  fi
  CRASHES=$((CRASHES + 1))
  echo "[watchdog] CTM exited with code $EXIT_CODE (crash #$CRASHES), restarting in 5s..." >> "${logDir}/ctm.log"
  if [ $CRASHES -ge 5 ]; then
    echo "[watchdog] Too many crashes ($CRASHES), giving up." >> "${logDir}/ctm.log"
    break
  fi
  sleep 5
done
`;
    fs.mkdirSync(path.join(walleDir, 'bin'), { recursive: true });
    fs.writeFileSync(watchdogScript, watchdogContent, { mode: 0o755 });

    const child = spawn('bash', [watchdogScript], {
      cwd: walleDir,
      detached: true,
      stdio: 'ignore',
      env: { ...process.env, CTM_PORT: port },
    });
    child.unref();
    fs.writeFileSync(path.join(logDir, 'walle.pid'), String(child.pid));
  }
}

// ── Helpers ──

function npmInstall(dir) {
  try {
    for (const relDir of MANAGED_PACKAGE_DIRS) {
      runNpm(path.join(dir, relDir), ['install', '--loglevel=warn']);
    }
    repairNativeDependencies(dir, { phase: 'install' });
  } catch (err) {
    console.error(`\n  ${RED}npm install failed.${RESET}`);
    if (err && err.message) console.error(`  ${DIM}${err.message}${RESET}`);
    console.error(`  Try manually with the same Node version used by Wall-E:\n    cd ${dir}/claude-task-manager && npm install && npm rebuild better-sqlite3 node-pty\n    cd ${dir}/wall-e && npm install && npm rebuild better-sqlite3 sqlite-vec tree-sitter-bash\n`);
    process.exit(1);
  }
}

function repairNativeDependencies(walleDir, {
  phase = 'install',
  checkDependency = checkNativeDependency,
  runNpmCommand = runNpm,
  log = console.log,
} = {}) {
  const repairs = [];
  for (const relDir of MANAGED_PACKAGE_DIRS) {
    const packageDir = path.join(walleDir, relDir);
    const deps = nativeDependenciesForPackage(packageDir);
    if (!deps.length) continue;

    const failed = [];
    for (const dep of deps) {
      const check = checkDependency(packageDir, dep);
      if (!check.ok) failed.push(dep);
    }
    if (!failed.length) continue;

    log(`  ${YELLOW}Rebuilding native modules for ${relDir}${RESET} ${DIM}(Node ${process.version}, ABI ${process.versions.modules})${RESET}`);
    runNpmCommand(packageDir, ['rebuild', ...failed, '--loglevel=warn']);

    const stillBroken = [];
    for (const dep of failed) {
      const check = checkDependency(packageDir, dep);
      if (!check.ok) stillBroken.push(`${dep}: ${firstLine(check.error)}`);
    }
    if (stillBroken.length) {
      throw new Error(`Native module repair failed in ${relDir} after ${phase}: ${stillBroken.join('; ')}`);
    }
    repairs.push(`${relDir}: ${failed.join(', ')}`);
  }

  if (repairs.length) {
    log(`  ${GREEN}Native modules rebuilt successfully${RESET} ${DIM}(${repairs.join('; ')})${RESET}`);
  }
  return repairs;
}

function nativeDependenciesForPackage(packageDir) {
  const pkgPath = path.join(packageDir, 'package.json');
  let pkg;
  try {
    pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
  } catch {
    return [];
  }
  const declared = {
    ...(pkg.dependencies || {}),
    ...(pkg.optionalDependencies || {}),
  };
  return Object.keys(declared).filter((name) => NATIVE_DEPENDENCIES.has(name));
}

function checkNativeDependency(packageDir, dependency) {
  try {
    execFileSync(process.execPath, ['-e', `require(${JSON.stringify(dependency)})`], {
      cwd: packageDir,
      encoding: 'utf8',
      stdio: ['ignore', 'pipe', 'pipe'],
      timeout: 15000,
      env: npmChildEnv(),
    });
    return { ok: true };
  } catch (err) {
    const error = [err && err.message, err && err.stdout, err && err.stderr].filter(Boolean).join('\n');
    return { ok: false, error };
  }
}

function runNpm(cwd, args) {
  const runner = resolveNpmRunner();
  if (runner.type === 'node-cli') {
    execFileSync(process.execPath, [runner.path, ...args], {
      cwd,
      stdio: 'inherit',
      env: npmChildEnv(),
    });
    return;
  }
  execFileSync('npm', args, {
    cwd,
    stdio: 'inherit',
    env: npmChildEnv(),
  });
}

function resolveNpmRunner(env = process.env, execPath = process.execPath) {
  const envPath = normalizeNpmExecPath(env.npm_execpath);
  if (envPath && fs.existsSync(envPath)) return { type: 'node-cli', path: envPath };

  for (const candidate of npmCliCandidates(execPath)) {
    if (fs.existsSync(candidate)) return { type: 'node-cli', path: candidate };
  }
  return { type: 'bin', path: 'npm' };
}

function normalizeNpmExecPath(value) {
  if (!value) return null;
  const resolved = path.resolve(value);
  if (path.basename(resolved) === 'npx-cli.js') {
    return path.join(path.dirname(resolved), 'npm-cli.js');
  }
  return resolved;
}

function npmCliCandidates(execPath = process.execPath) {
  const binDir = path.dirname(execPath);
  return [
    path.join(binDir, 'node_modules', 'npm', 'bin', 'npm-cli.js'),
    path.join(binDir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
    path.join(binDir, '..', 'libexec', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
  ].map((candidate) => path.resolve(candidate));
}

function npmChildEnv() {
  const nodeDir = path.dirname(process.execPath);
  return {
    ...process.env,
    PATH: [nodeDir, process.env.PATH || ''].filter(Boolean).join(path.delimiter),
    npm_config_update_notifier: 'false',
  };
}

function firstLine(value) {
  return String(value || '').split(/\r?\n/).find(Boolean) || 'unknown error';
}

function stampVersion(dir) {
  try {
    const ver = require('../package.json').version;
    const pkgPath = path.join(dir, 'package.json');
    let pkg = {};
    try { pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch {}
    pkg.version = ver;
    fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
  } catch {}
}

function saveWalleDir(dir) {
  const metaDir = path.join(process.env.HOME, '.walle');
  fs.mkdirSync(metaDir, { recursive: true });
  fs.writeFileSync(INSTALL_PATH_FILE, dir + '\n');
}

function findWalleDir() {
  const dir = findWalleDirSilent();
  if (dir) return dir;
  console.error(`\n  ${RED}Can't find Wall-E installation.${RESET}`);
  console.error(`  Run ${BOLD}npx create-walle install ./my-agent${RESET} first.\n`);
  process.exit(1);
}

function findWalleDirSilent() {
  if (process.env.WALLE_DIR && fs.existsSync(path.join(process.env.WALLE_DIR, 'claude-task-manager'))) {
    return process.env.WALLE_DIR;
  }
  if (fs.existsSync(path.join(process.cwd(), 'claude-task-manager', 'server.js'))) {
    return process.cwd();
  }
  if (fs.existsSync(INSTALL_PATH_FILE)) {
    const dir = fs.readFileSync(INSTALL_PATH_FILE, 'utf8').trim();
    if (fs.existsSync(path.join(dir, 'claude-task-manager', 'server.js'))) return dir;
  }
  return null;
}

function readPort(dir) {
  try {
    const env = fs.readFileSync(path.join(dir, '.env'), 'utf8');
    const m = env.match(/^CTM_PORT=(\d+)/m);
    if (m) return m[1];
  } catch {}
  return '3456';
}

function readWallePort(dir) {
  try {
    const env = fs.readFileSync(path.join(dir, '.env'), 'utf8');
    const m = env.match(/^WALL_E_PORT=(\d+)/m);
    if (m) return m[1];
  } catch {}
  return String(parseInt(readPort(dir)) + 1);
}

function detectName() {
  try { return execFileSync('git', ['config', 'user.name'], { encoding: 'utf8' }).trim(); } catch {}
  try { return execFileSync('id', ['-F'], { encoding: 'utf8' }).trim(); } catch {}
  return process.env.USER || 'Owner';
}

function detectTimezone() {
  try { return Intl.DateTimeFormat().resolvedOptions().timeZone; } catch {}
  return 'UTC';
}

function compileHotkeyDaemon(walleDir) {
  if (process.platform !== 'darwin') return; // macOS only
  const swiftSource = path.join(walleDir, 'claude-task-manager', 'bin', 'ctm-hotkey.swift');
  if (!fs.existsSync(swiftSource)) return;
  const binary = path.join(process.env.HOME, '.local', 'bin', 'ctm-hotkey');
  try {
    fs.mkdirSync(path.dirname(binary), { recursive: true });
    execFileSync('swiftc', ['-O', '-o', binary, swiftSource, '-framework', 'Cocoa'], { timeout: 120000, stdio: 'pipe' });
    fs.chmodSync(binary, 0o755);
    console.log(`  ${GREEN}Compiled hotkey daemon${RESET} ${DIM}(${binary})${RESET}`);
  } catch {
    // swiftc not available or compilation failed — non-fatal
    console.log(`  ${DIM}Skipped hotkey daemon (Swift compiler not available)${RESET}`);
  }
}

function copyRecursive(src, dest) {
  const stat = fs.statSync(src);
  if (stat.isDirectory()) {
    fs.mkdirSync(dest, { recursive: true });
    for (const entry of fs.readdirSync(src)) {
      if (entry === 'node_modules' || entry === '.git') continue;
      copyRecursive(path.join(src, entry), path.join(dest, entry));
    }
  } else {
    fs.copyFileSync(src, dest);
  }
}

module.exports = {
  checkNativeDependency,
  firstLine,
  main,
  nativeDependenciesForPackage,
  normalizeNpmExecPath,
  npmCliCandidates,
  repairNativeDependencies,
  resolveNpmRunner,
  writeCliLifecycleEvent,
};
