/**
 * @license
 * Copyright 2025 HopCode
 * SPDX-License-Identifier: Apache-2.0
 */
import process from 'node:process';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import os from 'node:os';
import { getNpmVersion, getGitVersion } from './systemInfo.js';
import { validateAuthMethod } from '../config/auth.js';
import { canUseRipgrep, getMCPServerStatus, MCPServerStatus, } from '@hoptrendy/hopcode-core';
import { t } from '../i18n/index.js';
import { loadGitHubToken } from './githubTokenStore.js';
const MIN_NODE_MAJOR = 20;
function checkNodeVersion() {
    const version = process.version;
    const major = parseInt(version.replace(/^v/, '').split('.')[0], 10);
    if (isNaN(major) || major < MIN_NODE_MAJOR) {
        return {
            category: t('System'),
            name: t('Node.js version'),
            status: 'fail',
            message: version,
            detail: t('Node.js v{{min}}+ is required. Current: {{version}}', {
                min: String(MIN_NODE_MAJOR),
                version,
            }),
        };
    }
    return {
        category: t('System'),
        name: t('Node.js version'),
        status: 'pass',
        message: version,
    };
}
async function checkNpmVersion() {
    const version = await getNpmVersion();
    if (version === 'unknown') {
        return {
            category: t('System'),
            name: t('npm version'),
            status: 'warn',
            message: t('not found'),
            detail: t('npm is not available. Some features may not work.'),
        };
    }
    return {
        category: t('System'),
        name: t('npm version'),
        status: 'pass',
        message: version,
    };
}
function checkPlatform() {
    return {
        category: t('System'),
        name: t('Platform'),
        status: 'pass',
        message: `${process.platform}/${process.arch} (${os.release()})`,
    };
}
function checkAuth(context) {
    const authType = context.services.config?.getAuthType();
    if (!authType) {
        return {
            category: t('Authentication'),
            name: t('API key'),
            status: 'fail',
            message: t('not configured'),
            detail: t('Run /auth to configure authentication.'),
        };
    }
    const error = validateAuthMethod(authType, context.services.config ?? undefined);
    if (error) {
        return {
            category: t('Authentication'),
            name: t('API key'),
            status: 'fail',
            message: t('invalid ({{authType}})', { authType }),
            detail: error,
        };
    }
    return {
        category: t('Authentication'),
        name: t('API key'),
        status: 'pass',
        message: t('configured ({{authType}})', { authType }),
    };
}
async function checkApiClient(context) {
    const config = context.services.config;
    if (!config) {
        return {
            category: t('Authentication'),
            name: t('API client'),
            status: 'fail',
            message: t('config not loaded'),
        };
    }
    try {
        const client = config.getHopCodeClient();
        if (client.isInitialized()) {
            return {
                category: t('Authentication'),
                name: t('API client'),
                status: 'pass',
                message: t('client initialized'),
            };
        }
        return {
            category: t('Authentication'),
            name: t('API client'),
            status: 'warn',
            message: t('client not initialized'),
            detail: t('The API client has not been initialized yet.'),
        };
    }
    catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        return {
            category: t('Authentication'),
            name: t('API client'),
            status: 'warn',
            message: t('error'),
            detail: errorMsg,
        };
    }
}
function checkSettings(context) {
    const settings = context.services.settings;
    if (!settings) {
        return {
            category: t('Configuration'),
            name: t('Settings'),
            status: 'fail',
            message: t('not loaded'),
            detail: t('Settings could not be loaded. Check your settings files for syntax errors.'),
        };
    }
    return {
        category: t('Configuration'),
        name: t('Settings'),
        status: 'pass',
        message: t('loaded'),
    };
}
function checkModel(context) {
    const model = context.services.config?.getModel();
    if (!model) {
        return {
            category: t('Configuration'),
            name: t('Model'),
            status: 'fail',
            message: t('not configured'),
            detail: t('Run /model to select a model.'),
        };
    }
    return {
        category: t('Configuration'),
        name: t('Model'),
        status: 'pass',
        message: model,
    };
}
function checkMcpServers(context) {
    const config = context.services.config;
    const servers = config?.getMcpServers();
    if (!servers || Object.keys(servers).length === 0) {
        return [
            {
                category: t('MCP Servers'),
                name: t('MCP servers'),
                status: 'pass',
                message: t('none configured'),
            },
        ];
    }
    // In non-interactive mode MCP connections are never established, so querying
    // getMCPServerStatus would always return DISCONNECTED and produce false failures.
    // Report configured servers as unchecked instead.
    if (context.executionMode !== 'interactive') {
        return Object.keys(servers).map((name) => ({
            category: t('MCP Servers'),
            name,
            status: 'pass',
            message: config?.isMcpServerDisabled(name)
                ? t('disabled')
                : t('configured (not checked in non-interactive mode)'),
        }));
    }
    return Object.keys(servers).map((name) => {
        // Skip disabled servers — report as informational pass
        if (config?.isMcpServerDisabled(name)) {
            return {
                category: t('MCP Servers'),
                name,
                status: 'pass',
                message: t('disabled'),
            };
        }
        const status = getMCPServerStatus(name);
        switch (status) {
            case MCPServerStatus.CONNECTED:
                return {
                    category: t('MCP Servers'),
                    name,
                    status: 'pass',
                    message: t('connected'),
                };
            case MCPServerStatus.CONNECTING:
                return {
                    category: t('MCP Servers'),
                    name,
                    status: 'warn',
                    message: t('connecting'),
                    detail: t('Server is still starting up.'),
                };
            case MCPServerStatus.DISCONNECTED:
            default:
                return {
                    category: t('MCP Servers'),
                    name,
                    status: 'fail',
                    message: t('disconnected'),
                    detail: t('Check that the server process is running and configuration is correct.'),
                };
        }
    });
}
function checkToolRegistry(context) {
    const registry = context.services.config?.getToolRegistry();
    if (!registry) {
        return {
            category: t('Tools'),
            name: t('Tool registry'),
            status: 'fail',
            message: t('not loaded'),
        };
    }
    const count = registry.getAllTools().length;
    return {
        category: t('Tools'),
        name: t('Tool registry'),
        status: 'pass',
        message: t('{{count}} tools registered', { count: String(count) }),
    };
}
async function checkRipgrep(context) {
    try {
        const useBuiltin = context.services.config?.getUseBuiltinRipgrep() ?? false;
        const result = await canUseRipgrep(useBuiltin);
        if (result) {
            return {
                category: t('Tools'),
                name: t('Ripgrep'),
                status: 'pass',
                message: t('available'),
            };
        }
        return {
            category: t('Tools'),
            name: t('Ripgrep'),
            status: 'warn',
            message: t('not available'),
            detail: t('Install ripgrep for faster file search: https://github.com/BurntSushi/ripgrep'),
        };
    }
    catch {
        return {
            category: t('Tools'),
            name: t('Ripgrep'),
            status: 'warn',
            message: t('check failed'),
        };
    }
}
const execFileAsync = promisify(execFile);
const NPM_PACKAGE_NAME = '@hoptrendy/hopcode-cli';
async function checkNpmCli() {
    try {
        const { stdout } = await execFileAsync('npm', [
            'view',
            NPM_PACKAGE_NAME,
            'version',
            '--json',
        ]);
        const published = stdout.trim().replace(/^"|"$/g, '');
        return {
            category: t('HopCode'),
            name: t('NPM CLI package'),
            status: 'pass',
            message: t('published v{{version}}', { version: published }),
        };
    }
    catch {
        return {
            category: t('HopCode'),
            name: t('NPM CLI package'),
            status: 'warn',
            message: t('not found on npm'),
            detail: t('{{pkg}} has not been published to NPM yet.', {
                pkg: NPM_PACKAGE_NAME,
            }),
        };
    }
}
async function checkGit(context) {
    if (context.services.git) {
        return {
            category: t('Git'),
            name: t('Git'),
            status: 'pass',
            message: t('available'),
        };
    }
    // services.git is undefined in non-interactive mode — probe the binary directly
    const version = await getGitVersion();
    if (version === 'unknown') {
        return {
            category: t('Git'),
            name: t('Git'),
            status: 'warn',
            message: t('not available'),
            detail: t('Git features will be limited.'),
        };
    }
    return {
        category: t('Git'),
        name: t('Git'),
        status: 'pass',
        message: version,
    };
}
function checkGithubToken() {
    const stored = loadGitHubToken();
    const token = stored?.accessToken ?? process.env['GITHUB_TOKEN'];
    if (token) {
        return {
            category: t('HopCode'),
            name: t('GitHub token'),
            status: 'pass',
            message: t('configured'),
        };
    }
    return {
        category: t('HopCode'),
        name: t('GitHub token'),
        status: 'warn',
        message: t('not configured'),
        detail: t('Set GITHUB_TOKEN env var or run: hopcode github auth. Required for /ci command.'),
    };
}
/**
 * Check that the configured web search provider endpoint is reachable.
 */
