import fs from "node:fs";
import path from "node:path";
import { installSkillMarkdown } from "./skill-executor.js";
const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif"]);
const SKILL_EXTENSIONS = new Set([".md", ".markdown"]);
const READ_TEXT_MAX_BYTES = 256 * 1024;
/**
 * Resolve a user-supplied attachment path against the workspace
 * attachments/ root. Rejects absolute paths, drive-letter paths, and
 * anything that would escape via `..`. Strips a leading "attachments/"
 * so the LLM may use either form.
 */
export function resolveAttachmentPath(workspace, input) {
    if (typeof input !== "string" || !input) {
        throw new Error("attachment path is required");
    }
    // Reject absolute paths on the raw input BEFORE any stripping. Otherwise
    // "/tmp/a.txt" would slip past by becoming the relative "tmp/a.txt".
    if (path.isAbsolute(input) || /^[A-Za-z]:[\\/]/.test(input)) {
        throw new Error(`path '${input}' must be relative to attachments/ (got absolute path)`);
    }
    // Accept both "foo.txt" and "attachments/foo.txt" (user-facing preambles
    // label files with the attachments/ prefix, so the LLM mirrors that).
    const rel = input.replace(/^\.\/+/, "").replace(/^attachments\/+/, "");
    const root = path.resolve(workspace, "attachments");
    const abs = path.resolve(root, rel);
    if (abs !== root && !abs.startsWith(root + path.sep)) {
        throw new Error(`path '${input}' escapes attachments/ directory — only names inside attachments/ are allowed`);
    }
    return abs;
}
export async function executeAttachmentAction(deps, action, args) {
    try {
        const root = path.resolve(deps.workspace, "attachments");
        switch (action) {
            case "list": {
                if (!fs.existsSync(root))
                    return "(no attachments)";
                const entries = fs
                    .readdirSync(root)
                    .filter((name) => !name.startsWith("."))
                    .map((name) => {
                    const stat = fs.statSync(path.join(root, name));
                    return {
                        name,
                        size: stat.size,
                        mtime: stat.mtime.toISOString(),
                        isDir: stat.isDirectory(),
                    };
                })
                    .filter((e) => !e.isDir)
                    .sort((a, b) => (a.mtime < b.mtime ? 1 : -1));
                if (entries.length === 0)
                    return "(no attachments)";
                return entries
                    .map((e) => `- ${e.name}  (${e.size} bytes, ${e.mtime})`)
                    .join("\n");
            }
            case "read_text": {
                const abs = resolveAttachmentPath(deps.workspace, args.path);
                if (!fs.existsSync(abs))
                    return `File not found: ${args.path}`;
                const stat = fs.statSync(abs);
                if (stat.isDirectory())
                    return `Not a file: ${args.path}`;
                if (stat.size > READ_TEXT_MAX_BYTES) {
                    return `File too large for read_text (${stat.size} bytes; cap ${READ_TEXT_MAX_BYTES}). Use a smaller file or process it externally.`;
                }
                const fd = fs.openSync(abs, "r");
                try {
                    const probeLen = Math.min(stat.size, 8192);
                    const probe = Buffer.alloc(probeLen);
                    fs.readSync(fd, probe, 0, probeLen, 0);
                    if (probe.includes(0)) {
                        return `File appears to be binary (null bytes detected); read_text only handles text files.`;
                    }
                }
                finally {
                    fs.closeSync(fd);
                }
                return fs.readFileSync(abs, "utf-8");
            }
            case "send": {
                const abs = resolveAttachmentPath(deps.workspace, args.path);
                if (!fs.existsSync(abs))
                    return `File not found: ${args.path}`;
                const stat = fs.statSync(abs);
                if (stat.isDirectory())
                    return `Not a file: ${args.path}`;
                const sender = deps.getAttachmentSender();
                if (!sender) {
                    return `Cannot send file: no frontend is currently registered to deliver attachments (are you running with Telegram enabled?).`;
                }
                const ext = path.extname(abs).toLowerCase();
                const asImage = typeof args.asImage === "boolean"
                    ? args.asImage
                    : IMAGE_EXTENSIONS.has(ext);
                const caption = typeof args.caption === "string" ? args.caption : undefined;
                try {
                    await sender({ path: abs, caption, asImage });
                }
                catch (err) {
                    return `Failed to send ${path.basename(abs)}: ${err instanceof Error ? err.message : String(err)}`;
                }
                return `Sent ${path.basename(abs)} to user${asImage ? " as photo" : " as document"}${caption ? " with caption." : "."}`;
            }
            case "install_skill": {
                const abs = resolveAttachmentPath(deps.workspace, args.path);
                if (!fs.existsSync(abs))
                    return `File not found: ${args.path}`;
                const stat = fs.statSync(abs);
                if (stat.isDirectory())
                    return `Not a file: ${args.path}`;
                const ext = path.extname(abs).toLowerCase();
                if (!SKILL_EXTENSIONS.has(ext)) {
                    return `Skill files must be markdown (.md); got '${ext}'.`;
                }
                const content = fs.readFileSync(abs, "utf-8");
                const overrideName = typeof args.skillName === "string" && args.skillName.trim()
                    ? args.skillName.trim()
                    : undefined;
                return installSkillMarkdown(deps.skillGenerator, content, {
                    skillName: overrideName,
                    overwrite: args.overwrite === true,
                    sourceName: path.basename(abs, path.extname(abs)),
                });
            }
            default:
                return `Unknown attachment action: ${action}`;
        }
    }
    catch (err) {
        return `Error: ${err instanceof Error ? err.message : String(err)}`;
    }
}
//# sourceMappingURL=attachment-executor.js.map