import { readdir, readFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { getSession } from "../lib/neo4j.js";
import { ATTACHMENTS_ROOT, } from "../lib/attachments.js";
export async function memoryListAttachments(accountId) {
    const t0 = Date.now();
    const log = (stage, detail) => console.error(`[memory-list-attachments] [${accountId.slice(0, 8)}] ${stage}${detail ? ` — ${detail}` : ""} (${Date.now() - t0}ms)`);
    log("start");
    const accountDir = resolve(ATTACHMENTS_ROOT, accountId);
    if (!existsSync(accountDir)) {
        log("empty", "no attachments directory");
        return { entries: [], totalFiles: 0, ingestedCount: 0, skippedCount: 0 };
    }
    // Read all attachment subdirectories
    const subdirs = await readdir(accountDir);
    const entries = [];
    let skippedCount = 0;
    for (const subdir of subdirs) {
        const metaPath = resolve(accountDir, subdir, `${subdir}.meta.json`);
        if (!existsSync(metaPath)) {
            skippedCount++;
            continue;
        }
        try {
            const raw = await readFile(metaPath, "utf-8");
            const meta = JSON.parse(raw);
            entries.push({
                attachmentId: meta.attachmentId,
                elementId: null,
                filename: meta.filename,
                mimeType: meta.mimeType,
                sizeBytes: meta.sizeBytes,
                storedAt: meta.storedAt,
                ingested: false,
                softDeleted: false,
            });
        }
        catch {
            log("skip", `corrupt meta.json for ${subdir}`);
            skippedCount++;
        }
    }
    if (entries.length === 0) {
        log("empty", "no valid meta.json files found");
        return { entries, totalFiles: 0, ingestedCount: 0, skippedCount };
    }
    // Cross-reference with Neo4j for ingestion status
    const session = getSession();
    try {
        const attachmentIds = entries.map((e) => e.attachmentId);
        // Include trashed nodes — their attachmentId is nulled on trash, so match
        // via _trashedKeys JSON snapshot too. softDeleted flag covers both legacy
        // deletedAt and `:Trashed` label forms.
        const result = await session.run(`MATCH (k:KnowledgeDocument { accountId: $accountId })
       WHERE k.attachmentId IN $attachmentIds
       RETURN k.attachmentId AS aid,
              elementId(k) AS elementId,
              k.deletedAt AS deletedAt,
              false AS trashed
       UNION
       MATCH (k:KnowledgeDocument:Trashed)
       WHERE k._trashedKeys IS NOT NULL
         AND ANY(aid IN $attachmentIds WHERE k._trashedKeys CONTAINS aid)
       RETURN coalesce(k.attachmentId, '') AS aid,
              elementId(k) AS elementId,
              null AS deletedAt,
              true AS trashed`, { accountId, attachmentIds });
        // Build a lookup keyed by attachmentId. For trashed rows we recover the
        // original attachmentId out of the JSON snapshot on the client — we don't
        // parse JSON in Cypher for portability.
        const ingestionMap = new Map();
        for (const record of result.records) {
            const elementId = record.get("elementId");
            const deletedAt = record.get("deletedAt");
            const trashed = record.get("trashed");
            let aid = record.get("aid");
            if (trashed) {
                // Recover attachmentId from _trashedKeys — look it up via a targeted read.
                const snap = await session.run(`MATCH (k) WHERE elementId(k) = $eid RETURN k._trashedKeys AS keysJson`, { eid: elementId });
                const keysJson = snap.records[0]?.get("keysJson");
                if (keysJson) {
                    try {
                        const parsed = JSON.parse(keysJson);
                        if (typeof parsed.attachmentId === "string")
                            aid = parsed.attachmentId;
                    }
                    catch {
                        // Malformed snapshot — skip.
                    }
                }
            }
            if (!aid || !attachmentIds.includes(aid))
                continue;
            ingestionMap.set(aid, {
                ingested: true,
                softDeleted: trashed || deletedAt != null,
                elementId,
            });
        }
        let ingestedCount = 0;
        for (const entry of entries) {
            const status = ingestionMap.get(entry.attachmentId);
            if (status) {
                entry.ingested = status.ingested;
                entry.softDeleted = status.softDeleted;
                entry.elementId = status.elementId;
                ingestedCount++;
            }
        }
        // Sort by storedAt descending (newest first)
        entries.sort((a, b) => new Date(b.storedAt).getTime() - new Date(a.storedAt).getTime());
        log("done", `${entries.length} files, ${ingestedCount} ingested, ${skippedCount} skipped`);
        return {
            entries,
            totalFiles: entries.length,
            ingestedCount,
            skippedCount,
        };
    }
    finally {
        await session.close();
    }
}
//# sourceMappingURL=memory-list-attachments.js.map