/**
 * CSV-based storage implementation for judge evaluations.
 *
 * Stores evaluation results in a CSV file with the following columns:
 * - timestamp: ISO 8601 timestamp
 * - promptId: Prompt identifier
 * - branchName: Git branch name
 * - commitSha: Git commit SHA
 * - appId: Application identifier
 * - prompt: The actual prompt text
 * - passed: Boolean pass/fail status
 * - score: Overall score (0-100)
 * - feedback: Summary feedback text
 *
 * CSV format provides simple, human-readable storage suitable for analysis
 * in spreadsheet applications or data processing tools.
 *
 * Configuration:
 * The storage location can be configured via the JUDGE_STORAGE_PATH environment variable:
 * - Directory path: `JUDGE_STORAGE_PATH=~/eval` (creates evaluations.csv inside)
 * - Full file path: `JUDGE_STORAGE_PATH=~/eval/results.csv`
 * - Supports ~ expansion for home directory
 * - Default: `<appRoot>/.superblocks/judge-evaluations/evaluations.csv`
 */
import { promises as fs } from "fs";
import path from "path";
/**
 * Default storage directory relative to project root.
 */
const DEFAULT_STORAGE_DIR = ".superblocks/judge-evaluations";
/**
 * CSV header row.
 */
const CSV_HEADER = "timestamp,promptId,branchName,commitSha,appId,prompt,passed,score,feedback\n";
/**
 * CSV-based implementation of JudgeStorage.
 *
 * Thread-safe through file system atomic operations.
 * Appends new evaluations to maintain chronological order.
 */
