/**
 * Watchdog process that monitors 4r and cleans up Docker containers on ANY exit
 * Runs detached and polls the parent process, stopping containers when parent dies
 */
import { execSync, spawn } from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { resolveLocalGatewayShutdownPort, shutdownAutostartedGatewayVerified, } from './local-gateway-pid.js';
const WATCHDOG_PID_FILE = '4runr-watchdog.pid';
const PARENT_PID_FILE = '4runr-parent.pid';
function getWatchdogPidPath() {
    return path.join(os.tmpdir(), WATCHDOG_PID_FILE);
}
function getParentPidPath() {
    return path.join(os.tmpdir(), PARENT_PID_FILE);
}
/**
 * Check if a process is running
 */
function isProcessRunning(pid) {
    try {
        process.kill(pid, 0); // Signal 0 just checks if process exists
        return true;
    }
    catch {
        return false;
    }
}
/** Must match `container_name` in docker-compose.local.yml */
const COMPOSE_DB_CONTAINERS = ['4runr-postgres', '4runr-redis'];
/**
 * Stop Docker containers gracefully (keeps containers, just stops them — not `compose down`).
 *
 * Stacks are usually started from the Gateway bundle dir (`cwd` = .../apps/gateway), so Compose
 * project name is `gateway`. User-data `%APPDATA%/4runr/docker-compose.yml` uses project `4runr`,
 * so `docker compose -f ... stop` there often stops nothing. Always stop by fixed container names first.
 */
function stopContainers() {
    try {
        try {
            if (process.platform === 'win32') {
                execSync('docker exec 4runr-redis redis-cli SAVE 2>nul', {
                    stdio: 'pipe',
                    timeout: 5000,
                    windowsHide: true,
                });
            }
            else {
                execSync('docker exec 4runr-redis redis-cli SAVE 2>/dev/null || true', {
                    stdio: 'pipe',
                    timeout: 5000,
                });
            }
        }
        catch {
            // Redis might not be running — continue
        }
        const stopOpts = {
            stdio: 'pipe',
            timeout: 35000,
        };
        if (process.platform === 'win32')
            stopOpts.windowsHide = true;
        for (const name of COMPOSE_DB_CONTAINERS) {
            try {
                execSync(`docker stop -t 30 ${name}`, stopOpts);
            }
            catch {
                // Not running or already stopped
            }
        }
        const composeDir = process.platform === 'win32'
            ? path.join(process.env.APPDATA || '', '4runr')
            : path.join(process.env.HOME || '', '.4runr');
        const composePath = path.join(composeDir, 'docker-compose.yml');
        if (fs.existsSync(composePath)) {
            try {
                if (process.platform === 'win32') {
                    execSync(`docker compose -p 4runr -f "${composePath}" stop`, {
                        stdio: 'pipe',
                        timeout: 30000,
                        windowsHide: true,
                    });
                }
                else {
                    execSync(`docker compose -p 4runr -f "${composePath}" stop`, {
                        stdio: 'pipe',
                        timeout: 30000,
                    });
                }
            }
            catch {
                // Best-effort after explicit container stops
            }
        }
    }
    catch {
        // Docker unavailable
    }
}
/**
 * Stop the local Gateway Node process and Postgres/Redis containers (volumes preserved).
 * Used on TUI disconnect from localhost and on full `4r` exit via `stopWatchdog()`.
 */
export function shutdownLocalGatewayStack() {
    killGateway();
    stopContainers();
    process.stderr.write('[4Runr] Local Gateway stopped; Postgres + Redis containers stop attempted (volumes preserved).\n');
}
/**
 * Kill any autostarted Gateway process (Windows: full process tree + listener fallback).
 * Prints verification lines to stderr so you can trust cleanup without running netstat.
 */
function killGateway() {
    const port = resolveLocalGatewayShutdownPort();
    const r = shutdownAutostartedGatewayVerified(port);
    for (const line of r.lines) {
        process.stderr.write(`[4Runr] ${line}\n`);
    }
}
/**
 * Start watchdog process (detached, survives parent exit)
 */
