/**
 * Dashboard MCP Tools
 *
 * Tools for opening and checking the status of the bundled dashboard,
 * license server, and MCP HTTP server.
 *
 * CRITICAL: NEVER use console.log() - stdout is reserved for JSON-RPC protocol.
 *
 * @module tools/dashboard
 */
import { z } from 'zod';
import { execFile } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { platform } from 'node:os';
import { formatResponse, handleError } from './shared.js';
const DASHBOARD_URL = process.env.DASHBOARD_URL || `http://localhost:${process.env.PORT || '3367'}`;
const LICENSE_SERVER_URL = process.env.OCR_LICENSE_SERVER || 'http://localhost:3000';
const MCP_HTTP_URL = process.env.MCP_SERVER_URL || `http://localhost:${process.env.MCP_HTTP_PORT || '3366'}`;
/**
 * Check if a service is reachable via HTTP GET with timeout.
 */
async function checkService(url, timeoutMs = 3000) {
    try {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), timeoutMs);
        const resp = await fetch(url, { signal: controller.signal, redirect: 'manual' });
        clearTimeout(timer);
        const healthy = resp.status >= 200 && resp.status < 400;
        return { reachable: true, status: resp.status, healthy };
    }
    catch {
        return { reachable: false, healthy: false };
    }
}
/**
 * Detect if running inside WSL (Windows Subsystem for Linux).
 */
function isWSL() {
    try {
        const release = readFileSync('/proc/version', 'utf8');
        return /microsoft|wsl/i.test(release);
    }
    catch {
        return false;
    }
}
/**
 * Open a localhost URL in the user's default browser.
 *
 * Uses a 5-second timeout so the MCP tool never hangs — some openers
 * (notably xdg-open on certain DEs) block until the browser exits.
 * On WSL, prefers `wslview` (wslu) or falls back to `cmd.exe /c start`.
 */