async function checkProviderReachability(context) {
    const config = context.services.config;
    const webSearchConfig = config?.getWebSearchConfig?.();
    if (!webSearchConfig || webSearchConfig.provider.length === 0) {
        return {
            category: t('Configuration'),
            name: t('Web search provider'),
            status: 'warn',
            message: t('not configured'),
            detail: t('No web search providers are configured. DuckDuckGo (free) is used as fallback.'),
        };
    }
    // Try to ping the first non-duckduckgo provider; DDG has no /v1/models endpoint
    const providerEndpoints = {
        tavily: 'https://api.tavily.com',
        exa: 'https://api.exa.ai',
        bing: 'https://api.bing.microsoft.com',
        jina: 'https://s.jina.ai',
        firecrawl: 'https://api.firecrawl.dev',
        google: 'https://www.googleapis.com',
        dashscope: 'https://dashscope.aliyuncs.com',
    };
    const configuredTypes = webSearchConfig.provider
        .map((p) => p.type)
        .filter((t) => t !== 'duckduckgo');
    if (configuredTypes.length === 0) {
        return {
            category: t('Configuration'),
            name: t('Web search provider'),
            status: 'pass',
            message: t('DuckDuckGo (free fallback, always available)'),
        };
    }
    const providerType = configuredTypes[0];
    const baseUrl = providerEndpoints[providerType];
    if (!baseUrl) {
        return {
            category: t('Configuration'),
            name: t('Web search provider'),
            status: 'pass',
            message: t('configured ({{provider}})', { provider: providerType }),
        };
    }
    try {
        const start = Date.now();
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), 5000);
        const response = await fetch(baseUrl, {
            method: 'HEAD',
            signal: controller.signal,
        }).catch(() => fetch(baseUrl, { method: 'GET', signal: controller.signal }));
        clearTimeout(timeout);
        const latency = Date.now() - start;
        if (response.status === 401 || response.status === 403) {
            // Endpoint reachable, but bad/missing API key — still a pass for reachability
            return {
                category: t('Configuration'),
                name: t('Web search provider'),
                status: 'pass',
                message: t('{{provider}} reachable ({{latency}}ms) — check API key', {
                    provider: providerType,
                    latency: String(latency),
                }),
            };
        }
        return {
            category: t('Configuration'),
            name: t('Web search provider'),
            status: 'pass',
            message: t('{{provider}} reachable ({{latency}}ms)', {
                provider: providerType,
                latency: String(latency),
            }),
        };
    }
    catch (error) {
        const msg = error instanceof Error ? error.message : String(error);
        return {
            category: t('Configuration'),
            name: t('Web search provider'),
            status: 'warn',
            message: t('{{provider}} unreachable', { provider: providerType }),
            detail: msg,
        };
    }
}
export async function runDoctorChecks(context) {
    // Run async checks in parallel
    const [npmResult, ripgrepResult, apiClientResult, gitResult, npmCliResult, providerResult,] = await Promise.all([
        checkNpmVersion(),
        checkRipgrep(context),
        checkApiClient(context),
        checkGit(context),
        checkNpmCli(),
        checkProviderReachability(context),
    ]);
    return [
        // System
        checkNodeVersion(),
        npmResult,
        checkPlatform(),
        // Authentication
        checkAuth(context),
        apiClientResult,
        // Configuration
        checkSettings(context),
        checkModel(context),
        // MCP Servers
        ...checkMcpServers(context),
        // Tools
        checkToolRegistry(context),
        ripgrepResult,
        // Web Search
        providerResult,
        // Git
        gitResult,
        // HopCode
        npmCliResult,
        checkGithubToken(),
    ];
}
//# sourceMappingURL=doctorChecks.js.map