'use strict';
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// CLI bridge — the thin layer the bash entry points call.
//
// The bash scripts own configuration, gating, locking and logging, exactly as they do for every
// other Varaverk job. This file owns only the work that is genuinely awkward in bash: float
// vector math and SQLite BLOBs. That split follows the existing api_cache_writer.sh precedent —
// a bash shim in front of the language that fits the task.
//
// Every value arrives as an argument or an environment variable read by the caller. This file
// never reads a conf file itself, so there is exactly one place that decides what the settings
// are.
// ═══════════════════════════════════════════════════════════════════════════════════════════════

const { buildIndex } = require('./index.js');
const { search }     = require('./search.js');

function arg(name, dflt) {
    const p = `--${name}=`;
    const hit = process.argv.find(a => a.startsWith(p));
    return hit ? hit.slice(p.length) : dflt;
}
function flag(name) {
    return process.argv.includes(`--${name}`);
}

function fail(msg, code = 1) {
    console.error(msg);
    process.exit(code);
}

async function cmdIndex() {
    const root  = arg('root');
    const db    = arg('db');
    const url   = arg('url');
    const model = arg('model', 'nomic-embed-text');
    if (!root || !db || !url) fail('index: --root, --db and --url are required');

    const quiet = flag('quiet');
    let stats;
    try {
        stats = await buildIndex({
            root, dbPath: db, url, model,
            batch:   parseInt(arg('batch', '32'), 10),
            timeout: parseInt(arg('timeout', '120000'), 10),
            force:   flag('force'),
            dryRun:  flag('dry-run'),
            onProgress: p => {
                if (p.error) console.error(`embed batch failed: ${p.error}`);
                else if (!quiet && p.done % 320 === 0) console.log(`  embedded ${p.done}/${p.total}`);
            },
        });
    } catch (e) {
        fail(`index failed: ${e.message}`, 2);
    }

    if (flag('json')) { console.log(JSON.stringify(stats)); return; }
    if (stats.dryRun) {
        console.log(`DRY RUN — ${stats.files} file(s) would be indexed, ${stats.chunks} chunk(s) embedded`);
        console.log(`          ${stats.skipped} unchanged, ${stats.removed} stale entr(ies) would be dropped`);
        return;
    }
    console.log(`indexed ${stats.files} file(s), ${stats.chunks} chunk(s) embedded`);
    console.log(`  ${stats.skipped} unchanged, ${stats.removed} removed, ${stats.failed} failed`);
    console.log(`  index now holds ${stats.total} chunk(s)`);
    // A partial index is usable but not complete — say so in the exit code so a caller can act.
    if (stats.failed) process.exit(3);
}

async function cmdSearch() {
    const db    = arg('db');
    const url   = arg('url');
    const model = arg('model', 'nomic-embed-text');
    const q     = arg('query');
    if (!db || !url || !q) fail('search: --db, --url and --query are required');

    let r;
    try {
        r = await search({
            dbPath: db, url, embedModel: model, query: q,
            k:       parseInt(arg('k', '8'), 10),
            perFile: parseInt(arg('per-file', '3'), 10),
            section: arg('section', null),
            kind:    arg('kind', null),
        });
    } catch (e) {
        fail(`search failed: ${e.message}`, 2);
    }

    if (flag('json')) { console.log(JSON.stringify(r)); return; }
    if (!r.results.length) { console.log('no matches'); return; }
    if (r.intents.length) console.log(`intent: ${r.intents.join(', ')}\n`);
    for (const x of r.results) {
        const label = x.heading || x.section || '-';
        console.log(`── ${x.score.toFixed(3)}  ${x.path}  [${x.section || x.kind}] ${label}`);
        console.log(x.content.split('\n').map(l => '   ' + l).join('\n'));
        console.log('');
    }
}

// Retrieval + generation. The prompt is built here so the context block and the instructions
// stay in one reviewable place.
async function cmdAsk() {
    const db      = arg('db');
    const url     = arg('url');
    const embed   = arg('embed-model', 'nomic-embed-text');
    const gen     = arg('model');
    const q       = arg('query');
    const timeout = parseInt(arg('timeout', '240000'), 10);
    if (!db || !url || !gen || !q) fail('ask: --db, --url, --model and --query are required');

    let r;
    try {
        // section and kind must be forwarded here too. They were not, so both filters worked
        // under `search` and were silently ignored under `ask` — the documented
        // --section=CONFIGURATION usage retrieved from the whole index and the answer looked
        // plausible, which is the worst way for a filter to fail.
        r = await search({
            dbPath: db, url, embedModel: embed, query: q,
            k: parseInt(arg('k', '6'), 10), perFile: parseInt(arg('per-file', '2'), 10),
            section: arg('section', null),
            kind:    arg('kind', null),
        });
    } catch (e) {
        fail(`retrieval failed: ${e.message}`, 2);
    }
    if (!r.results.length) fail('no relevant context found in the index', 4);

    const context = r.results.map((x, i) => {
        const label = [x.path, x.section, x.heading].filter(Boolean).join(' › ');
        return `[${i + 1}] ${label}\n${x.content}`;
    }).join('\n\n');

    const prompt =
`You are answering questions about Varaverk, a two-server self-healing home media ecosystem.

Answer ONLY from the context below. If the context does not contain the answer, say so plainly
and name what is missing — do not fill the gap from general knowledge about Linux, Docker or
rsync, because this system's conventions are frequently not the conventional ones.

Cite the source of each claim as [n]. Be concise and concrete.

CONTEXT
${context}

QUESTION
${q}

ANSWER`;

    let res;
    try {
        res = await fetch(`${url.replace(/\/$/, '')}/api/generate`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                model: gen, prompt, stream: false,
                options: { temperature: 0.2, num_ctx: 8192 },
            }),
            signal: AbortSignal.timeout(timeout),
        });
    } catch (e) {
        fail(`generation failed: ${e.message}`, 2);
    }
    if (!res.ok) fail(`generation HTTP ${res.status}`, 2);
    const j = await res.json();

    if (flag('json')) {
        console.log(JSON.stringify({ answer: j.response, sources: r.results.map(x => ({ path: x.path, section: x.section, heading: x.heading, score: x.score })) }));
        return;
    }
    console.log((j.response || '').trim());
    console.log('\nSources:');
    r.results.forEach((x, i) => {
        console.log(`  [${i + 1}] ${[x.path, x.section, x.heading].filter(Boolean).join(' › ')}`);
    });
}

const cmd = process.argv[2];
const table = { index: cmdIndex, search: cmdSearch, ask: cmdAsk };
if (!table[cmd]) fail(`usage: cli.js <index|search|ask> [--flags]`);
table[cmd]().catch(e => fail(e.message, 2));
