/**
 * Gravity Bridge — VS Code Extension (SDK Edition)
 * 
 * Uses antigravity-sdk for:
 * - Real-time step/conversation monitoring via EventMonitor
 * - Full conversation content via LSBridge.getConversation()
 * - Message sending via CascadeManager.sendPrompt()
 * - Accept/Reject via CascadeManager.acceptStep()/rejectStep()
 * 
 * Communication with Discord via file-based bridge protocol.
 */

import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as cp from 'child_process';
import { WSBridgeClient, WSResponseData, WSCommandData } from './ws-client';
import { initStepProbe, BridgeContext, writePendingApproval, tryApprovalStrategies, writeRegistration, getApprovalContext, resetPendingState, resetPendingStateForReconnect, handleDiffReviewResponse, getActiveSessionId as getStepProbeSessionId, getStepProbeContext } from './step-probe';
import { startHttpBridge, getDeterministicPort, HttpBridgeContext } from './http-bridge';
import { setupApprovalObserver } from './html-patcher';
import { watchCommandsDir, handleWSCommand, disposeCommandsWatcher, CommandHandlerContext } from './command-handler';

// ─── File-based logging (AI can read directly) ───
function logToFile(msg: string) {
    const ts = new Date().toISOString().replace('T', ' ').substring(0, 19);
    // Include projectName prefix so shared log can distinguish which extension instance logged
    const prefix = projectName ? `[${projectName}]` : '';
    const line = `${ts} ${prefix} ${msg}`;
    console.log(`Gravity Bridge: ${prefix} ${msg}`);
    try {
        if (!bridgePath) return;
        const logFile = path.join(bridgePath, 'extension.log');
        // Log rotation: truncate when >10MB, keep last 2MB
        try {
            const stat = fs.statSync(logFile);
            if (stat.size > 10 * 1024 * 1024) {
                const content = fs.readFileSync(logFile, 'utf-8');
                fs.writeFileSync(logFile, content.slice(-2 * 1024 * 1024), 'utf-8');
            }
        } catch { /* file doesn't exist yet */ }
        fs.appendFileSync(logFile, line + '\n', 'utf-8');
    } catch (e: any) {
        console.error(`Gravity Bridge LOG WRITE FAIL: ${e.message}`);
    }
}

// antigravity-sdk embedded locally (src/sdk/)
let AntigravitySDK: any;
let sdk: any;

let statusBar: vscode.StatusBarItem;
let bridgePath: string;
let projectName: string;
let workspaceUri: string = '';  // filesystem path of the workspace folder (for session filtering)
let isActive = false;
let autoApproveEnabled = false;  // toggled via !auto from Discord
let watcher: fs.FSWatcher | null = null;
let wsBridge: WSBridgeClient | null = null;  // WebSocket Hub connection


// ─── Project Detection ───

function detectProjectName(): string {
    const config = vscode.workspace.getConfiguration('gravityBridge');
    const configName = config.get<string>('projectName');
    if (configName) { return configName; }

    const folders = vscode.workspace.workspaceFolders;
    if (folders && folders.length > 0) {
        const cwd = folders[0].uri.fsPath;
        try {
            const gitConfigPath = path.join(cwd, '.git', 'config');
            if (fs.existsSync(gitConfigPath)) {
                const configContent = fs.readFileSync(gitConfigPath, 'utf8');
                const match = configContent.match(/url\s*=\s*.*\/([^\/]+?)(?:\.git)?$/m);
                if (match && match[1]) {
                    return match[1].toLowerCase().replace(/[\s\-]+/g, '_');
                }
            }
        } catch { }
        return path.basename(cwd).toLowerCase().replace(/[\s\-]+/g, '_');
    }
    return 'default';
}

// ─── Bridge File I/O ───



// Module-level activeSessionId so writeChatSnapshot can register sessions lazily
let activeSessionId = '';
let activeTrajectoryId = '';
// Track recently sent Discord→AG texts to avoid echo relay
const recentDiscordSentTexts: Map<string, number> = new Map();

function writeChatSnapshot(text: string) {
    try {
        if (wsBridge && wsBridge.isConnected()) {
            wsBridge.sendChat({
                content: text,
                conversation_id: getStepProbeSessionId(),
                project_name: projectName,
            });
            logToFile(`[SNAPSHOT-WS] sent (${text.length} chars)`);
            if (getStepProbeSessionId()) { writeRegistration(getStepProbeSessionId()); }
        }
    } catch (e: any) {
        console.log(`Gravity Bridge: snapshot write error: ${e.message}`);
    }
}

