/**
 * Handler for fs_replace messages.
 * Performs find/replace across files within a project, emits fs_changed events
 * for modified files, and returns replacement counts per file.
 */
import { readdir, readFile, writeFile } from "node:fs/promises";
import { join, relative } from "node:path";
import { ErrorCode } from "@pocketcoder/shared/constants";
import { getProject } from "../db/repositories/projects.js";
import { incrementSeq } from "../db/repositories/fs-state.js";
import { logger } from "../utils/logger.js";
const EXCLUDED_DIRS = new Set([
    "node_modules",
    ".git",
    ".hg",
    ".svn",
    "dist",
    "build",
    ".next",
    ".nuxt",
    "__pycache__",
    ".cache",
]);
async function replaceInFiles(projectPath, find, replace, options) {
    const filesModified = [];
    let totalReplacements = 0;
    const flags = options.caseSensitive ? "g" : "gi";
    let pattern;
    if (options.regex) {
        pattern = new RegExp(find, flags);
    }
    else {
        const escaped = find.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        pattern = new RegExp(escaped, flags);
    }
    // Simple glob matching for file filtering
    let globPattern = null;
    if (options.glob) {
        const globEscaped = options.glob
            .replace(/[.+^${}()|[\]\\]/g, "\\$&")
            .replace(/\*/g, ".*")
            .replace(/\?/g, ".");
        globPattern = new RegExp(`^${globEscaped}$`);
    }
    async function walk(dir) {
        let entries;
        try {
            entries = await readdir(dir, { withFileTypes: true });
        }
        catch {
            return;
        }
        for (const entry of entries) {
            if (entry.isDirectory()) {
                if (EXCLUDED_DIRS.has(entry.name))
                    continue;
                await walk(join(dir, entry.name));
            }
            else if (entry.isFile()) {
                // Apply glob filter on filename
                if (globPattern && !globPattern.test(entry.name))
                    continue;
                const filePath = join(dir, entry.name);
                const relPath = relative(projectPath, filePath);
                try {
                    const content = await readFile(filePath, "utf-8");
                    // Reset lastIndex for global regex
                    pattern.lastIndex = 0;
                    // Count replacements
                    const matches = content.match(pattern);
                    if (!matches || matches.length === 0)
                        continue;
                    const replacementCount = matches.length;
                    const newContent = content.replace(pattern, replace);
                    await writeFile(filePath, newContent, "utf-8");
                    filesModified.push({ path: relPath, replacements: replacementCount });
                    totalReplacements += replacementCount;
                }
                catch {
                    // Skip files that can't be read/written (binary, permissions, etc.)
                }
            }
        }
    }
    await walk(projectPath);
    return { filesModified, totalReplacements };
}
export async function handleFsReplace(message, send) {
    const msg = message;
    const { requestId, sessionId, payload } = msg;
    const { find, replace, glob, caseSensitive, regex } = payload;
    const project = getProject(sessionId);
    if (!project) {
        send({
            type: "fs_replace_response",
            requestId,
            error: {
                code: ErrorCode.INVALID_REQUEST,
                message: `Project not found: ${sessionId}`,
            },
        });
        return;
    }
    // Validate find string
    if (!find || find.length < 1) {
        send({
            type: "fs_replace_response",
            requestId,
            error: {
                code: ErrorCode.QUERY_TOO_SHORT,
                message: "Find string must be at least 1 character",
            },
        });
        return;
    }
    // Validate regex if regex mode
    if (regex) {
        try {
            new RegExp(find);
        }
        catch {
            send({
                type: "fs_replace_response",
                requestId,
                error: {
                    code: ErrorCode.INVALID_REGEX,
                    message: "Invalid regular expression",
                },
            });
            return;
        }
    }
    try {
        const options = {
            glob,
            caseSensitive: caseSensitive ?? false,
            regex: regex ?? false,
        };
        const result = await replaceInFiles(project.path, find, replace, options);
        // Emit fs_changed events for modified files
        if (result.filesModified.length > 0) {
            const changes = result.filesModified.map((f) => ({
                kind: "modify",
                path: f.path,
            }));
            const seq = incrementSeq(project.project_id);
            send({
                type: "fs_changed",
                sessionId,
                payload: {
                    seq,
                    changes,
                },
            });
        }
        send({
            type: "fs_replace_response",
            requestId,
            payload: {
                totalReplacements: result.totalReplacements,
                filesModified: result.filesModified,
            },
        });
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        logger.error(`fs_replace error: ${errorMessage}`);
        send({
            type: "fs_replace_response",
            requestId,
            error: {
                code: ErrorCode.UNKNOWN_ERROR,
                message: errorMessage,
            },
        });
    }
}
//# sourceMappingURL=fs-replace.js.map