export function openInBrowser(url) {
    return new Promise((resolve) => {
        const os = platform();
        // Guard against openers that block until the browser exits.
        const timeout = setTimeout(() => {
            resolve({ opened: true });
        }, 5000);
        const callback = (err) => {
            clearTimeout(timeout);
            if (err) {
                resolve({ opened: false, error: err.message });
            }
            else {
                resolve({ opened: true });
            }
        };
        let child;
        if (os === 'darwin') {
            child = execFile('open', [url], callback);
        }
        else if (os === 'win32') {
            child = execFile('cmd.exe', ['/c', 'start', '', url], callback);
        }
        else if (isWSL()) {
            // WSL: wslview (from wslu) is best; fall back to cmd.exe
            child = execFile('wslview', [url], (err) => {
                if (err) {
                    child = execFile('cmd.exe', ['/c', 'start', '', url], callback);
                    child.unref();
                }
                else {
                    clearTimeout(timeout);
                    resolve({ opened: true });
                }
            });
        }
        else {
            child = execFile('xdg-open', [url], callback);
        }
        // Detach so we don't hold the child process open.
        child.unref();
    });
}
// ═══════════════════════════════════════════════════════════════════════════════
// TOOL HANDLERS
// ═══════════════════════════════════════════════════════════════════════════════
async function handleDashboardOpen(params) {
    try {
        const path = typeof params.path === 'string' ? params.path : '';
        // Validate and build URL — only allow http/https localhost
        const base = DASHBOARD_URL;
        const cleanPath = path.startsWith('/') ? path : (path ? `/${path}` : '');
        const url = `${base}${cleanPath}`;
        // Validate the constructed URL
        const parsed = new URL(url);
        if (parsed.hostname !== 'localhost' && parsed.hostname !== '127.0.0.1') {
            return formatResponse({ opened: false, url, message: 'Only localhost URLs are allowed.' });
        }
        if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
            return formatResponse({ opened: false, url, message: 'Only http/https protocols are allowed.' });
        }
        // Pre-flight: verify dashboard and MCP HTTP server are healthy before opening browser
        const [dashHealth, mcpHealth] = await Promise.all([
            checkService(`${DASHBOARD_URL}/`, 5000),
            checkService(`${MCP_HTTP_URL}/health`, 5000),
        ]);
        if (!dashHealth.healthy && !mcpHealth.healthy) {
            return formatResponse({
                opened: false,
                url,
                message: 'Dashboard and MCP HTTP server are not running. ' +
                    'Start the Docker container first: ocr-provenance-mcp start',
                services: {
                    dashboard: { url: DASHBOARD_URL, status: 'unreachable' },
                    mcp_http: { url: MCP_HTTP_URL, status: 'unreachable' },
                },
                fix: 'Run "ocr-provenance-mcp start" or "docker compose up -d" to start all services.',
            });
        }
        if (!dashHealth.healthy) {
            return formatResponse({
                opened: false,
                url,
                message: `Dashboard is not healthy at ${DASHBOARD_URL}. ` +
                    'The MCP HTTP server is running but the dashboard service may not be started.',
                services: {
                    dashboard: { url: DASHBOARD_URL, status: dashHealth.reachable ? `unhealthy (${dashHealth.status})` : 'unreachable' },
                    mcp_http: { url: MCP_HTTP_URL, status: 'ok' },
                },
                fix: 'Restart the Docker container: ocr-provenance-mcp restart',
            });
        }
        if (!mcpHealth.healthy) {
            // Dashboard is up but MCP HTTP server is down — dashboard will show "MCP unreachable"
            // Still open the browser so the user sees the dashboard error page with diagnostics
            console.error(`[dashboard] WARNING: MCP HTTP server not healthy at ${MCP_HTTP_URL}. ` +
                'Dashboard will show limited functionality.');
        }
        const result = await openInBrowser(url);
        if (result.opened) {
            return formatResponse({
                opened: true,
                url,
                message: `Dashboard opened in your default browser at ${url}`,
                services: {
                    dashboard: { url: DASHBOARD_URL, status: dashHealth.healthy ? 'ok' : 'unreachable' },
                    mcp_http: { url: MCP_HTTP_URL, status: mcpHealth.healthy ? 'ok' : 'unreachable' },
                },
            });
        }
        return formatResponse({
            opened: false,
            url,
            message: `Could not open browser automatically. Please open this URL manually: ${url}`,
            error: result.error,
        });
    }
    catch (error) {
        return handleError(error);
    }
}
async function handleDashboardStatus() {
    try {
        const dashUrl = `${DASHBOARD_URL}/`;
        // Strip any path suffix (e.g., /v1) from license server URL for health check
        const licenseBase = LICENSE_SERVER_URL.replace(/\/v1\/?$/, '');
        const licenseUrl = `${licenseBase}/health`;
        const mcpUrl = `${MCP_HTTP_URL}/health`;
        const [dash, license, mcp] = await Promise.all([
            checkService(dashUrl),
            checkService(licenseUrl),
            checkService(mcpUrl),
        ]);
        const services = [
            {
                name: 'Dashboard',
                url: DASHBOARD_URL,
                reachable: dash.reachable,
                healthy: dash.healthy,
                status: dash.status ?? null,
            },
            {
                name: 'License Server',
                url: LICENSE_SERVER_URL,
                reachable: license.reachable,
                healthy: license.healthy,
                status: license.status ?? null,
            },
            {
                name: 'MCP HTTP Server',
                url: MCP_HTTP_URL,
                reachable: mcp.reachable,
                healthy: mcp.healthy,
                status: mcp.status ?? null,
            },
        ];
        const allHealthy = services.every((s) => s.healthy);
        // Determine mode based on what's healthy
        let mode;
        if (dash.healthy && license.healthy && mcp.healthy) {
            mode = 'full';
        }
        else if (mcp.healthy && license.healthy) {
            mode = 'mcp+billing';
        }
        else if (mcp.healthy) {
            mode = 'mcp-only';
        }
        else {
            mode = 'unknown';
        }
        return formatResponse({
            services,
            all_healthy: allHealthy,
            mode,
        });
    }
    catch (error) {
        return handleError(error);
    }
}
// ═══════════════════════════════════════════════════════════════════════════════
// TOOL DEFINITIONS
// ═══════════════════════════════════════════════════════════════════════════════
export const dashboardTools = {
    ocr_dashboard_open: {
        description: 'Open the OCR Provenance dashboard in the user\'s default browser. ' +
            'Supports an optional path parameter for deep-linking (e.g., "/dashboard/account"). ' +
            'Returns the URL if the browser cannot be opened automatically.',
        inputSchema: {
            path: z
                .string()
                .optional()
                .describe('Optional path to append to the dashboard URL (e.g., "/dashboard/account")'),
        },
        handler: handleDashboardOpen,
    },
    ocr_dashboard_status: {
        description: 'Check the reachability and health of all bundled services: Dashboard (port 3367), ' +
            'License Server (port 3000), and MCP HTTP Server (port 3366). ' +
            'Returns each service\'s status and the current operating mode (full/mcp+billing/mcp-only).',
        inputSchema: {},
        handler: handleDashboardStatus,
    },
};
//# sourceMappingURL=dashboard.js.map