/**
 * owlcoda init — interactive config setup wizard.
 * Creates config.json from template with optional model auto-detection.
 */
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { dirname } from 'node:path';
import { get as httpGet } from 'node:http';
import { getOwlcodaConfigPath } from './paths.js';
function httpGetJson(url, timeoutMs = 5000) {
    return new Promise((resolve, reject) => {
        const req = httpGet(url, { timeout: timeoutMs }, res => {
            const chunks = [];
            res.on('data', (c) => chunks.push(c));
            res.on('end', () => {
                try {
                    resolve(JSON.parse(Buffer.concat(chunks).toString()));
                }
                catch {
                    reject(new Error('Invalid JSON response'));
                }
            });
        });
        req.on('error', reject);
        req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
    });
}
async function detectRouterModels(routerUrl) {
    try {
        const data = await httpGetJson(`${routerUrl}/v1/models`);
        if (data?.data && Array.isArray(data.data)) {
            return data.data.map((m) => m.id).filter(Boolean);
        }
    }
    catch {
        // Router not available — not an error for init
    }
    return [];
}
/**
 * Default candidate backends, probed in order when the user does not pass
 * --router. First responder wins. Covers the three OpenAI-compatible local
 * runtimes most users actually run.
 *
 * The legacy :8009 router is intentionally not probed here — `owlcoda doctor`
 * downgrades it to `deprecated_router_models`, so auto-selecting it would put
 * fresh users on the deprecated path on their very first run.
 */