function writeChatSnapshotWithFiles(text: string, files: Array<{ name: string, content: string }>) {
    try {
        if (wsBridge && wsBridge.isConnected()) {
            wsBridge.sendChat({
                content: text,
                attached_files: files,
                conversation_id: getStepProbeSessionId(),
                project_name: projectName,
            });
            logToFile(`[SNAPSHOT-WS] sent with ${files.length} files (${text.length} chars)`);
            if (getStepProbeSessionId()) { writeRegistration(getStepProbeSessionId()); }
        }
    } catch (e: any) {
        console.log(`Gravity Bridge: snapshot+files write error: ${e.message}`);
    }
}


// ─── Command handling extracted to ./command-handler.ts ───

// ─── SDK Integration ───

async function initSDK(context: vscode.ExtensionContext): Promise<boolean> {
    try {
        const sdkModule = require('./sdk/index');
        AntigravitySDK = sdkModule.AntigravitySDK;
    } catch (err: any) {
        console.log(`Gravity Bridge: antigravity-sdk load failed: ${err.message}`);
        return false;
    }

    try {
        sdk = new AntigravitySDK(context);
        await sdk.initialize();
        console.log('Gravity Bridge: ✅ SDK initialized');

        // ── FIX: SDK's _findLSProcess() uses case-sensitive String.includes() ──
        // workspace_id in LS process has 'Desktop' (capital D), but SDK hint
        // generates 'desktop' (lowercase) → match fails → connects to WRONG LS.
        // Re-discover the correct LS using case-insensitive workspace_id matching.
        await fixLSConnection();

        return true;
    } catch (err: any) {
        console.log(`Gravity Bridge: SDK init failed: ${err.message}`);
        return false;
    }
}

/**
 * Fix SDK's LS connection by finding the correct language_server process
 * for this workspace using case-insensitive matching.
 *
 * SDK bug: _findLSProcess() compares workspaceHint via JS String.includes()
 * which is case-sensitive. workspace_id in process args has original casing
 * (e.g., file_c_3A_Users_Certes_Desktop_variet_agent) but SDK hint is
 * lowercased (desktop_variet_agent) → no match → falls back to first LS
 * found (wrong workspace).
 */
