/**
 * Shared filesystem utilities.
 *
 * Eliminates duplication of ensureDir, sha256, appendJsonl, loadJson, saveJson
 * across all modules.
 *
 * @connexum/ai-governance
 */
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
/**
 * Ensure a directory exists, creating it recursively if needed.
 */
export function ensureDir(dir) {
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }
}
/**
 * Compute SHA-256 hash of a file's contents.
 */
export function sha256File(filePath) {
    const content = fs.readFileSync(filePath);
    return crypto.createHash('sha256').update(content).digest('hex');
}
/**
 * Compute SHA-256 hash of a string or buffer.
 */
export function sha256(input) {
    return crypto.createHash('sha256').update(input).digest('hex');
}
/**
 * Append a JSON object as a line to a JSONL file.
 * Creates the file and parent directories if they don't exist.
 */
export function appendJsonl(filePath, data) {
    ensureDir(path.dirname(filePath));
    fs.appendFileSync(filePath, JSON.stringify(data) + '\n');
}
/**
 * Parse a single line from a JSONL file.
 * Returns a discriminated union so callers must explicitly handle failures.
 */
export function parseJsonlLine(line) {
    try {
        return { ok: true, value: JSON.parse(line) };
    }
    catch (err) {
        return { ok: false, error: 'parse-error', message: String(err) };
    }
}
/**
 * Read all lines from a JSONL file, parsing each as JSON.
 * Returns empty array if file doesn't exist or is empty.
 *
 * SYSTEMIC-L2: Lines that fail JSON.parse are silently skipped to preserve
 * backwards-compatible return type (T[]). Use `readJsonlWithResults` if
 * you need to detect and handle parse failures explicitly.
 */
export function readJsonl(filePath) {
    if (!fs.existsSync(filePath))
        return [];
    try {
        const content = fs.readFileSync(filePath, 'utf-8').trim();
        if (!content)
            return [];
        const results = [];
        for (const line of content.split('\n').filter(Boolean)) {
            const result = parseJsonlLine(line);
            if (result.ok) {
                results.push(result.value);
            }
            // Lines that fail parse are skipped -- callers that need to detect
            // malformed entries should use readJsonlWithResults() instead.
        }
        return results;
    }
    catch {
        return [];
    }
}
/**
 * SYSTEMIC-L2: Variant of readJsonl that surfaces parse results explicitly.
 * Returns one JsonlParseResult per non-empty line. Callers receive full
 * visibility into parse failures without silent data loss.
 *
 * Use this wherever a malformed entry is a meaningful signal (e.g., WAL
 * replay, audit log ingestion, compliance record loading).
 */
export function readJsonlWithResults(filePath) {
    if (!fs.existsSync(filePath)) {
        return [{ ok: false, error: 'not-found', message: `File not found: ${filePath}` }];
    }
    try {
        const content = fs.readFileSync(filePath, 'utf-8').trim();
        if (!content)
            return [];
        return content.split('\n').filter(Boolean).map(line => parseJsonlLine(line));
    }
    catch (err) {
        return [{ ok: false, error: 'io-error', message: String(err) }];
    }
}
/**
 * Load and parse a JSON file. Returns null if not found or invalid.
 */
export function loadJson(filePath) {
    if (!fs.existsSync(filePath))
        return null;
    try {
        return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
    }
    catch {
        return null;
    }
}
/**
 * Save an object as a formatted JSON file.
 * Creates parent directories if needed.
 */
export function saveJson(filePath, data) {
    ensureDir(path.dirname(filePath));
    fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
}
/**
 * Safely read the last N lines from a file without loading the entire file.
 * Falls back to full read for small files.
 */
export function readLastLines(filePath, n) {
    if (!fs.existsSync(filePath))
        return [];
    try {
        const content = fs.readFileSync(filePath, 'utf-8').trim();
        if (!content)
            return [];
        const lines = content.split('\n').filter(Boolean);
        return lines.slice(-n);
    }
    catch {
        return [];
    }
}
/**
 * Read new bytes from a file starting at a given offset.
 * Returns the new content and updated offset.
 */
export function readFromOffset(filePath, offset) {
    if (!fs.existsSync(filePath))
        return { content: '', newOffset: offset };
    try {
        const stat = fs.statSync(filePath);
        if (stat.size <= offset)
            return { content: '', newOffset: offset };
        const fd = fs.openSync(filePath, 'r');
        const buffer = Buffer.alloc(stat.size - offset);
        fs.readSync(fd, buffer, 0, buffer.length, offset);
        fs.closeSync(fd);
        return { content: buffer.toString('utf-8'), newOffset: stat.size };
    }
    catch {
        return { content: '', newOffset: offset };
    }
}
//# sourceMappingURL=fs.js.map