"use strict";
// =============================================================================
// Pure Agent - File Operations (Native Node.js)
// Ported from Hermes tools/file_operations.py (using fs instead of shell)
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.NativeFileOperations = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const result_types_1 = require("../result-types");
const utils_1 = require("../utils");
const constants_1 = require("../constants");
const logger_1 = require("../logger");
// ---------------------------------------------------------------------------
// Sensitive path protection (ported from Hermes file_safety.py)
// ---------------------------------------------------------------------------
const WRITE_DENIED_PREFIXES = [
    '/etc/', '/boot/', '/usr/lib/systemd/', '/proc/', '/sys/',
];
const WRITE_DENIED_PATHS = new Set([
    '/etc/passwd', '/etc/shadow', '/etc/sudoers',
]);
function isWriteDenied(filepath) {
    const normalized = path.resolve(filepath);
    if (WRITE_DENIED_PATHS.has(normalized))
        return true;
    for (const prefix of WRITE_DENIED_PREFIXES) {
        if (normalized.startsWith(prefix))
            return true;
    }
    // Docker socket
    if (normalized.includes('docker.sock'))
        return true;
    return false;
}
// ---------------------------------------------------------------------------
// Blocked device paths
// ---------------------------------------------------------------------------
const BLOCKED_DEVICE_PATHS = new Set([
    '/dev/zero', '/dev/urandom', '/dev/random', '/dev/null',
    '/dev/stdin', '/dev/stdout', '/dev/stderr',
]);
// ---------------------------------------------------------------------------
// NativeFileOperations
// ---------------------------------------------------------------------------
class NativeFileOperations {
    cwd;
    constructor(cwd) {
        this.cwd = cwd;
    }
    // -------------------------------------------------------------------------
    // Read file
    // -------------------------------------------------------------------------
    readFile(filepath, offset = 1, limit = constants_1.DEFAULT_READ_LIMIT) {
        const resolved = (0, utils_1.resolvePath)(filepath, this.cwd);
        // Block device paths
        if (BLOCKED_DEVICE_PATHS.has(resolved)) {
            return (0, result_types_1.readError)(`Cannot read device path: ${resolved}`);
        }
        try {
            const stat = fs.statSync(resolved);
            if (stat.isDirectory()) {
                return this._readDirectory(resolved);
            }
            // Binary/image check
            if ((0, result_types_1.isBinaryFile)(resolved)) {
                if ((0, result_types_1.isImageFile)(resolved)) {
                    const content = fs.readFileSync(resolved);
                    return {
                        content: '',
                        totalLines: 0,
                        fileSize: stat.size,
                        truncated: false,
                        isBinary: true,
                        isImage: true,
                        base64Content: content.toString('base64'),
                        mimeType: this._getMimeType(resolved),
                    };
                }
                return {
                    content: `[Binary file: ${path.basename(resolved)}, ${stat.size} bytes]`,
                    totalLines: 0,
                    fileSize: stat.size,
                    truncated: false,
                    isBinary: true,
                    isImage: false,
                };
            }
            // Read content
            let content = fs.readFileSync(resolved, 'utf-8');
            const lines = content.split('\n');
            const totalLines = lines.length;
            // Apply pagination
            const startIdx = Math.max(0, offset - 1); // 1-based to 0-based
            const endIdx = Math.min(startIdx + limit, totalLines);
            const selectedLines = lines.slice(startIdx, endIdx);
            // Format with line numbers (cat -n style)
            const numbered = selectedLines.map((line, i) => {
                const lineNum = startIdx + i + 1;
                return `${lineNum.toString().padStart(6)}\t${line}`;
            }).join('\n');
            const truncatedResult = numbered.length > constants_1.DEFAULT_MAX_READ_CHARS;
            let finalContent = numbered;
            let hint;
            if (truncatedResult) {
                const t = (0, utils_1.truncate)(numbered, constants_1.DEFAULT_MAX_READ_CHARS);
                finalContent = t.text;
                hint = `Content truncated at ${constants_1.DEFAULT_MAX_READ_CHARS} chars. Use offset/limit to read different sections.`;
            }
            if (endIdx < totalLines) {
                hint = hint || `Showing lines ${offset}-${endIdx} of ${totalLines}. Use offset to continue reading.`;
            }
            return {
                content: finalContent,
                totalLines,
                fileSize: stat.size,
                truncated: truncatedResult || endIdx < totalLines,
                hint,
                isBinary: false,
                isImage: false,
            };
        }
        catch (err) {
            if (err.code === 'ENOENT') {
                // Try to find similar files
                const similar = this._findSimilarFiles(resolved);
                return (0, result_types_1.readError)(`File not found: ${resolved}${similar.length > 0 ? `\nSimilar files: ${similar.join(', ')}` : ''}`);
            }
            if (err.code === 'EACCES') {
                return (0, result_types_1.readError)(`Permission denied: ${resolved}`);
            }
            return (0, result_types_1.readError)(`Failed to read file: ${err.message}`);
        }
    }
    readFileRaw(filepath) {
        const resolved = (0, utils_1.resolvePath)(filepath, this.cwd);
        try {
            const stat = fs.statSync(resolved);
            const content = fs.readFileSync(resolved, 'utf-8');
            return {
                content,
                totalLines: content.split('\n').length,
                fileSize: stat.size,
                truncated: false,
                isBinary: false,
                isImage: false,
            };
        }
        catch (err) {
            return (0, result_types_1.readError)(`Failed to read file: ${err.message}`);
        }
    }
    // -------------------------------------------------------------------------
    // Write file
    // -------------------------------------------------------------------------
    writeFile(filepath, content) {
        const resolved = (0, utils_1.resolvePath)(filepath, this.cwd);
        // Security check
        if (isWriteDenied(resolved)) {
            return (0, result_types_1.writeError)(`Write denied for sensitive path: ${resolved}`);
        }
        try {
            // Create directory if needed
            const dir = path.dirname(resolved);
            let dirsCreated = false;
            if (!fs.existsSync(dir)) {
                fs.mkdirSync(dir, { recursive: true });
                dirsCreated = true;
            }
            // Detect existing line ending style
            let lineEnding = '\n';
            try {
                const existing = fs.readFileSync(resolved, 'utf-8').slice(0, 4096);
                const detected = (0, result_types_1.detectLineEnding)(existing);
                if (detected)
                    lineEnding = detected;
            }
            catch { /* file doesn't exist yet, use default */ }
            // Normalize line endings to match
            const normalized = (0, result_types_1.normalizeLineEndings)(content, lineEnding);
            // Write atomically via temp file
            const tmpPath = resolved + '.tmp.' + process.pid;
            fs.writeFileSync(tmpPath, normalized, 'utf-8');
            fs.renameSync(tmpPath, resolved);
            const bytesWritten = Buffer.byteLength(normalized, 'utf-8');
            (0, logger_1.getLogger)().info(`Wrote ${bytesWritten} bytes to ${resolved}`);
            return { bytesWritten, dirsCreated };
        }
        catch (err) {
            return (0, result_types_1.writeError)(`Failed to write file: ${err.message}`);
        }
    }
    // -------------------------------------------------------------------------
    // Patch (replace)
    // -------------------------------------------------------------------------
    patchReplace(filepath, oldString, newString, replaceAll = false) {
        const resolved = (0, utils_1.resolvePath)(filepath, this.cwd);
        if (isWriteDenied(resolved)) {
            return (0, result_types_1.patchError)(`Write denied for sensitive path: ${resolved}`);
        }
        try {
            let content = fs.readFileSync(resolved, 'utf-8');
            // Check if old_string exists
            if (!content.includes(oldString)) {
                // Try fuzzy match
                const hint = this._fuzzyMatchHint(content, oldString);
                return (0, result_types_1.patchError)(`old_string not found in ${resolved}` +
                    (hint ? `\n${hint}` : ''));
            }
            // Count occurrences
            const count = (content.split(oldString).length - 1);
            if (count > 1 && !replaceAll) {
                return (0, result_types_1.patchError)(`Found ${count} occurrences of old_string in ${resolved}. ` +
                    `Use replace_all=true or provide a more specific match.`);
            }
            // Apply replacement
            if (replaceAll) {
                content = content.split(oldString).join(newString);
            }
            else {
                content = content.replace(oldString, newString);
            }
            // Write back
            const tmpPath = resolved + '.tmp.' + process.pid;
            fs.writeFileSync(tmpPath, content, 'utf-8');
            fs.renameSync(tmpPath, resolved);
            (0, logger_1.getLogger)().info(`Patched ${resolved} (${count} replacement${count !== 1 ? 's' : ''})`);
            return {
                success: true,
                diff: `--- ${resolved}\n+++ ${resolved}\n@@ -1 +1 @@\n-${oldString.split('\n')[0]}\n+${newString.split('\n')[0]}`,
                filesModified: [resolved],
            };
        }
        catch (err) {
            if (err.code === 'ENOENT') {
                return (0, result_types_1.patchError)(`File not found: ${resolved}`);
            }
            return (0, result_types_1.patchError)(`Patch failed: ${err.message}`);
        }
    }
    // -------------------------------------------------------------------------
    // Delete file
    // -------------------------------------------------------------------------
    deleteFile(filepath) {
        const resolved = (0, utils_1.resolvePath)(filepath, this.cwd);
        if (isWriteDenied(resolved)) {
            return (0, result_types_1.writeError)(`Delete denied for sensitive path: ${resolved}`);
        }
        try {
            const stat = fs.statSync(resolved);
            if (stat.isDirectory()) {
                return (0, result_types_1.writeError)(`Cannot delete directory with deleteFile. Use terminal: rm -rf`);
            }
            fs.unlinkSync(resolved);
            (0, logger_1.getLogger)().info(`Deleted ${resolved}`);
            return { bytesWritten: 0, dirsCreated: false };
        }
        catch (err) {
            return (0, result_types_1.writeError)(`Failed to delete: ${err.message}`);
        }
    }
    // -------------------------------------------------------------------------
    // Move file
    // -------------------------------------------------------------------------
    moveFile(src, dst) {
        const resolvedSrc = (0, utils_1.resolvePath)(src, this.cwd);
        const resolvedDst = (0, utils_1.resolvePath)(dst, this.cwd);
        try {
            const dir = path.dirname(resolvedDst);
            if (!fs.existsSync(dir)) {
                fs.mkdirSync(dir, { recursive: true });
            }
            fs.renameSync(resolvedSrc, resolvedDst);
            (0, logger_1.getLogger)().info(`Moved ${resolvedSrc} -> ${resolvedDst}`);
            return { bytesWritten: 0, dirsCreated: false };
        }
        catch (err) {
            return (0, result_types_1.writeError)(`Failed to move file: ${err.message}`);
        }
    }
    // -------------------------------------------------------------------------
    // Search (ripgrep-like, using Node.js fs)
    // -------------------------------------------------------------------------
    search(pattern, searchPath = '.', fileGlob, limit = constants_1.DEFAULT_SEARCH_LIMIT, offset = 0) {
        const resolved = (0, utils_1.resolvePath)(searchPath, this.cwd);
        try {
            // Try ripgrep first if available
            return this._searchWithRg(pattern, resolved, fileGlob, limit, offset);
        }
        catch {
            // Fallback to Node.js implementation
            return this._searchWithNode(pattern, resolved, fileGlob, limit, offset);
        }
    }
    _searchWithRg(pattern, searchPath, fileGlob, limit = constants_1.DEFAULT_SEARCH_LIMIT, offset = 0) {
        const rgBin = (0, constants_1.isWindows)() ? 'rg.exe' : 'rg';
        let cmd = `${rgBin} --no-heading --line-number --color=never`;
        if (fileGlob) {
            cmd += ` --glob="${fileGlob}"`;
        }
        // Add limit (ripgrep uses --max-count for per-file, --max-columns for overall)
        cmd += ` --max-count=${limit + offset}`;
        // Escape pattern for shell
        const escapedPattern = pattern.replace(/'/g, "'\\''");
        cmd += ` '${escapedPattern}'`;
        cmd += ` "${searchPath}"`;
        try {
            const output = (0, child_process_1.execSync)(cmd, {
                encoding: 'utf-8',
                timeout: 30000,
                maxBuffer: 10 * 1024 * 1024,
                windowsHide: true,
            });
            const lines = output.trim().split('\n').filter(l => l.length > 0);
            const matches = [];
            for (let i = offset; i < Math.min(lines.length, offset + limit); i++) {
                const line = lines[i];
                // Format: path:line:content  or  path:line:col:content
                const match = line.match(/^(.+?):(\d+):(.*)$/);
                if (match) {
                    matches.push({
                        path: match[1],
                        lineNumber: parseInt(match[2], 10),
                        content: match[3],
                    });
                }
            }
            return {
                matches,
                totalCount: lines.length,
                truncated: lines.length > offset + limit,
            };
        }
        catch (err) {
            // rg returns exit code 1 when no matches found
            if (err.status === 1) {
                return { matches: [], totalCount: 0, truncated: false };
            }
            throw err; // Re-throw to trigger fallback
        }
    }
    _searchWithNode(pattern, searchPath, fileGlob, limit = constants_1.DEFAULT_SEARCH_LIMIT, offset = 0) {
        const regex = new RegExp(pattern, 'gi');
        const matches = [];
        let totalCount = 0;
        const walk = (dir) => {
            if (matches.length >= offset + limit)
                return;
            let entries;
            try {
                entries = fs.readdirSync(dir, { withFileTypes: true });
            }
            catch {
                return; // Permission denied or similar
            }
            for (const entry of entries) {
                if (matches.length >= offset + limit)
                    return;
                // Skip hidden files and common non-useful directories
                if (entry.name.startsWith('.') || entry.name === 'node_modules')
                    continue;
                const fullPath = path.join(dir, entry.name);
                if (entry.isDirectory()) {
                    walk(fullPath);
                }
                else if (entry.isFile()) {
                    // Apply glob filter
                    if (fileGlob && !this._matchGlob(entry.name, fileGlob))
                        continue;
                    // Skip binary files
                    if ((0, result_types_1.isBinaryFile)(fullPath))
                        continue;
                    try {
                        const content = fs.readFileSync(fullPath, 'utf-8');
                        const lines = content.split('\n');
                        for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
                            if (regex.test(lines[lineIdx])) {
                                totalCount++;
                                if (totalCount > offset && matches.length < limit) {
                                    matches.push({
                                        path: fullPath,
                                        lineNumber: lineIdx + 1,
                                        content: lines[lineIdx],
                                    });
                                }
                                regex.lastIndex = 0; // Reset regex
                            }
                        }
                    }
                    catch {
                        // Skip files that can't be read
                    }
                }
            }
        };
        walk(searchPath);
        return {
            matches,
            totalCount,
            truncated: totalCount > offset + limit,
        };
    }
    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------
    _readDirectory(dirpath) {
        try {
            const entries = fs.readdirSync(dirpath, { withFileTypes: true });
            const listing = entries.map(e => {
                const suffix = e.isDirectory() ? '/' : '';
                return `${e.name}${suffix}`;
            }).join('\n');
            return {
                content: `[Directory: ${dirpath}]\n${listing}`,
                totalLines: entries.length,
                fileSize: 0,
                truncated: false,
                isBinary: false,
                isImage: false,
            };
        }
        catch (err) {
            return (0, result_types_1.readError)(`Failed to read directory: ${err.message}`);
        }
    }
    _getMimeType(filepath) {
        const ext = path.extname(filepath).toLowerCase();
        const mimeMap = {
            '.png': 'image/png',
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.gif': 'image/gif',
            '.webp': 'image/webp',
            '.svg': 'image/svg+xml',
            '.bmp': 'image/bmp',
        };
        return mimeMap[ext] || 'application/octet-stream';
    }
    _findSimilarFiles(filepath) {
        const dir = path.dirname(filepath);
        const basename = path.basename(filepath);
        try {
            const entries = fs.readdirSync(dir);
            return entries
                .filter(e => {
                const lower = e.toLowerCase();
                const target = basename.toLowerCase();
                return lower.includes(target.slice(0, 4)) || target.includes(lower.slice(0, 4));
            })
                .slice(0, 5);
        }
        catch {
            return [];
        }
    }
    _fuzzyMatchHint(content, target) {
        const lines = content.split('\n');
        const targetLines = target.split('\n');
        const firstLine = targetLines[0];
        if (!firstLine || firstLine.length < 5)
            return '';
        // Find lines that are similar to the first line of the target
        const firstWord = firstLine.trim().split(/\s+/)[0];
        if (!firstWord || firstWord.length < 3)
            return '';
        const similar = [];
        for (let i = 0; i < lines.length; i++) {
            if (lines[i].includes(firstWord)) {
                similar.push(`  Line ${i + 1}: ${lines[i].trim().slice(0, 100)}`);
                if (similar.length >= 3)
                    break;
            }
        }
        if (similar.length === 0)
            return '';
        return `Did you mean one of these?\n${similar.join('\n')}`;
    }
    _matchGlob(filename, glob) {
        // Simple glob matching: supports *, ?, **
        const regexStr = glob
            .replace(/\./g, '\\.')
            .replace(/\*\*/g, '{{GLOBSTAR}}')
            .replace(/\*/g, '[^/]*')
            .replace(/\?/g, '[^/]')
            .replace(/\{\{GLOBSTAR\}\}/g, '.*');
        const regex = new RegExp(`^${regexStr}$`, 'i');
        return regex.test(filename);
    }
}
exports.NativeFileOperations = NativeFileOperations;
//# sourceMappingURL=file-operations.js.map