export async function fixLSConnection(): Promise<boolean> {
    if (!sdk?.ls) { logToFile('[LS-FIX] skipped: sdk.ls not available'); return false; }
    try {
        const folders = vscode.workspace.workspaceFolders;
        if (!folders || folders.length === 0) { logToFile('[LS-FIX] skipped: no workspace folders'); return false; }

        // Generate the workspace hint the same way SDK does, but we'll match case-insensitively
        const folder = folders[0].uri.fsPath;
        const parts = folder.replace(/\\/g, '/').split('/');
        const hint = parts.slice(-2).join('_').replace(/[-.\s]/g, '_').toLowerCase();

        if (!hint) { logToFile('[LS-FIX] skipped: empty hint'); return false; }

        // Find all language_server processes with csrf_token
        const { exec } = cp;
        const { promisify } = require('util');
        const execAsync = promisify(exec);

        let output: string;
        try {
            const psScript = `Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'language_server' -and $_.CommandLine -match 'csrf_token' } | ForEach-Object { $_.ProcessId.ToString() + '|' + $_.CommandLine }`;
            const encoded = Buffer.from(psScript, 'utf16le').toString('base64');
            const result = await execAsync(
                `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand ${encoded}`,
                { encoding: 'utf8', timeout: 5000, windowsHide: true }
            );
            output = result.stdout;
        } catch (psErr: any) {
            logToFile(`[LS-FIX] skipped: PowerShell failed — ${psErr.message?.substring(0, 100)}`);
            return false;
        }

        const lines = output.split('\n').filter((l: string) => l.trim().length > 0);
        if (lines.length === 0) { logToFile('[LS-FIX] skipped: no LS processes found'); return false; }
        // NOTE: Do NOT skip on single LS — SDK may have fallen back to wrong LS
        // due to case-sensitive hint mismatch, even when only one process exists.
        logToFile(`[LS-FIX] found ${lines.length} LS process(es), hint="${hint}"`);

        // Find the line whose workspace_id matches our workspace (case-insensitive)
        let matchedLine: string | null = null;
        let fallbackLine: string | null = null; // v15: LS without workspace_id (AG restart)
        for (const line of lines) {
            // Match workspace_id arg against our hint
            const wsMatch = line.match(/--workspace_id[= ](\S+)/i);
            if (wsMatch) {
                const wsid = wsMatch[1].toLowerCase();
                if (wsid.includes(hint)) {
                    matchedLine = line;
                    break;
                }
            } else {
                // v15: LS without --workspace_id (new AG main LS after restart)
                // Skip --enable_lsp processes (secondary/old LSP instances)
                if (!line.includes('--enable_lsp') && !fallbackLine) {
                    fallbackLine = line;
                    logToFile(`[LS-FIX] found fallback LS (no workspace_id): PID=${line.split('|')[0]?.trim()}`);
                }
            }
        }

        if (!matchedLine) {
            if (fallbackLine) {
                // v15: Use workspace_id-less LS as fallback (common after AG restart)
                logToFile(`[LS-FIX] No workspace_id match — using fallback LS`);
                matchedLine = fallbackLine;
            } else {
                logToFile(`[LS-FIX] No LS process matched hint="${hint}" (${lines.length} processes)`);
                return false;
            }
        }

        // Extract port and csrf_token from matched line
        const csrfMatch = matchedLine.match(/--csrf_token[= ](\S+)/);
        const extPortMatch = matchedLine.match(/--extension_server_port[= ](\d+)/);
        const pidMatch = matchedLine.split('|')[0]?.trim();

        if (!csrfMatch || !extPortMatch) {
            logToFile(`[LS-FIX] Matched LS but missing csrf/port args`);
            return false;
        }

        const csrfToken = csrfMatch[1];
        const extPort = parseInt(extPortMatch[1], 10);
        const pid = parseInt(pidMatch || '0', 10);

        // Check if SDK already connected to this LS
        if (sdk.ls.port === extPort) {
            logToFile(`[LS-FIX] SDK already on correct LS port=${extPort}`);
            return false;
        }

        // Find ConnectRPC port via netstat (same as SDK logic)
        let netstatOutput: string;
        try {
            const result = await execAsync(
                `netstat -aon`,
                { encoding: 'utf8', timeout: 4000, windowsHide: true }
            );
            netstatOutput = result.stdout.split('\n')
                .filter((l: string) => l.includes('LISTENING') && l.includes(pid.toString()))
                .join('\n');
        } catch (err: any) {
            // Netstat failed — try extension_server_port as fallback
            logToFile(`[LS-FIX] netstat failed: ${err.message.substring(0, 80)}, using ext_port=${extPort} for PID=${pid}`);
            sdk.ls.setConnection(extPort, csrfToken, false);
            logToFile(`[LS-FIX] ✅ Reconnected to correct LS: port=${extPort} hint="${hint}" PID=${pid}`);
            return true;
        }

        const portMatches = netstatOutput.matchAll(/127\.0\.0\.1:(\d+)/g);
        const ports: number[] = [];
        for (const m of portMatches) {
            const p = parseInt(m[1], 10);
            if (p !== extPort && !ports.includes(p)) {
                ports.push(p);
            }
        }

        // Try each port — prefer HTTPS, fall back to HTTP
        const httpModule = require('http');
        const httpsModule = require('https');

        for (const useTls of [true, false]) {
            const mod = useTls ? httpsModule : httpModule;
            const proto = useTls ? 'https' : 'http';
            for (const port of ports) {
                try {
                    const ok = await new Promise<boolean>((resolve) => {
                        const req = mod.request(
                            `${proto}://127.0.0.1:${port}/exa.language_server_pb.LanguageServerService/GetUserStatus`,
                            {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json', 'Content-Length': 2 },
                                rejectUnauthorized: false,
                                timeout: 2000,
                            },
                            (res: any) => resolve(res.statusCode === 200 || res.statusCode === 401)
                        );
                        req.on('error', () => resolve(false));
                        req.on('timeout', () => { req.destroy(); resolve(false); });
                        req.write('{}');
                        req.end();
                    });

                    if (ok) {
                        sdk.ls.setConnection(port, csrfToken, useTls);
                        logToFile(`[LS-FIX] ✅ Reconnected to correct LS: port=${port} ${proto} hint="${hint}" PID=${pid}`);
                        return true;
                    }
                } catch { /* try next */ }
            }
        }

        // Last resort: use extension_server_port
        sdk.ls.setConnection(extPort, csrfToken, false);
        logToFile(`[LS-FIX] ✅ Reconnected via ext_port=${extPort} hint="${hint}" PID=${pid}`);
        return true;
    } catch (err: any) {
        logToFile(`[LS-FIX] error: ${err.message}`);
        return false;
    }
}

