import { MemoryGraphDataSchema, EmbeddingIndexSchema, ConversationSummarySchema, } from "./types.js";
import fs from "node:fs";
import path from "node:path";
import { createLogger } from "../../logger.js";
// memoryIds become directory names, so anything that could escape the
// configured baseDir (path separators, leading dots, control chars) must
// be rejected. We allow letters, digits, dash, underscore, and dot — but
// disallow segments that are exactly ".", "..", or contain a slash.
const MEMORY_ID_PATTERN = /^[A-Za-z0-9._-]+$/;
function validateMemoryId(memoryId) {
    if (!memoryId ||
        memoryId === "." ||
        memoryId === ".." ||
        !MEMORY_ID_PATTERN.test(memoryId)) {
        throw new Error(`Invalid memoryId "${memoryId}". memoryIds must match ${MEMORY_ID_PATTERN} and cannot be "." or "..".`);
    }
}
export class FileMemoryStore {
    baseDir;
    /** Built once in the constructor from `logLevel`. Each line emitted
     *  is `[memory]`-prefixed so it groups with the manager's output
     *  when grepping. */
    logger;
    constructor(baseDir, logLevel) {
        this.baseDir = baseDir;
        this.logger = createLogger(logLevel ?? "info");
    }
    dir(memoryId) {
        validateMemoryId(memoryId);
        return path.join(this.baseDir, memoryId);
    }
    ensureDir(memoryId) {
        const dir = this.dir(memoryId);
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true });
            this.logger.debug(`[memory] FileMemoryStore: mkdir ${dir}`);
        }
    }
    // Run a Zod schema over `data` and throw a helpful, debuggable error
    // if the shape is wrong. We include the file path + the failing
    // field path(s) so a corrupted file can be located and inspected.
    validate(schema, data, filePath, direction) {
        const result = schema.safeParse(data);
        if (!result.success) {
            const issues = result.error.issues
                .map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
                .join("; ");
            throw new Error(`MemoryStore ${direction} schema mismatch at ${filePath}: ${issues}`);
        }
        return result.data;
    }
    async readJSON(filePath) {
        if (!fs.existsSync(filePath)) {
            this.logger.debug(`[memory] FileMemoryStore: read miss ${filePath}`);
            return null;
        }
        const content = await fs.promises.readFile(filePath, "utf-8");
        this.logger.debug(`[memory] FileMemoryStore: read ${filePath} (${content.length} bytes)`);
        return JSON.parse(content);
    }
    async writeJSON(filePath, data) {
        const serialized = JSON.stringify(data, null, 2);
        await fs.promises.writeFile(filePath, serialized, "utf-8");
        this.logger.debug(`[memory] FileMemoryStore: wrote ${filePath} (${serialized.length} bytes)`);
    }
    async loadGraph(memoryId) {
        const filePath = path.join(this.dir(memoryId), "graph.json");
        const raw = await this.readJSON(filePath);
        if (raw === null)
            return { entities: [], relations: [], nextId: 1 };
        return this.validate(MemoryGraphDataSchema, raw, filePath, "load");
    }
    async saveGraph(memoryId, graph) {
        this.ensureDir(memoryId);
        const filePath = path.join(this.dir(memoryId), "graph.json");
        this.validate(MemoryGraphDataSchema, graph, filePath, "save");
        await this.writeJSON(filePath, graph);
    }
    async loadEmbeddings(memoryId) {
        const filePath = path.join(this.dir(memoryId), "embeddings.json");
        const raw = await this.readJSON(filePath);
        if (raw === null)
            return null;
        return this.validate(EmbeddingIndexSchema, raw, filePath, "load");
    }
    async saveEmbeddings(memoryId, index) {
        this.ensureDir(memoryId);
        const filePath = path.join(this.dir(memoryId), "embeddings.json");
        this.validate(EmbeddingIndexSchema, index, filePath, "save");
        await this.writeJSON(filePath, index);
    }
    async loadSummary(memoryId) {
        const filePath = path.join(this.dir(memoryId), "summary.json");
        const raw = await this.readJSON(filePath);
        if (raw === null)
            return null;
        return this.validate(ConversationSummarySchema, raw, filePath, "load");
    }
    async saveSummary(memoryId, summary) {
        this.ensureDir(memoryId);
        const filePath = path.join(this.dir(memoryId), "summary.json");
        this.validate(ConversationSummarySchema, summary, filePath, "save");
        await this.writeJSON(filePath, summary);
    }
}