export class CsvJudgeStorage {
    filePath;
    /**
     * Creates a new CSV storage instance.
     *
     * @param storageDir - Directory for storage (default: .superblocks/judge-evaluations)
     * @param filename - CSV filename (default: evaluations.csv)
     */
    constructor(storageDir = DEFAULT_STORAGE_DIR, filename = "evaluations.csv") {
        this.filePath = path.join(storageDir, filename);
    }
    /**
     * Ensures the storage directory and file exist.
     *
     * Creates directory if needed and initializes CSV with header row.
     */
    async ensureStorageExists() {
        const dir = path.dirname(this.filePath);
        // Create directory if it doesn't exist
        await fs.mkdir(dir, { recursive: true });
        // Check if file exists
        try {
            await fs.access(this.filePath);
        }
        catch {
            // File doesn't exist, create with header
            await fs.writeFile(this.filePath, CSV_HEADER, "utf8");
        }
    }
    /**
     * Escapes a value for CSV format.
     *
     * Handles quotes and commas by wrapping in double quotes and escaping
     * internal quotes.
     */
    escapeCsvValue(value) {
        const str = String(value);
        // If value contains comma, quote, or newline, wrap in quotes and escape quotes
        if (str.includes(",") || str.includes('"') || str.includes("\n")) {
            return `"${str.replace(/"/g, '""')}"`;
        }
        return str;
    }
    /**
     * Converts a StoredEvaluation to a CSV row.
     */
    toCsvRow(evaluation) {
        const { timestamp, promptId, branchName, commitSha, appId, prompt, evaluation: result, } = evaluation;
        const values = [
            this.escapeCsvValue(timestamp),
            this.escapeCsvValue(promptId),
            this.escapeCsvValue(branchName),
            this.escapeCsvValue(commitSha),
            this.escapeCsvValue(appId),
            this.escapeCsvValue(prompt),
            this.escapeCsvValue(result.passed),
            this.escapeCsvValue(result.overallScore),
            this.escapeCsvValue(result.summary),
        ];
        return values.join(",") + "\n";
    }
    /**
     * Parses a CSV row into a StoredEvaluation.
     *
     * Note: This is a simplified parser that doesn't handle all CSV edge cases.
     * For production use with complex data, consider a proper CSV parsing library.
     */
    fromCsvRow(row) {
        // Skip empty rows or header
        if (!row.trim() || row.startsWith("timestamp,")) {
            return null;
        }
        // Simple CSV parsing (doesn't handle all edge cases)
        const values = [];
        let current = "";
        let inQuotes = false;
        for (let i = 0; i < row.length; i++) {
            const char = row[i];
            if (char === '"') {
                // Check for escaped quote
                if (inQuotes && row[i + 1] === '"') {
                    current += '"';
                    i++; // Skip next quote
                }
                else {
                    inQuotes = !inQuotes;
                }
            }
            else if (char === "," && !inQuotes) {
                values.push(current);
                current = "";
            }
            else {
                current += char;
            }
        }
        // Add last value
        values.push(current);
        if (values.length < 9) {
            return null;
        }
        const [timestamp, promptId, branchName, commitSha, appId, prompt, passed, score, feedback,] = values;
        // Validate all required fields are present
        if (!timestamp ||
            !promptId ||
            !branchName ||
            !commitSha ||
            !appId ||
            !prompt ||
            !passed ||
            !score ||
            feedback === undefined) {
            return null;
        }
        return {
            timestamp,
            promptId,
            branchName,
            commitSha,
            appId,
            prompt,
            evaluation: {
                passed: passed === "true",
                overallScore: parseFloat(score) || 0,
                summary: feedback,
                criteriaEvaluations: [], // Not stored in CSV for simplicity
                suggestions: [], // Not stored in CSV for simplicity
                tokensUsed: { input: 0, output: 0, cached: 0, total: 0 }, // Not stored in CSV
                evaluationDurationMs: 0, // Not stored in CSV
            },
        };
    }
    /**
     * Saves an evaluation result to the CSV file.
     *
     * Appends a new row to the file. Creates file and directory if needed.
     */
    async saveEvaluation(evaluation) {
        await this.ensureStorageExists();
        const row = this.toCsvRow(evaluation);
        await fs.appendFile(this.filePath, row, "utf8");
    }
    /**
     * Reads all evaluations from the CSV file.
     */
    async readAllEvaluations() {
        try {
            await this.ensureStorageExists();
            const content = await fs.readFile(this.filePath, "utf8");
            const rows = content.split("\n");
            const evaluations = [];
            for (const row of rows) {
                const evaluation = this.fromCsvRow(row);
                if (evaluation) {
                    evaluations.push(evaluation);
                }
            }
            return evaluations;
        }
        catch {
            // File doesn't exist or can't be read
            return [];
        }
    }
    /**
     * Filters evaluations based on query parameters.
     */
    applyQuery(evaluations, query) {
        if (!query) {
            return evaluations;
        }
        let filtered = evaluations;
        if (query.promptId !== undefined) {
            filtered = filtered.filter((e) => e.promptId === query.promptId);
        }
        if (query.branchName !== undefined) {
            filtered = filtered.filter((e) => e.branchName === query.branchName);
        }
        if (query.appId !== undefined) {
            filtered = filtered.filter((e) => e.appId === query.appId);
        }
        if (query.passed !== undefined) {
            filtered = filtered.filter((e) => e.evaluation.passed === query.passed);
        }
        if (query.minScore !== undefined) {
            filtered = filtered.filter((e) => e.evaluation.overallScore >= query.minScore);
        }
        if (query.startDate !== undefined) {
            filtered = filtered.filter((e) => e.timestamp >= query.startDate);
        }
        if (query.endDate !== undefined) {
            filtered = filtered.filter((e) => e.timestamp <= query.endDate);
        }
        // Sort by timestamp (newest first)
        filtered.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
        if (query.limit !== undefined) {
            filtered = filtered.slice(0, query.limit);
        }
        return filtered;
    }
    /**
     * Retrieves all evaluations matching the query.
     */
    async getEvaluations(query) {
        const all = await this.readAllEvaluations();
        return this.applyQuery(all, query);
    }
    /**
     * Retrieves evaluations for a specific prompt.
     */
    async getEvaluationsByPrompt(promptId) {
        return this.getEvaluations({ promptId });
    }
    /**
     * Retrieves evaluations for a specific branch.
     */
    async getEvaluationsByBranch(branchName) {
        return this.getEvaluations({ branchName });
    }
    /**
     * Deletes all stored evaluations.
     *
     * Removes the CSV file. Use with caution.
     */
    async clear() {
        try {
            await fs.unlink(this.filePath);
        }
        catch {
            // File doesn't exist, nothing to clear
        }
    }
}
//# sourceMappingURL=csv-storage.js.map