/**
 * Stage 1: Sanitize VisionClaw JSONL logs.
 *
 * Applies:
 *  1. Remove lines containing noise substrings ([compact], [image-pruner])
 *  2. Remove lines whose message starts with specific prefixes
 *  3. Replace leaked API key strings
 *  4. Redact obs domain URLs
 *
 * When outputDir is provided, writes sanitized copies there (non-destructive).
 * Otherwise modifies files in-place (legacy `sanitize-logs` CLI behavior).
 */
import fs from "node:fs";
import path from "node:path";
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const APIKEY_OLD = "apiKey=58Io52IKCOO1cir4WKDHNu0Cq9CF4HzQ";
const APIKEY_NEW = "apiKey=58Io52IKCOO1xxxxxxxxxx";
const REMOVE_SUBSTRINGS = ["[compact]", "[image-pruner]"];
const DROP_MESSAGE_PREFIXES = ["Version check", "Image resized for vision"];
const REDACT_OBS_DOMAIN = "visionclawobs.com";
const REDACT_OBS_REPLACEMENT = "<obsurl>";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function parseMessageField(line) {
    const trimmed = line.trim();
    if (!trimmed)
        return null;
    try {
        const obj = JSON.parse(trimmed);
        const msg = obj.message;
        return typeof msg === "string" ? msg : null;
    }
    catch {
        return null;
    }
}
function cleanLines(lines) {
    const stats = {
        removedSubstring: 0,
        removedPrefix: 0,
        replaced: 0,
        redactedObsDomain: 0,
    };
    // 1. Remove lines with noise substrings
    let stage = lines.filter((line) => {
        if (REMOVE_SUBSTRINGS.some((s) => line.includes(s))) {
            stats.removedSubstring++;
            return false;
        }
        return true;
    });
    // 2. Replace API key + obs domain
    stage = stage.map((line) => {
        if (line.includes(APIKEY_OLD)) {
            stats.replaced += countOccurrences(line, APIKEY_OLD);
            line = line.replaceAll(APIKEY_OLD, APIKEY_NEW);
        }
        if (line.includes(REDACT_OBS_DOMAIN)) {
            stats.redactedObsDomain += countOccurrences(line, REDACT_OBS_DOMAIN);
            line = line.replaceAll(REDACT_OBS_DOMAIN, REDACT_OBS_REPLACEMENT);
        }
        return line;
    });
    // 3. Drop lines by message prefix
    stage = stage.filter((line) => {
        const msg = parseMessageField(line);
        if (msg !== null &&
            DROP_MESSAGE_PREFIXES.some((p) => msg.startsWith(p))) {
            stats.removedPrefix++;
            return false;
        }
        return true;
    });
    return { cleaned: stage, stats };
}
function countOccurrences(str, sub) {
    let count = 0;
    let idx = str.indexOf(sub);
    while (idx !== -1) {
        count++;
        idx = str.indexOf(sub, idx + sub.length);
    }
    return count;
}
export function sanitize(opts) {
    const { logsDir, outputDir } = opts;
    const files = fs
        .readdirSync(logsDir)
        .filter((f) => f.endsWith(".jsonl"))
        .sort()
        .map((f) => path.join(logsDir, f));
    if (outputDir) {
        fs.mkdirSync(outputDir, { recursive: true });
    }
    const totals = {
        filesChanged: 0,
        removedSubstring: 0,
        removedPrefix: 0,
        replaced: 0,
        redactedObsDomain: 0,
        linesBefore: 0,
        linesAfter: 0,
    };
    for (const filePath of files) {
        const originalText = fs.readFileSync(filePath, "utf-8");
        const lines = originalText.split("\n");
        const { cleaned, stats } = cleanLines(lines);
        totals.linesBefore += lines.length;
        totals.linesAfter += cleaned.length;
        totals.removedSubstring += stats.removedSubstring;
        totals.removedPrefix += stats.removedPrefix;
        totals.replaced += stats.replaced;
        totals.redactedObsDomain += stats.redactedObsDomain;
        const newText = cleaned.join("\n");
        if (outputDir) {
            const outPath = path.join(outputDir, path.basename(filePath));
            fs.writeFileSync(outPath, newText, "utf-8");
            if (newText !== originalText)
                totals.filesChanged++;
        }
        else if (newText !== originalText) {
            fs.writeFileSync(filePath, newText, "utf-8");
            totals.filesChanged++;
        }
    }
    const result = {
        filesProcessed: files.length,
        filesChanged: totals.filesChanged,
        linesBefore: totals.linesBefore,
        linesAfter: totals.linesAfter,
        removedSubstring: totals.removedSubstring,
        removedPrefix: totals.removedPrefix,
        replacedApikey: totals.replaced,
        redactedObsDomain: totals.redactedObsDomain,
    };
    console.log("Sanitize complete:", result);
    return result;
}
// ---------------------------------------------------------------------------
// CLI entry point (standalone command — modifies in-place)
// ---------------------------------------------------------------------------
export function runSanitize(args) {
    const logsDir = args[0];
    if (!logsDir) {
        console.error("Usage: visionclaw sanitize-logs <logs-dir>");
        process.exit(1);
    }
    if (!fs.existsSync(logsDir) || !fs.statSync(logsDir).isDirectory()) {
        console.error(`Not a directory: ${logsDir}`);
        process.exit(1);
    }
    const files = fs
        .readdirSync(logsDir)
        .filter((f) => f.endsWith(".jsonl"));
    if (files.length === 0) {
        console.error(`No .jsonl files found in ${logsDir}`);
        process.exit(1);
    }
    sanitize({ logsDir });
}
//# sourceMappingURL=sanitize.js.map