import { execSync, spawn, spawnSync } from 'child_process';
import { askYesNo } from './ask.js';
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { BRIDGE_LOG_FILE, BRIDGE_PID_FILE, CURSORCONNECT_DIR, SUPERVISOR_ENV_FILE, SUPERVISOR_PID_FILE, } from './paths.js';
import { resolveBridgeDir } from './bridge-dir.js';
import { ensureBridgeBuilt } from './bridge-build.js';
import { t } from './i18n.js';
const CONNECT_DIST = dirname(fileURLToPath(import.meta.url));
export function isBridgeRunning() {
    if (!existsSync(BRIDGE_PID_FILE))
        return false;
    const pid = parseInt(readFileSync(BRIDGE_PID_FILE, 'utf-8').trim(), 10);
    if (!Number.isFinite(pid))
        return false;
    try {
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
export function stopBridgeProcess() {
    if (!existsSync(BRIDGE_PID_FILE))
        return;
    const pid = parseInt(readFileSync(BRIDGE_PID_FILE, 'utf-8').trim(), 10);
    try {
        process.kill(pid, 'SIGTERM');
    }
    catch {
        /* already dead */
    }
    try {
        writeFileSync(BRIDGE_PID_FILE, '');
    }
    catch {
        /* ignore */
    }
}
export function stopSupervisor() {
    if (!existsSync(SUPERVISOR_PID_FILE))
        return;
    const pid = parseInt(readFileSync(SUPERVISOR_PID_FILE, 'utf-8').trim(), 10);
    try {
        process.kill(pid, 'SIGTERM');
    }
    catch {
        /* already dead */
    }
    try {
        writeFileSync(SUPERVISOR_PID_FILE, '');
    }
    catch {
        /* ignore */
    }
}
/** Останавливает bridge и фоновый CDP-supervisor (`cursorconnect stop`). */
export function stopConnect() {
    stopSupervisor();
    stopBridgeProcess();
}
/** @deprecated alias */
export function stopBridge() {
    stopBridgeProcess();
}
export function startSupervisor(env, serviceArgv = []) {
    stopSupervisor();
    mkdirSync(CURSORCONNECT_DIR, { recursive: true, mode: 0o700 });
    writeFileSync(SUPERVISOR_ENV_FILE, JSON.stringify({ env, argv: serviceArgv }), { encoding: 'utf-8', mode: 0o600 });
    const child = spawn('node', [join(CONNECT_DIST, 'cdp-supervisor.js')], {
        detached: true,
        stdio: 'ignore',
        env: process.env,
    });
    child.unref();
    writeFileSync(SUPERVISOR_PID_FILE, String(child.pid));
}
function killProcessesOnBridgePort() {
    try {
        execSync('lsof -t -i:3847 2>/dev/null | xargs kill -TERM 2>/dev/null', {
            stdio: 'ignore',
            shell: '/bin/bash',
        });
        /* give old bridge time to exit */
        execSync('sleep 0.5', { stdio: 'ignore', shell: '/bin/bash' });
    }
    catch {
        /* port free */
    }
}
export function startBridge(env) {
    stopBridgeProcess();
    killProcessesOnBridgePort();
    const bridgeDir = resolveBridgeDir();
    ensureBridgeBuilt(bridgeDir);
    if (!process.env.CURSORCONNECT_BRIDGE_DIR?.trim()) {
        process.env.CURSORCONNECT_BRIDGE_DIR = bridgeDir;
    }
    const logFd = openLogAppend();
    const child = spawn('node', ['dist/index.js'], {
        cwd: bridgeDir,
        env: { ...process.env, ...env },
        detached: true,
        stdio: ['ignore', logFd, logFd],
    });
    child.unref();
    try {
        closeSync(logFd);
    }
    catch {
        /* ignore */
    }
    writeFileSync(BRIDGE_PID_FILE, String(child.pid));
    return child;
}
function openLogAppend() {
    return openSync(BRIDGE_LOG_FILE, 'a');
}
export async function waitBridgeHealth(maxMs = 30_000, opts) {
    const requireCdp = opts?.requireCdp === true;
    const deadline = Date.now() + maxMs;
    while (Date.now() < deadline) {
        try {
            const res = await fetch('http://127.0.0.1:3847/health');
            if (res.ok) {
                const j = (await res.json());
                const ok = Boolean(j.ok);
                const cdp = Boolean(j.cdp);
                if (ok && (!requireCdp || cdp))
                    return { ok, cdp };
            }
        }
        catch {
            /* retry */
        }
        await sleep(500);
    }
    try {
        const res = await fetch('http://127.0.0.1:3847/health');
        if (res.ok) {
            const j = (await res.json());
            return { ok: Boolean(j.ok), cdp: Boolean(j.cdp) };
        }
    }
    catch {
        /* ignore */
    }
    return { ok: false, cdp: false };
}
export async function waitRelayConnector(relayUrl, maxMs = 45_000) {
    const base = relayUrl.replace(/\/$/, '');
    const deadline = Date.now() + maxMs;
    while (Date.now() < deadline) {
        try {
            const res = await fetch(`${base}/health`);
            if (res.ok) {
                const h = (await res.json());
                if (h.connector)
                    return true;
            }
        }
        catch {
            /* retry */
        }
        await sleep(800);
    }
    return false;
}
/** Mac в relay-комнате, код на сервере, токен зарегистрирован (иначе app получит 404). */
export async function waitRelayPairingReady(relayUrl, roomId, maxMs = 45_000, clientToken) {
    const base = relayUrl.replace(/\/$/, '');
    const deadline = Date.now() + maxMs;
    let lastConnector = false;
    let lastCodes = 0;
    let lastTokens = 0;
    let lastTokenRegistered = false;
    while (Date.now() < deadline) {
        try {
            const q = new URLSearchParams({ roomId });
            if (clientToken)
                q.set('token', clientToken);
            const res = await fetch(`${base}/api/diagnostics?${q}`, {
                signal: AbortSignal.timeout(8_000),
            });
            if (res.ok) {
                const d = (await res.json());
                lastConnector = Boolean(d.connectorInRoom);
                lastCodes = d.pairingCodesActive ?? 0;
                lastTokens = d.tokensRegistered ?? 0;
                lastTokenRegistered = d.tokenRegistered === true;
                const tokenOk = clientToken ? lastTokenRegistered : lastTokens > 0;
                if (lastConnector && lastCodes > 0 && tokenOk) {
                    return {
                        ok: true,
                        connectorInRoom: true,
                        pairingCodesActive: lastCodes,
                        tokensRegistered: lastTokens,
                        tokenRegistered: lastTokenRegistered,
                    };
                }
            }
        }
        catch {
            /* retry */
        }
        await sleep(800);
    }
    return {
        ok: false,
        connectorInRoom: lastConnector,
        pairingCodesActive: lastCodes,
        tokensRegistered: lastTokens,
        tokenRegistered: lastTokenRegistered,
    };
}
export function isCursorRunning() {
    try {
        execSync('pgrep -x Cursor', { stdio: 'ignore' });
        return true;
    }
    catch {
        return false;
    }
}
export async function quitCursor(maxWaitMs = 20_000) {
    if (!isCursorRunning())
        return true;
    console.log(t('cdp.quitCursor'));
    spawnSync('osascript', ['-e', 'tell application "Cursor" to quit'], {
        stdio: 'ignore',
    });
    const deadline = Date.now() + maxWaitMs;
    while (Date.now() < deadline) {
        await sleep(400);
        if (!isCursorRunning())
            return true;
    }
    console.log(t('cdp.forceQuitCursor'));
    try {
        spawnSync('killall', ['Cursor'], { stdio: 'ignore' });
    }
    catch {
        /* ignore */
    }
    await sleep(800);
    return !isCursorRunning();
}
const CDP_LAUNCH_CMD = 'open -a Cursor --args --remote-debugging-port=9222';
export function printManualCdpInstructions() {
    console.log(`\n${t('cdp.manualTitle')}`);
    console.log(`  ${CDP_LAUNCH_CMD}`);
    console.log(`  ${t('cdp.manualQuitFirst')}\n`);
}
export function launchCursorWithCdp() {
    spawn('open', ['-a', 'Cursor', '--args', '--remote-debugging-port=9222'], {
        detached: true,
        stdio: 'ignore',
    }).unref();
}
async function waitCdp(cdpUrl, attempts = 40) {
    for (let i = 0; i < attempts; i++) {
        if (await cdpReachable(cdpUrl))
            return true;
        await sleep(500);
    }
    return false;
}
async function shouldRestartCursor(opts) {
    if (opts.forceRestart === true)
        return true;
    if (opts.noRestartCursor !== true)
        return true;
    if (opts.restartCursor === true)
        return true;
    if (process.stdin.isTTY) {
        return askYesNo(t('cdp.askRestart'));
    }
    return false;
}
export async function ensureCursorCdp(opts = {}) {
    const cdpUrl = opts.cdpUrl ?? 'http://127.0.0.1:9222';
    const forceRestart = opts.forceRestart === true;
    if (!forceRestart && (await cdpReachable(cdpUrl))) {
        return true;
    }
    const running = isCursorRunning();
    if (!running) {
        console.log(t('cdp.notRunningLaunch'));
        launchCursorWithCdp();
        if (await waitCdp(cdpUrl, 50))
            return true;
        printManualCdpInstructions();
        return false;
    }
    // Cursor запущен без CDP (или forceRestart) — перезапуск с --remote-debugging-port=9222
    const shouldRestart = await shouldRestartCursor(opts);
    if (!shouldRestart) {
        printManualCdpInstructions();
        return false;
    }
    console.log(t('cdp.restartingWithCdp'));
    const quitOk = await quitCursor();
    if (!quitOk) {
        console.warn(t('cdp.quitFailed'));
        printManualCdpInstructions();
        return false;
    }
    launchCursorWithCdp();
    if (await waitCdp(cdpUrl, 50))
        return true;
    printManualCdpInstructions();
    return false;
}
async function waitBridgeCdpConnected(waitMs) {
    const h = await waitBridgeHealth(waitMs, { requireCdp: true });
    return h.ok && h.cdp;
}
/**
 * CDP обязателен: порт 9222 + bridge health.cdp.
 * При сбое — перезапуск Cursor (и bridge через syncBridge).
 */
export async function ensureConnectCdpReady(opts = {}) {
    const waitBridgeMs = opts.waitBridgeMs ?? 30_000;
    const runAttempt = async (forceRestart) => {
        if (!(await ensureCursorCdp({ ...opts, forceRestart }))) {
            return false;
        }
        if (!opts.syncBridge) {
            return true;
        }
        await opts.syncBridge();
        return waitBridgeCdpConnected(waitBridgeMs);
    };
    if (await runAttempt(false)) {
        return true;
    }
    const autoRecover = opts.noRestartCursor !== true || opts.restartCursor === true;
    if (!autoRecover) {
        if (process.stdin.isTTY) {
            const yes = await askYesNo(t('cdp.askRestart'));
            if (yes && (await runAttempt(true))) {
                return true;
            }
        }
        printManualCdpInstructions();
        return false;
    }
    console.error(t('cdp.bridgeNotConnected'));
    console.log(t('cdp.restartingWithCdp'));
    if (await runAttempt(true)) {
        return true;
    }
    if (process.stdin.isTTY) {
        const yes = await askYesNo(t('cdp.askRestartAgain'));
        if (yes && (await runAttempt(true))) {
            return true;
        }
    }
    printManualCdpInstructions();
    return false;
}
async function cdpReachable(cdpUrl) {
    try {
        const res = await fetch(`${cdpUrl.replace(/\/$/, '')}/json/version`);
        return res.ok;
    }
    catch {
        return false;
    }
}
function sleep(ms) {
    return new Promise((r) => setTimeout(r, ms));
}