const DEFAULT_ROUTER_CANDIDATES = [
    { url: 'http://127.0.0.1:11434/v1', label: 'Ollama' },
    { url: 'http://127.0.0.1:1234/v1', label: 'LM Studio' },
    { url: 'http://127.0.0.1:8000/v1', label: 'vLLM' },
];
async function probeDefaultRouters() {
    for (const candidate of DEFAULT_ROUTER_CANDIDATES) {
        const models = await detectRouterModels(candidate.url);
        if (models.length > 0) {
            return { routerUrl: candidate.url, routerLabel: candidate.label, models };
        }
    }
    // Nothing answered — keep Ollama as the most-likely default the user will
    // install next, and let buildConfigFromModels fall back to a placeholder.
    return {
        routerUrl: DEFAULT_ROUTER_CANDIDATES[0].url,
        routerLabel: DEFAULT_ROUTER_CANDIDATES[0].label,
        models: [],
    };
}
function inferTier(modelId, index, total) {
    const id = modelId.toLowerCase();
    // Size-based heuristics
    if (id.includes('70b') || id.includes('72b') || id.includes('65b') || id.includes('large'))
        return 'heavy';
    if (id.includes('7b') || id.includes('8b') || id.includes('3b') || id.includes('small') || id.includes('mini'))
        return 'fast';
    // Position-based fallback
    if (total >= 3) {
        if (index === 0)
            return 'balanced';
        if (index === total - 1)
            return 'fast';
        return 'heavy';
    }
    return 'balanced';
}
function isEmbeddingShaped(modelId) {
    const id = modelId.toLowerCase();
    return id.includes('embedding') || id.includes('embed') || id.includes('rerank') || id.includes('bge-');
}
function buildConfigFromModels(models, routerUrl, port) {
    // Filter out embedding / rerank models from the chat model list entirely.
    // They cannot generate chat responses, and surfacing them in the /model
    // picker only causes new users to pick one and get a cryptic failure on
    // their first prompt. If the router exposes ONLY embedding models we keep
    // them as a last resort so the user at least sees what was discovered.
    const chatCapable = models.filter(id => !isEmbeddingShaped(id));
    const usableModels = chatCapable.length > 0 ? chatCapable : models;
    const modelConfigs = usableModels.map((id, i) => {
        const tier = inferTier(id, i, usableModels.length);
        return {
            id,
            label: id,
            backendModel: id,
            aliases: [],
            tier,
            default: i === 0,
        };
    });
    // No detected models → write an empty models[]. The historical
    // your-default-model placeholder caused two real Windows failures: a sticky
    // local default with no endpoint blocked launch on local-runtime preflight,
    // and persisted across upgrades so users who later added a cloud provider
    // still routed through the local path. Empty models[] lets doLaunch route
    // straight to Admin/start onboarding.
    return {
        port,
        host: '127.0.0.1',
        routerUrl,
        routerTimeoutMs: 600000,
        responseModelStyle: 'platform',
        models: modelConfigs,
        logLevel: 'info',
        // L3 training-data collection — opt-in. Set to true to enable per-session
        // quality scoring and JSONL collection under ~/.owlcoda/training/.
        // Env OWLCODA_TRAINING_COLLECTION=1 overrides per process.
        trainingCollection: false,
    };
}
export async function runInit(options = {}) {
    const configPath = getOwlcodaConfigPath();
    const port = options.port ?? 8019;
    // Check if config already exists
    if (existsSync(configPath) && !options.force) {
        return {
            created: false,
            configPath,
            message: `Config already exists at ${configPath}. Use --force to overwrite.`,
        };
    }
    // Resolve endpoint + models. If the user passed --endpoint (or the legacy
    // --router alias), honor it as their explicit choice (single probe).
    // Otherwise sweep the well-known local OpenAI-compatible endpoints and pick
    // the first responder.
    let routerUrl;
    let detectedModels;
    let routerLabel;
    if (options.routerUrl) {
        routerUrl = options.routerUrl;
        detectedModels = await detectRouterModels(routerUrl);
    }
    else {
        const probe = await probeDefaultRouters();
        routerUrl = probe.routerUrl;
        detectedModels = probe.models;
        routerLabel = probe.models.length > 0 ? probe.routerLabel : undefined;
    }
    // Build config
    const config = buildConfigFromModels(detectedModels, routerUrl, port);
    // Ensure parent directory exists so first-run with a fresh OWLCODA_HOME
    // (or ~/.owlcoda/ missing on a clean system) doesn't ENOENT here.
    const parentDir = dirname(configPath);
    if (!existsSync(parentDir)) {
        mkdirSync(parentDir, { recursive: true });
    }
    // Write config
    writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
    const modelMsg = detectedModels.length > 0
        ? `Auto-detected ${detectedModels.length} model(s)${routerLabel ? ` from ${routerLabel}` : ' from router'} (${routerUrl})`
        : `No local backend reachable. Add a cloud provider in Admin (run 'owlcoda admin') or start a local runtime, then re-launch.`;
    return {
        created: true,
        configPath,
        message: `Created ${configPath}\n${modelMsg}`,
        modelsDetected: detectedModels.length > 0 ? detectedModels : undefined,
    };
}
export function formatInitResult(result) {
    const lines = [];
    if (result.created) {
        lines.push(`✅ ${result.message}`);
        lines.push('');
        lines.push('Next steps:');
        if (!result.modelsDetected) {
            lines.push('  1. Run: owlcoda admin    — add a cloud provider (MiniMax, OpenRouter, …) or wire a local runtime');
        }
        lines.push(`  ${result.modelsDetected ? '1' : '2'}. Run: owlcoda doctor   — verify your environment`);
        lines.push(`  ${result.modelsDetected ? '2' : '3'}. Run: owlcoda          — start using OwlCoda`);
        lines.push('');
        lines.push('OwlCoda native mode is ready:');
        lines.push('  · 42+ tools available — run /tools to see them');
        lines.push('  · /skills to discover coding skills');
        lines.push('  · /model to switch between local and cloud models');
        lines.push('  · /dashboard for system health monitoring');
        lines.push('  · /why-native to see what makes OwlCoda different');
        lines.push('');
        lines.push('Privacy:');
        lines.push('  · Training-data collection is OFF by default. Sessions stay local-only.');
        lines.push('  · To opt in: set "trainingCollection": true in config.json,');
        lines.push('    or run with OWLCODA_TRAINING_COLLECTION=1. Collected data');
        lines.push('    is PII-sanitized and stays in ~/.owlcoda/training/.');
    }
    else {
        lines.push(`⚠️  ${result.message}`);
    }
    return lines.join('\n');
}
//# sourceMappingURL=init.js.map