import { readFile, writeFile, mkdir } from "fs/promises";
import { join, dirname, resolve, relative, isAbsolute } from "path";
import { getProjectRoot } from "../util/fs.js";
import { loadMemorySettings, summarizeMemorySettings, } from "./memory-config.js";
import { loadMemorySettings as loadMemorySettingsForLink } from "./memory-config.js";
/**
 * Idempotent, non-fatal finalizer: links a finalized run manifest into the
 * local graph memory backend. Returns true when the active backend linked the
 * run, false when no graph backend is available (e.g. file-only backend).
 *
 * Only ids/status/paths/sha256/counts are persisted — never raw evidence or
 * decision bodies, and never secrets. Callers should still wrap this in their
 * own try/catch so a graph write failure can never fail a run.
 */
export async function linkRunToGraph(runId, manifest, options = {}) {
    const settings = await loadMemorySettingsForLink(options.projectRoot, options.env);
    const store = await createMemoryStore(settings.backend, options);
    if (!store || typeof store.linkRun !== "function")
        return false;
    await store.linkRun(runId, manifest);
    return true;
}
export async function createMemoryStore(backend, options = {}) {
    if (backend === "local_graph") {
        const { LocalGraphMemoryStore } = await import("./local-graph-memory-store.js");
        return LocalGraphMemoryStore.create(options);
    }
    if (backend === "kuzu") {
        const { KuzuMemoryStore } = await import("./kuzu-memory-store.js");
        return KuzuMemoryStore.create(options);
    }
    return null;
}
export class MemoryStore {
    options;
    basePath;
    settingsPromise;
    graphStorePromise;
    graphStatePromise;
    constructor(basePath, options = {}) {
        this.options = options;
        this.basePath = resolve(basePath);
    }
    async ensureGraphState() {
        this.graphStatePromise ??= (async () => {
            const graph = await this.getGraphStore();
            if (!graph)
                return;
            const settings = await this.getSettings();
            try {
                await graph.write("project", JSON.stringify({ key: settings.project.key, name: settings.project.name }));
            }
            catch (err) {
                if (graph.strict)
                    throw err;
            }
        })();
        return this.graphStatePromise;
    }
    safePath(inputPath) {
        if (isAbsolute(inputPath)) {
            throw new Error(`MemoryStore: absolute paths are not allowed: ${inputPath}`);
        }
        const full = resolve(join(this.basePath, inputPath));
        const rel = relative(this.basePath, full);
        if (rel.startsWith("..") || rel === "..") {
            throw new Error(`MemoryStore: path traversal detected: ${inputPath}`);
        }
        return full;
    }
    async read(path) {
        const full = this.safePath(path);
        const graph = await this.getGraphStore();
        if (graph) {
            try {
                const graphContent = await graph.read(path);
                if (graphContent)
                    return graphContent;
                const fileContent = await this.readFileContent(full);
                if (fileContent && graph.migrateFiles) {
                    await graph.write(path, fileContent);
                    return fileContent;
                }
                if (graph.strict)
                    return graphContent;
                return fileContent;
            }
            catch (err) {
                if (graph.strict)
                    throw err;
            }
        }
        return this.readFileContent(full);
    }
    async write(path, content) {
        await this.ensureGraphState();
        const full = this.safePath(path);
        const graph = await this.getGraphStore();
        if (graph) {
            try {
                await graph.write(path, content);
            }
            catch (err) {
                if (graph.strict)
                    throw err;
            }
            if (!graph.mirrorFiles)
                return;
        }
        await this.writeFileContent(full, content);
    }
    async append(path, content) {
        const existing = await this.read(path);
        await this.write(path, existing ? `${existing}\n${content}` : content);
    }
    async readRunMemory(runId, path) {
        const { full, graphPath } = this.safeRunPath(runId, path);
        const graph = await this.getGraphStore();
        if (graph) {
            try {
                const graphContent = await graph.read(graphPath);
                if (graphContent)
                    return graphContent;
                const fileContent = await this.readFileContent(full);
                if (fileContent && graph.migrateFiles) {
                    await graph.write(graphPath, fileContent);
                    return fileContent;
                }
                if (graph.strict)
                    return graphContent;
                return fileContent;
            }
            catch (err) {
                if (graph.strict)
                    throw err;
            }
        }
        return this.readFileContent(full);
    }
    async writeRunMemory(runId, path, content) {
        await this.ensureGraphState();
        const { full, graphPath } = this.safeRunPath(runId, path);
        const graph = await this.getGraphStore();
        if (graph) {
            try {
                await graph.write(graphPath, content);
            }
            catch (err) {
                if (graph.strict)
                    throw err;
            }
            if (!graph.mirrorFiles)
                return;
        }
        await this.writeFileContent(full, content);
    }
    async search(query, limit) {
        const graph = await this.getGraphStore();
        if (!graph)
            return [];
        return graph.search(query, limit);
    }
    async ontology() {
        const graph = await this.getGraphStore();
        return typeof graph?.ontology === "function" ? graph.ontology() : null;
    }
    async mindmap(query, limit) {
        const graph = await this.getGraphStore();
        return typeof graph?.mindmap === "function" ? graph.mindmap(query, limit) : null;
    }
    async graphQuery(query) {
        const graph = await this.getGraphStore();
        if (typeof graph?.graphQuery !== "function") {
            throw new Error("omk_graph_query requires a graph memory backend (local_graph or kuzu)");
        }
        return graph.graphQuery(query);
    }
    async status() {
        const graph = await this.getGraphStore();
        if (graph)
            return graph.status;
        return summarizeMemorySettings(await this.getSettings());
    }
    async readFileContent(fullPath) {
        try {
            return await readFile(fullPath, "utf-8");
        }
        catch {
            return "";
        }
    }
    async writeFileContent(fullPath, content) {
        await mkdir(dirname(fullPath), { recursive: true });
        await writeFile(fullPath, content, "utf-8");
    }
    safeRunPath(runId, path) {
        if (runId.includes("..") || runId.startsWith("/") || runId.startsWith("\\")) {
            throw new Error(`MemoryStore: invalid runId: ${runId}`);
        }
        const root = this.options.projectRoot ?? getProjectRoot();
        const runBase = resolve(join(root, ".omk", "runs", runId, "memory"));
        const full = resolve(join(runBase, path));
        const rel = relative(runBase, full);
        if (rel.startsWith("..") || rel === "..") {
            throw new Error(`MemoryStore: path traversal detected: ${path}`);
        }
        return { full, graphPath: join("runs", runId, path).replace(/\\/g, "/") };
    }
    async getSettings() {
        const env = this.options.sessionId
            ? { ...(this.options.env ?? process.env), OMK_SESSION_ID: this.options.sessionId }
            : this.options.env;
        this.settingsPromise ??= loadMemorySettings(this.options.projectRoot, env);
        return this.settingsPromise;
    }
    async getGraphStore() {
        this.graphStorePromise ??= (async () => {
            const settings = await this.getSettings();
            return createMemoryStore(settings.backend, {
                projectRoot: this.options.projectRoot,
                sessionId: this.options.sessionId,
                source: this.options.source,
                env: this.options.env,
            });
        })();
        return this.graphStorePromise;
    }
}