// ─── Approval Observer + Product.json Checksums extracted to ./html-patcher.ts ───

// ─── HTTP Bridge Server extracted to ./http-bridge.ts ───

// ─── Step Probe, Response Watcher, Approval Strategies → extracted to ./step-probe.ts ───



export async function activate(context: vscode.ExtensionContext) {
    console.log('Gravity Bridge: activating...');

    // Project detection
    projectName = detectProjectName();
    // Store workspace folder path for session filtering (prevents cross-window session grabbing)
    const folders = vscode.workspace.workspaceFolders;
    workspaceUri = folders && folders.length > 0 ? folders[0].uri.fsPath : '';
    console.log(`Gravity Bridge: project "${projectName}" workspace="${workspaceUri}"`);

    // Bridge path
    const config = vscode.workspace.getConfiguration('gravityBridge');
    const configPath = config.get<string>('bridgePath');
    bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge');

    console.log(`Gravity Bridge: bridge path: ${bridgePath}`);

    // ── WebSocket Hub Connection ──
    const hubUrl = process.env.GRAVITY_HUB_URL || config.get<string>('hubUrl') || '';
    const regCode = process.env.GRAVITY_REGISTRATION_CODE || config.get<string>('registrationCode') || '';
    const pcName = os.hostname();
    if (hubUrl) {
        wsBridge = new WSBridgeClient(hubUrl, regCode, projectName, pcName, {
            onResponse: (data: WSResponseData) => {
                logToFile(`[WS-RESPONSE] ${data.request_id?.substring(0, 12)} approved=${data.approved} step_type=${data.step_type || '(none)'}`);
                const approved = data.approved ?? true;
                const stepType = data.step_type || '';

                // ── diff_review: Accept all / Reject all (REGRESSION FIX) ──
                // Previously only handled in processResponseFile (file-bridge path).
                // WS path was missing this logic entirely, causing Accept All to fail.
                if (stepType === 'diff_review') {
                    logToFile(`[WS-RESPONSE] diff_review detected — routing to handleDiffReviewResponse`);
                    handleDiffReviewResponse({
                        request_id: data.request_id,
                        approved,
                        button_index: data.button_index,
                        step_type: stepType,
                    })
                        .then(result => {
                            logToFile(`[WS-RESPONSE] diff_review result: ${result}`);
                            resetPendingState();
                        })
                        .catch(err => logToFile(`[WS-RESPONSE] diff_review error: ${err.message}`));
                    return;
                }

                // Normal approval — tryApprovalStrategies
                // v22: ALSO write response file so Observer's pollResponseGroup can click
                // the correct button (with exact button_index). Without this, only the
                // imprecise pollTriggerClick fallback was used for WS-path responses.
                const responseDir = path.join(bridgePath, 'response');
                if (!fs.existsSync(responseDir)) {
                    fs.mkdirSync(responseDir, { recursive: true });
                }
                const respPayload = {
                    request_id: data.request_id,
                    approved,
                    button_index: data.button_index ?? 0,
                    step_type: stepType,
                    project_name: projectName,
                    _from_ws: true,
                };
                fs.writeFileSync(
                    path.join(responseDir, `${data.request_id}.json`),
                    JSON.stringify(respPayload),
                    'utf-8'
                );
                logToFile(`[WS-RESPONSE] Response file written for pollResponseGroup: ${data.request_id?.substring(0, 12)}`);

                const approvalCtx = getApprovalContext();
                logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
                tryApprovalStrategies(approved, approvalCtx.sessionId, stepType, approvalCtx.stepIndex)
                    .then(result => {
                        logToFile(`[WS-RESPONSE] Approval result: ${result}`);
                        resetPendingState();
                    })
                    .catch(err => logToFile(`[WS-RESPONSE] Approval error: ${err.message}`));
            },
            onCommand: (data: WSCommandData) => {
                logToFile(`[WS-CMD] ${data.text?.substring(0, 50)}`);
                handleWSCommand({
                    bridgePath, projectName, sdk, ls: sdk?.ls, autoApproveEnabled, logToFile,
                    onAutoApproveChanged: (enabled: boolean) => { autoApproveEnabled = enabled; },
                    recentDiscordSentTexts,
                    getActiveSessionId: () => getStepProbeSessionId(),
                }, data);
            },
            onInstanceUpdate: (count, instances) => {
                logToFile(`[WS-INSTANCE] ${count} active instances`);
            },
            onConnected: (connId, instanceNum, token) => {
                logToFile(`[WS] Connected: ${connId} instance=#${instanceNum}`);
                statusBar.text = '$(check) Bridge WS';
                statusBar.tooltip = `Gravity Bridge: ${projectName} (WS #${instanceNum})`;
                // Reset step-probe state so WAITING steps are re-detected after reconnect
                resetPendingStateForReconnect();
            },
            onDisconnected: () => {
                logToFile('[WS] Disconnected — using file fallback');
                statusBar.text = '$(warning) Bridge (WS ↓)';
            },
            onError: (err) => {
                logToFile(`[WS-ERR] ${err}`);
            },
        }, logToFile);
        wsBridge.connect();
        logToFile(`[WS] Hub connection initiated: ${hubUrl}`);
    } else {
        logToFile('[WS] No GRAVITY_HUB_URL — WebSocket disabled, using file bridge only');
    }

    // ── Multi-project: no lock file, each project uses project_name-based filtering ──
    // (active_project.lock removed — was blocking concurrent multi-project usage)
    logToFile(`[INIT] project="${projectName}" pid=${process.pid} — multi-project mode (no lock)`);

    // Status bar
    statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
    statusBar.text = '$(sync~spin) Bridge';
    statusBar.tooltip = `Gravity Bridge: ${projectName}`;
    statusBar.show();
    context.subscriptions.push(statusBar);

    // Initialize SDK
    const sdkReady = await initSDK(context);

    if (sdkReady) {
        // ── Command Discovery Diagnostic ──
        // Enumerate ALL antigravity.* commands to find correct approval command names
        try {
            const allCmds = await vscode.commands.getCommands(true);
            const agCmds = allCmds.filter((c: string) => c.startsWith('antigravity.'));
            logToFile(`[CMD-DISCOVERY] Total antigravity.* commands: ${agCmds.length}`);
            // Log approval-related commands specifically
            const approvalKeywords = ['accept', 'reject', 'approve', 'terminal', 'agent', 'cascade', 'step', 'run', 'command.'];
            const relevantCmds = agCmds.filter((c: string) =>
                approvalKeywords.some(kw => c.toLowerCase().includes(kw))
            );
            logToFile(`[CMD-DISCOVERY] Approval-related commands (${relevantCmds.length}):`);
            for (const cmd of relevantCmds) {
                logToFile(`[CMD-DISCOVERY]   → ${cmd}`);
            }
            // Also dump ALL commands for full reference
            logToFile(`[CMD-DISCOVERY] ALL antigravity.* commands:`);
            for (const cmd of agCmds) {
                logToFile(`[CMD-DISCOVERY]   ${cmd}`);
            }
        } catch (e: any) {
            logToFile(`[CMD-DISCOVERY] error: ${e.message}`);
        }

        // Initialize step probe (polling + response watcher)
        initStepProbe({
            bridgePath,
            projectName,
            sdk,
            wsBridge,
            activeSessionId,
            sessionStalled: false,
            lastPendingStepIndex: -1,
            stallProbed: false,
            sawRunningAfterPending: true,
            setClickTrigger: (action: 'approve' | 'reject') => {
                const { setClickTrigger: setTrigger } = require('./http-bridge');
                setTrigger(action);
            },
            logToFile,
            workspaceUri,
            diffReviewMetadata: new Map(),
            recentDiscordSentTexts,
            writeChatSnapshot,
            writeChatSnapshotWithFiles,
            fixLSConnection,
        } as BridgeContext);
        // Start HTTP bridge with live step-probe state (prevents stale primitive bug)
        const httpBridgeCtx: HttpBridgeContext = {
            bridgePath, projectName, wsBridge, logToFile,
            get activeSessionId() { return getStepProbeContext().activeSessionId; },
            get sessionStalled() { return getStepProbeContext().sessionStalled; },
            get lastPendingStepIndex() { return getStepProbeContext().lastPendingStepIndex; },
            writeChatSnapshot,
        };
        const bridgePort = await startHttpBridge(httpBridgeCtx, sdk);
        let localPort = bridgePort;
        if (bridgePort) {
            try {
                const externalUri = await vscode.env.asExternalUri(vscode.Uri.parse(`http://127.0.0.1:${bridgePort}`));
                const match = externalUri.authority.match(/:(\d+)$/);
                if (match) { localPort = parseInt(match[1], 10); }
            } catch (e: any) {
                logToFile(`[OBSERVER] asExternalUri failed: ${e.message}`);
            }
            await setupApprovalObserver(sdk, localPort, logToFile);
        } else {
            logToFile('[OBSERVER] HTTP bridge failed — skipping observer setup');
        }
        statusBar.text = '$(check) Bridge';
        statusBar.tooltip = `Gravity Bridge Control | port:${localPort} | project:${projectName}`;

        // Register SDK-powered commands
        context.subscriptions.push(
            vscode.commands.registerCommand('gravityBridge.approve', async () => {
                try {
                    await sdk.cascade.acceptStep();
                    vscode.window.showInformationMessage('Gravity Bridge: Step approved');
                } catch (e: any) {
                    vscode.window.showErrorMessage(`Approve failed: ${e.message}`);
                }
            }),
            vscode.commands.registerCommand('gravityBridge.reject', async () => {
                try {
                    await sdk.cascade.rejectStep();
                    vscode.window.showInformationMessage('Gravity Bridge: Step rejected');
                } catch (e: any) {
                    vscode.window.showErrorMessage(`Reject failed: ${e.message}`);
                }
            })
        );
    } else {
        statusBar.text = '$(warning) Bridge (no SDK)';
        console.log('Gravity Bridge: SDK not available, file-based mode only');
    }

    // Watch commands directory
    watchCommandsDir({
        bridgePath, projectName, sdk, ls: sdk?.ls, autoApproveEnabled, logToFile,
        onAutoApproveChanged: (enabled: boolean) => { autoApproveEnabled = enabled; },
        recentDiscordSentTexts,
        getActiveSessionId: () => getStepProbeSessionId(),
    });

    // Response watcher is now initialized by initStepProbe() above
    // Register basic commands
    context.subscriptions.push(
        vscode.commands.registerCommand('gravityBridge.start', () => {
            isActive = true;
            statusBar.text = sdkReady ? '$(check) Bridge SDK' : '$(sync~spin) Bridge';
            vscode.window.showInformationMessage(`Gravity Bridge started for "${projectName}"`);
        }),
        vscode.commands.registerCommand('gravityBridge.stop', () => {
            isActive = false;
            // SDK monitor is disabled, no need to stop
            statusBar.text = '$(circle-slash) Bridge OFF';
            vscode.window.showInformationMessage('Gravity Bridge stopped');
        }),
        vscode.commands.registerCommand('gravityBridge.connect', async () => {
            if (!sdk) {
                vscode.window.showErrorMessage('SDK not initialized');
                return;
            }
            try {
                const sessions = await sdk.cascade.getSessions();
                const items = sessions.map((s: any) => ({
                    label: s.title || 'Untitled',
                    description: `step ${s.stepCount} • ${s.id?.substring(0, 8)}`,
                    sessionId: s.id,
                }));
                const pick = await vscode.window.showQuickPick(items, {
                    placeHolder: 'Select a conversation to connect'
                });
                if (pick) {
                    await sdk.cascade.focusSession((pick as any).sessionId);
                    vscode.window.showInformationMessage(`Connected to: ${(pick as any).label}`);
                }
            } catch (e: any) {
                vscode.window.showErrorMessage(`Connect failed: ${e.message}`);
            }
        })
    );

    // Cleanup
    context.subscriptions.push({
        dispose: () => {
            if (sdk) { try { sdk.dispose(); } catch { } }
            if (watcher) { watcher.close(); }
            disposeCommandsWatcher();
        }
    });

    console.log('Gravity Bridge: ✅ activated');
    isActive = true;
}

export function deactivate() {
    // Disconnect WebSocket
    if (wsBridge) {
        wsBridge.disconnect();
        wsBridge = null;
    }
    // Clean up stale lock file if it exists (legacy cleanup)
    try {
        const lockFile = path.join(bridgePath, 'active_project.lock');
        if (fs.existsSync(lockFile)) {
            const lockData = JSON.parse(fs.readFileSync(lockFile, 'utf-8'));
            if (lockData.pid === process.pid) {
                fs.unlinkSync(lockFile);
            }
        }
    } catch { }
    if (sdk) {
        try { sdk.dispose(); } catch { }
    }
}
