import fs from "node:fs";
import path from "node:path";
import { z } from "zod";
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { getMemoriesDir } from "../config/index.js";
/**
 * Validate that a path stays within the memories directory.
 * Maps `/memories/...` paths to the actual filesystem path.
 */
function resolveSafePath(memoryPath) {
    const memoriesDir = getMemoriesDir();
    if (!memoryPath.startsWith("/memories")) {
        throw new Error(`Path must start with /memories, got: ${memoryPath}`);
    }
    const relative = memoryPath.slice("/memories".length).replace(/^\//, "");
    const fullPath = relative ? path.join(memoriesDir, relative) : memoriesDir;
    const resolved = path.resolve(fullPath);
    const resolvedRoot = path.resolve(memoriesDir);
    if (!resolved.startsWith(resolvedRoot)) {
        throw new Error(`Path ${memoryPath} would escape /memories directory`);
    }
    return resolved;
}
function formatSize(bytes) {
    if (bytes < 1024)
        return `${bytes}`;
    if (bytes < 1024 * 1024)
        return `${(bytes / 1024).toFixed(1)}K`;
    return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
}
function ok(text) {
    return { content: [{ type: "text", text }] };
}
function err(text) {
    return { content: [{ type: "text", text }], isError: true };
}
// --- Command handlers ---
function handleView(memoryPath, viewRange) {
    const fullPath = resolveSafePath(memoryPath);
    if (!fs.existsSync(fullPath)) {
        return err(`The path ${memoryPath} does not exist. Please provide a valid path.`);
    }
    const stat = fs.statSync(fullPath);
    if (stat.isDirectory()) {
        const lines = [];
        lines.push(`Here're the files and directories up to 2 levels deep in ${memoryPath}, excluding hidden items and node_modules:`);
        lines.push(`${formatSize(stat.size)}\t${memoryPath}`);
        const entries = fs.readdirSync(fullPath, { withFileTypes: true });
        for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
            if (entry.name.startsWith(".") || entry.name === "node_modules")
                continue;
            const entryPath = path.join(fullPath, entry.name);
            const entryStat = fs.statSync(entryPath);
            const entryMemPath = `${memoryPath}/${entry.name}`;
            lines.push(`${formatSize(entryStat.size)}\t${entryMemPath}`);
            // Second level
            if (entry.isDirectory()) {
                const subEntries = fs.readdirSync(entryPath, { withFileTypes: true });
                for (const sub of subEntries.sort((a, b) => a.name.localeCompare(b.name))) {
                    if (sub.name.startsWith(".") || sub.name === "node_modules")
                        continue;
                    const subPath = path.join(entryPath, sub.name);
                    const subStat = fs.statSync(subPath);
                    lines.push(`${formatSize(subStat.size)}\t${entryMemPath}/${sub.name}`);
                }
            }
        }
        return ok(lines.join("\n"));
    }
    // File: return content with line numbers
    const content = fs.readFileSync(fullPath, "utf-8");
    const allLines = content.split("\n");
    if (allLines.length > 999_999) {
        return err(`File ${memoryPath} exceeds maximum line limit of 999,999 lines.`);
    }
    let displayLines = allLines;
    let startNum = 1;
    if (viewRange) {
        const startLine = Math.max(1, viewRange[0]) - 1;
        const endLine = viewRange[1] === -1 ? allLines.length : viewRange[1];
        displayLines = allLines.slice(startLine, endLine);
        startNum = startLine + 1;
    }
    const numbered = displayLines.map((line, i) => `${String(i + startNum).padStart(6, " ")}\t${line}`);
    return ok(`Here's the content of ${memoryPath} with line numbers:\n${numbered.join("\n")}`);
}
function handleCreate(memoryPath, fileText) {
    const fullPath = resolveSafePath(memoryPath);
    if (fs.existsSync(fullPath)) {
        return err(`Error: File ${memoryPath} already exists`);
    }
    const dir = path.dirname(fullPath);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }
    fs.writeFileSync(fullPath, fileText, "utf-8");
    return ok(`File created successfully at: ${memoryPath}`);
}
function handleStrReplace(memoryPath, oldStr, newStr) {
    const fullPath = resolveSafePath(memoryPath);
    if (!fs.existsSync(fullPath)) {
        return err(`Error: The path ${memoryPath} does not exist. Please provide a valid path.`);
    }
    const stat = fs.statSync(fullPath);
    if (!stat.isFile()) {
        return err(`Error: The path ${memoryPath} does not exist. Please provide a valid path.`);
    }
    const content = fs.readFileSync(fullPath, "utf-8");
    const lines = content.split("\n");
    // Find all occurrences
    const matchLines = [];
    let searchPos = 0;
    for (;;) {
        const idx = content.indexOf(oldStr, searchPos);
        if (idx === -1)
            break;
        const lineNum = content.substring(0, idx).split("\n").length;
        matchLines.push(lineNum);
        searchPos = idx + 1;
    }
    if (matchLines.length === 0) {
        return err(`No replacement was performed, old_str \`${oldStr}\` did not appear verbatim in ${memoryPath}.`);
    }
    if (matchLines.length > 1) {
        return err(`No replacement was performed. Multiple occurrences of old_str \`${oldStr}\` in lines: ${matchLines.join(", ")}. Please ensure it is unique`);
    }
    const newContent = content.replace(oldStr, newStr);
    fs.writeFileSync(fullPath, newContent, "utf-8");
    // Show snippet around the edit
    const editLine = matchLines[0] - 1;
    const snippetStart = Math.max(0, editLine - 2);
    const snippetEnd = Math.min(lines.length, editLine + 5);
    const newLines = newContent.split("\n");
    const snippet = newLines
        .slice(snippetStart, snippetEnd)
        .map((l, i) => `${String(snippetStart + i + 1).padStart(6, " ")}\t${l}`)
        .join("\n");
    return ok(`The memory file has been edited.\n${snippet}`);
}
function handleInsert(memoryPath, insertLine, insertText) {
    const fullPath = resolveSafePath(memoryPath);
    if (!fs.existsSync(fullPath)) {
        return err(`Error: The path ${memoryPath} does not exist`);
    }
    const stat = fs.statSync(fullPath);
    if (!stat.isFile()) {
        return err(`Error: The path ${memoryPath} does not exist`);
    }
    const content = fs.readFileSync(fullPath, "utf-8");
    const lines = content.split("\n");
    if (insertLine < 0 || insertLine > lines.length) {
        return err(`Error: Invalid \`insert_line\` parameter: ${insertLine}. It should be within the range of lines of the file: [0, ${lines.length}]`);
    }
    lines.splice(insertLine, 0, insertText.replace(/\n$/, ""));
    fs.writeFileSync(fullPath, lines.join("\n"), "utf-8");
    return ok(`The file ${memoryPath} has been edited.`);
}
function handleDelete(memoryPath) {
    const fullPath = resolveSafePath(memoryPath);
    if (memoryPath === "/memories") {
        return err("Error: Cannot delete the /memories directory itself");
    }
    if (!fs.existsSync(fullPath)) {
        return err(`Error: The path ${memoryPath} does not exist`);
    }
    fs.rmSync(fullPath, { recursive: true });
    return ok(`Successfully deleted ${memoryPath}`);
}
function handleRename(oldPath, newPath) {
    const fullOldPath = resolveSafePath(oldPath);
    const fullNewPath = resolveSafePath(newPath);
    if (!fs.existsSync(fullOldPath)) {
        return err(`Error: The path ${oldPath} does not exist`);
    }
    if (fs.existsSync(fullNewPath)) {
        return err(`Error: The destination ${newPath} already exists`);
    }
    const newDir = path.dirname(fullNewPath);
    if (!fs.existsSync(newDir)) {
        fs.mkdirSync(newDir, { recursive: true });
    }
    fs.renameSync(fullOldPath, fullNewPath);
    return ok(`Successfully renamed ${oldPath} to ${newPath}`);
}
// --- MCP tool definition ---
export function executeMemoryCommand(args) {
    try {
        switch (args.command) {
            case "view":
                return handleView(args.path ?? "/memories", args.view_range);
            case "create": {
                if (!args.path || args.file_text === undefined) {
                    return err("Error: path and file_text are required for create");
                }
                return handleCreate(args.path, args.file_text);
            }
            case "str_replace": {
                if (!args.path || args.old_str === undefined || args.new_str === undefined) {
                    return err("Error: path, old_str, and new_str are required for str_replace");
                }
                return handleStrReplace(args.path, args.old_str, args.new_str);
            }
            case "insert": {
                if (!args.path || args.insert_line === undefined || args.insert_text === undefined) {
                    return err("Error: path, insert_line, and insert_text are required for insert");
                }
                return handleInsert(args.path, args.insert_line, args.insert_text);
            }
            case "delete": {
                if (!args.path)
                    return err("Error: path is required for delete");
                return handleDelete(args.path);
            }
            case "rename": {
                if (!args.old_path || !args.new_path) {
                    return err("Error: old_path and new_path are required for rename");
                }
                return handleRename(args.old_path, args.new_path);
            }
            default:
                return err(`Unknown memory command: ${String(args.command)}`);
        }
    }
    catch (e) {
        return err(`Memory error: ${e instanceof Error ? e.message : String(e)}`);
    }
}
export const memoryTool = tool("memory", "Persistent memory storage. Use this to store and retrieve information across conversations. ALWAYS view /memories at the start of each wake cycle to check for earlier context. Commands: view, create, str_replace, insert, delete, rename.", {
    command: z
        .enum(["view", "create", "str_replace", "insert", "delete", "rename"])
        .describe("Memory command to execute"),
    path: z
        .string()
        .optional()
        .describe("Path starting with /memories (e.g. /memories/notes.txt)"),
    file_text: z
        .string()
        .optional()
        .describe("File content for 'create' command"),
    old_str: z
        .string()
        .optional()
        .describe("Text to find for 'str_replace' command"),
    new_str: z
        .string()
        .optional()
        .describe("Replacement text for 'str_replace' command"),
    insert_line: z
        .number()
        .optional()
        .describe("Line number for 'insert' command (0-indexed)"),
    insert_text: z
        .string()
        .optional()
        .describe("Text to insert for 'insert' command"),
    view_range: z
        .array(z.number())
        .length(2)
        .optional()
        .describe("Line range [start, end] for 'view' command (-1 for end of file)"),
    old_path: z
        .string()
        .optional()
        .describe("Source path for 'rename' command"),
    new_path: z
        .string()
        .optional()
        .describe("Destination path for 'rename' command"),
}, (args) => Promise.resolve(executeMemoryCommand(args)));
//# sourceMappingURL=memory.js.map