/**
 * Training-store — single source of truth for the locally collected training
 * dataset (~/.owlcoda/training/collected.jsonl + manifest.json).
 *
 * CLI, REPL slash command, and HTTP API all read through here so that
 * "what `owlcoda training status` shows" and "what `/training` shows" and
 * "what `GET /v1/training/status` returns" can never drift apart again.
 *
 * Reads are streaming where it matters — collected.jsonl can be hundreds of
 * MB. Sample/limit paths use line-by-line readline; full export streams via
 * the consumer (e.g. `pipe(createReadStream(...))`).
 */
import { createReadStream } from 'node:fs';
import { readFile, stat } from 'node:fs/promises';
import { createInterface } from 'node:readline';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { sanitizeText } from './sanitize.js';
// ─── Paths ───
export function getTrainingDir() {
    const home = process.env.OWLCODA_HOME ?? join(process.env.HOME ?? homedir(), '.owlcoda');
    return join(home, 'training');
}
export function getCollectedPath() {
    return join(getTrainingDir(), 'collected.jsonl');
}
export function getManifestPath() {
    return join(getTrainingDir(), 'manifest.json');
}
const EMPTY_STATUS = {
    totalCollected: 0,
    totalSkipped: 0,
    averageQuality: 0,
    lastCollectedAt: null,
    fileSize: 0,
    lineCount: 0,
    manifestPresent: false,
};
export async function readCollectedStatus() {
    const path = getCollectedPath();
    const status = { ...EMPTY_STATUS, path };
    try {
        const raw = await readFile(getManifestPath(), 'utf-8');
        const manifest = JSON.parse(raw);
        status.manifestPresent = true;
        status.totalCollected = Number(manifest.totalCollected ?? 0);
        status.totalSkipped = Number(manifest.totalSkipped ?? 0);
        status.averageQuality = Number(manifest.averageQuality ?? 0);
        status.lastCollectedAt = manifest.lastCollectedAt || null;
    }
    catch { /* manifest absent — leave defaults */ }
    try {
        const s = await stat(path);
        status.fileSize = s.size;
        status.lineCount = await countJsonlLines(path);
    }
    catch { /* collected.jsonl absent */ }
    return status;
}
async function countJsonlLines(path) {
    // Stream line-by-line — avoid pulling the whole 240MB into memory just to
    // count. readline gives us one event per logical line.
    let count = 0;
    const rl = createInterface({ input: createReadStream(path), crlfDelay: Infinity });
    for await (const line of rl) {
        if (line.trim())
            count++;
    }
    return count;
}
/**
 * Async iterable over collected.jsonl lines. Empty if file is missing.
 * Caller is responsible for terminating early — readline closes when the
 * iterator returns or breaks.
 */
export async function* readCollectedLines(opts = {}) {
    const path = getCollectedPath();
    let exists = false;
    try {
        await stat(path);
        exists = true;
    }
    catch { /* missing */ }
    if (!exists)
        return;
    const { limit, sanitize } = opts;
    const rl = createInterface({ input: createReadStream(path), crlfDelay: Infinity });
    let yielded = 0;
    for await (const raw of rl) {
        const line = raw.trim();
        if (!line)
            continue;
        yield sanitize ? sanitizeText(line).text : line;
        yielded++;
        if (limit !== undefined && yielded >= limit) {
            rl.close();
            return;
        }
    }
}
/**
 * Read first N lines and truncate each to maxChars. Designed for REPL
 * `/training sample` and similar surfaces — never returns more than
 * limit*maxChars characters total.
 */
export async function readCollectedSample(opts = {}) {
    const limit = opts.limit ?? 1;
    const maxChars = opts.maxChars ?? 240;
    const sanitize = opts.sanitize !== false;
    const out = [];
    for await (const line of readCollectedLines({ limit, sanitize })) {
        if (line.length <= maxChars) {
            out.push({ raw: line, truncated: false });
        }
        else {
            out.push({ raw: line.slice(0, maxChars), truncated: true });
        }
    }
    return out;
}
//# sourceMappingURL=training-store.js.map