export function startWatchdog(parentPid) {
    // Kill any existing watchdog
    const existingWatchdogPid = getWatchdogPidPath();
    if (fs.existsSync(existingWatchdogPid)) {
        try {
            const pid = parseInt(fs.readFileSync(existingWatchdogPid, 'utf-8').trim(), 10);
            if (pid > 0 && isProcessRunning(pid)) {
                process.kill(pid, 'SIGTERM');
            }
        }
        catch {
            // ignore
        }
    }
    // Write parent PID for watchdog to monitor
    fs.writeFileSync(getParentPidPath(), String(parentPid), 'utf-8');
    // Spawn detached watchdog process
    const watchdogScript = `
const fs = require('fs');
const os = require('os');
const path = require('path');
const { execSync } = require('child_process');

const parentPidPath = path.join(os.tmpdir(), '${PARENT_PID_FILE}');
const watchdogPidPath = path.join(os.tmpdir(), '${WATCHDOG_PID_FILE}');

// Write our own PID
fs.writeFileSync(watchdogPidPath, String(process.pid), 'utf-8');

function isRunning(pid) {
  try { process.kill(pid, 0); return true; } catch { return false; }
}

function cleanup() {
  try {
    // Kill Gateway (Windows: taskkill /T — npm-spawn leaves child Node alive after SIGTERM)
    const gwPid = path.join(os.tmpdir(), '4runr-os-local-gateway.pid');
    if (fs.existsSync(gwPid)) {
      try {
        const pid = parseInt(fs.readFileSync(gwPid, 'utf-8').trim(), 10);
        if (pid > 0 && isRunning(pid)) {
          if (process.platform === 'win32') {
            try { execSync('taskkill /PID ' + pid + ' /T /F', { stdio: 'pipe', timeout: 20000, windowsHide: true }); } catch {}
          } else {
            try { process.kill(pid, 'SIGTERM'); } catch {}
          }
        }
      } catch {}
      try { fs.unlinkSync(gwPid); } catch {}
    }
    
    try {
      execSync('docker exec 4runr-redis redis-cli SAVE', { stdio: 'pipe', timeout: 5000, windowsHide: process.platform === 'win32' });
    } catch {}
    try {
      execSync('docker stop -t 30 4runr-postgres', { stdio: 'pipe', timeout: 35000, windowsHide: process.platform === 'win32' });
    } catch {}
    try {
      execSync('docker stop -t 30 4runr-redis', { stdio: 'pipe', timeout: 35000, windowsHide: process.platform === 'win32' });
    } catch {}
    try {
      const composeDir = process.platform === 'win32'
        ? path.join(process.env.APPDATA || '', '4runr')
        : path.join(process.env.HOME || '', '.4runr');
      const composePath = path.join(composeDir, 'docker-compose.yml');
      if (fs.existsSync(composePath)) {
        execSync(\`docker compose -p 4runr -f "\${composePath}" stop\`, { stdio: 'pipe', timeout: 30000, windowsHide: process.platform === 'win32' });
      }
    } catch {}
  } catch {}
  
  // Cleanup PID files
  try { fs.unlinkSync(parentPidPath); } catch {}
  try { fs.unlinkSync(watchdogPidPath); } catch {}
  process.exit(0);
}

// Monitor parent process
const parentPid = parseInt(fs.readFileSync(parentPidPath, 'utf-8').trim(), 10);
const interval = setInterval(() => {
  if (!isRunning(parentPid)) {
    clearInterval(interval);
    cleanup();
  }
}, 1000); // Check every second

// Cleanup on watchdog exit signals
process.on('SIGTERM', cleanup);
process.on('SIGINT', cleanup);
`;
    const child = spawn(process.execPath, ['-e', watchdogScript], {
        detached: true,
        stdio: 'ignore',
        windowsHide: true,
    });
    child.unref(); // Allow parent to exit independently
}
/**
 * Stop watchdog and cleanup (called on graceful shutdown)
 */
export function stopWatchdog() {
    const watchdogPidPath = getWatchdogPidPath();
    if (fs.existsSync(watchdogPidPath)) {
        try {
            const pid = parseInt(fs.readFileSync(watchdogPidPath, 'utf-8').trim(), 10);
            if (pid > 0 && isProcessRunning(pid)) {
                process.kill(pid, 'SIGTERM');
            }
        }
        catch {
            // ignore
        }
    }
    // Manual cleanup since watchdog is being stopped gracefully
    shutdownLocalGatewayStack();
    // Cleanup PID files
    try {
        fs.unlinkSync(watchdogPidPath);
    }
    catch {
        // ignore
    }
    try {
        fs.unlinkSync(getParentPidPath());
    }
    catch {
        // ignore
    }
}
//# sourceMappingURL=watchdog.js.map