// engine/detectors/otel-detector.ts — SPEC-044
// Detects OpenTelemetry installation, backend, SLOs, and whether OTel is needed.
import { join } from 'node:path';
import { glob } from 'glob';
import { readFileOrNull, readPackageDeps, detectNodeOtel, detectPythonOtel, detectGoOtel, detectRustOtel, detectJavaOtel, MULTISERVICE_DEPS, } from './otel-ecosystem-detectors.js';
/** Env var pattern indicating an OTLP exporter is configured */
const OTEL_EXPORTER_ENV_VARS = [
    'OTEL_EXPORTER_OTLP_ENDPOINT',
    'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT',
    'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT',
    'OTEL_EXPORTER_JAEGER_ENDPOINT',
    'JAEGER_ENDPOINT',
    'HONEYCOMB_API_KEY',
];
/** Detect telemetry backend from OTEL_EXPORTER_OTLP_ENDPOINT value or known env var names */
function detectBackendFromContent(content) {
    if (content.includes('api.honeycomb.io')) {
        return 'honeycomb';
    }
    if (content.includes('tempo') || content.includes('grafana-agent')) {
        return 'grafana-tempo';
    }
    if (content.includes('datadog') || content.includes('DD_')) {
        return 'datadog';
    }
    if (content.includes('newrelic') || content.includes('NEW_RELIC')) {
        return 'newrelic';
    }
    if (content.includes('4317') || content.includes('4318') || content.includes('jaeger')) {
        return 'jaeger';
    }
    return undefined;
}
/** Detect telemetry backend by scanning config files and .env files */
async function detectBackend(projectPath) {
    const envFiles = [
        '.env',
        '.env.example',
        '.env.local',
        'docker-compose.yml',
        'docker-compose.yaml',
    ];
    for (const envFile of envFiles) {
        const content = await readFileOrNull(join(projectPath, envFile));
        if (!content) {
            continue;
        }
        const hasOtelVar = OTEL_EXPORTER_ENV_VARS.some((v) => content.includes(v));
        if (hasOtelVar) {
            const backend = detectBackendFromContent(content);
            if (backend) {
                return backend;
            }
        }
    }
    return undefined;
}
/** Detect SLO documents by scanning the project for SLO-related files */
async function detectSLOs(projectPath) {
    const sloFiles = ['slo.yaml', 'slo.yml', 'sli.yaml', 'sli.yml'];
    for (const f of sloFiles) {
        for (const prefix of ['', 'docs/', 'config/', '.github/']) {
            const content = await readFileOrNull(join(projectPath, prefix, f));
            if (content !== null) {
                return true;
            }
        }
    }
    // Check alerts/rules files
    for (const f of ['alerts.yaml', 'alerts.yml', 'rules.yaml', 'rules.yml']) {
        const content = await readFileOrNull(join(projectPath, f));
        if (content !== null) {
            return true;
        }
    }
    // Scan source files for SLO comments
    try {
        const srcFiles = await glob('**/*.{ts,js,go,py,java,kt}', {
            cwd: projectPath,
            nodir: true,
            maxDepth: 3,
            ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**', 'vendor/**'],
        });
        for (const f of srcFiles.slice(0, 30)) {
            const content = await readFileOrNull(join(projectPath, f));
            if (content && (content.includes('// SLO:') || content.includes('# SLO:'))) {
                return true;
            }
        }
    }
    catch {
        // ignore
    }
    return false;
}
/** Detect whether a Node.js project uses ConsoleSpanExporter */
async function detectConsoleExporter(projectPath) {
    try {
        const files = await glob('**/*.{ts,js}', {
            cwd: projectPath,
            nodir: true,
            maxDepth: 4,
            ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**'],
        });
        for (const f of files) {
            const content = await readFileOrNull(join(projectPath, f));
            if (content?.includes('ConsoleSpanExporter')) {
                return true;
            }
        }
    }
    catch {
        // ignore
    }
    return false;
}
/** Detect whether OTEL_SERVICE_NAME is set to a meaningful (non-generic) value */
async function detectServiceNameConfigured(projectPath) {
    const genericNames = ['app', 'server', 'service', 'my-service', 'myservice'];
    const envFiles = ['.env', '.env.example', '.env.local'];
    for (const envFile of envFiles) {
        const content = await readFileOrNull(join(projectPath, envFile));
        if (!content) {
            continue;
        }
        const match = /OTEL_SERVICE_NAME\s*=\s*(.+)/.exec(content);
        if (match) {
            const value = match[1]?.trim().replace(/["']/g, '') ?? '';
            if (value.length > 0 && !genericNames.includes(value.toLowerCase())) {
                return true;
            }
        }
    }
    return false;
}
/** Detect if the project needs OTel (microservice patterns without OTel) */
async function detectNeedsOtel(projectPath, isInstalled, hasMultipleServices) {
    if (isInstalled) {
        return false;
    }
    if (hasMultipleServices) {
        return true;
    }
    // Check for HTTP clients or async workers in package.json
    const pkg = await readPackageDeps(projectPath);
    if (pkg) {
        const all = { ...pkg.deps, ...pkg.devDeps };
        if (MULTISERVICE_DEPS.some((dep) => dep in all)) {
            return true;
        }
    }
    return false;
}
/** Determine which signals are configured based on detected packages and env vars */
async function detectSignals(projectPath, isInstalled) {
    if (!isInstalled) {
        return [];
    }
    const signals = ['traces']; // traces is always included when OTel is installed
    // Check for metrics and logs packages
    const pkg = await readPackageDeps(projectPath);
    if (pkg) {
        const all = { ...pkg.deps, ...pkg.devDeps };
        if ('@opentelemetry/sdk-metrics' in all || '@opentelemetry/sdk-metrics-base' in all) {
            signals.push('metrics');
        }
        if ('@opentelemetry/sdk-logs' in all || '@opentelemetry/winston-transport' in all) {
            signals.push('logs');
        }
    }
    // Check Python requirements for metrics/logs
    const reqContent = await readFileOrNull(join(projectPath, 'requirements.txt'));
    if (reqContent) {
        if (reqContent.includes('opentelemetry-sdk-metrics') ||
            reqContent.includes('opentelemetry-metrics')) {
            if (!signals.includes('metrics')) {
                signals.push('metrics');
            }
        }
    }
    return signals;
}
/**
 * Detect the installed SDK language (the primary language of the OTel SDK used).
 */
function detectSdkLanguage(nodeInstalled, pythonInstalled, goInstalled, rustInstalled, javaInstalled) {
    if (nodeInstalled) {
        return 'javascript';
    }
    if (pythonInstalled) {
        return 'python';
    }
    if (goInstalled) {
        return 'go';
    }
    if (javaInstalled) {
        return 'java';
    }
    if (rustInstalled) {
        return 'rust';
    }
    return null;
}
/**
 * Detect OpenTelemetry presence and configuration in a project.
 * Returns a complete OtelDetectionResult.
 */
export async function detectOtel(projectPath) {
    const [nodeInstalled, pythonInstalled, goInstalled, rustInstalled, javaInstalled] = await Promise.all([
        detectNodeOtel(projectPath),
        detectPythonOtel(projectPath),
        detectGoOtel(projectPath),
        detectRustOtel(projectPath),
        detectJavaOtel(projectPath),
    ]);
    const isInstalled = nodeInstalled || pythonInstalled || goInstalled || rustInstalled || javaInstalled;
    const [signals, backend, hasSLOs, hasConsoleExporter, serviceNameConfigured, needsOtel] = await Promise.all([
        detectSignals(projectPath, isInstalled),
        detectBackend(projectPath),
        detectSLOs(projectPath),
        isInstalled ? detectConsoleExporter(projectPath) : Promise.resolve(false),
        isInstalled ? detectServiceNameConfigured(projectPath) : Promise.resolve(false),
        detectNeedsOtel(projectPath, isInstalled, false),
    ]);
    const sdkLanguage = detectSdkLanguage(nodeInstalled, pythonInstalled, goInstalled, rustInstalled, javaInstalled);
    const result = {
        isInstalled,
        signals,
        hasSLOs,
        serviceNameConfigured,
        hasConsoleExporter,
        needsOtel,
        sdkLanguage,
    };
    if (backend !== undefined) {
        result.backend = backend;
    }
    return result;
}
//# sourceMappingURL=otel-detector.js.map