/**
 * Signet Agent Memory Hook
 *
 * Unified memory system for all AI harnesses (OpenClaw, Claude Code, OpenCode, Codex)
 * Features: hybrid search (vector + BM25), auto-embedding, cross-harness persistence
 * 
 * Spec: Signet v0.2.1 - https://signetai.sh
 */
import { spawn } from "node:child_process";
import path from "node:path";
import os from "node:os";
import fs from "node:fs/promises";

const MEMORY_SCRIPT = path.join(os.homedir(), ".agents/memory/scripts/memory.py");
const MEMORY_MD_PATH = path.join(os.homedir(), ".agents/memory/MEMORY.md");

/**
 * Run memory.py command and return stdout
 * @param {string[]} args
 * @returns {Promise<string>}
 */
async function runMemoryScript(args) {
    return new Promise((resolve, reject) => {
        const proc = spawn("python3", [MEMORY_SCRIPT, ...args], {
            timeout: 5000,
        });

        let stdout = "";
        let stderr = "";

        proc.stdout.on("data", (data) => {
            stdout += data.toString();
        });

        proc.stderr.on("data", (data) => {
            stderr += data.toString();
        });

        proc.on("close", (code) => {
            if (code === 0) {
                resolve(stdout.trim());
            } else {
                reject(new Error(stderr || `memory.py exited with code ${code}`));
            }
        });

        proc.on("error", (err) => {
            reject(err);
        });
    });
}

/**
 * Read recent messages from session file for memory extraction
 */
async function getRecentSessionContent(sessionFilePath) {
    try {
        const content = await fs.readFile(sessionFilePath, "utf-8");
        const lines = content.trim().split("\n");
        // Get last 20 lines (recent conversation)
        const recentLines = lines.slice(-20);
        
        const messages = [];
        for (const line of recentLines) {
            try {
                const entry = JSON.parse(line);
                if (entry.type === "message" && entry.message) {
                    const msg = entry.message;
                    const role = msg.role;
                    if ((role === "user" || role === "assistant") && msg.content) {
                        const text = Array.isArray(msg.content)
                            ? msg.content.find((c) => c.type === "text")?.text
                            : msg.content;
                        if (text && !text.startsWith("/")) {
                            messages.push(`${role}: ${text}`);
                        }
                    }
                }
            } catch {
                // Skip invalid JSON lines
            }
        }
        return messages.join("\n");
    } catch {
        return null;
    }
}

/**
 * Handle /remember command
 */
async function handleRemember(event) {
    const context = event.context || {};
    const args = context.args || "";
    
    if (!args.trim()) {
        event.messages.push("🧠 Usage: /remember <content>\n\nPrefixes:\n- `critical:` for pinned memories\n- `[tag1,tag2]:` for tagged memories");
        return;
    }

    try {
        // Detect harness from session key or default to openclaw
        const harness = event.sessionKey?.includes("claude") ? "claude-code" 
                      : event.sessionKey?.includes("opencode") ? "opencode"
                      : "openclaw";
        
        const result = await runMemoryScript([
            "save",
            "--mode", "explicit",
            "--who", harness,
            "--project", context.cwd || os.homedir(),
            "--content", args.trim()
        ]);
        event.messages.push(`🧠 ${result}`);
    } catch (err) {
        event.messages.push(`🧠 Error saving memory: ${err.message}`);
    }
}

/**
 * Handle /recall command
 */
async function handleRecall(event) {
    const context = event.context || {};
    const args = context.args || "";
    
    if (!args.trim()) {
        event.messages.push("🧠 Usage: /recall <search query>");
        return;
    }

    try {
        const result = await runMemoryScript(["query", args.trim(), "--limit", "10"]);
        if (result) {
            event.messages.push(`🧠 Memory search results:\n\n${result}`);
        } else {
            event.messages.push("🧠 No memories found matching your query.");
        }
    } catch (err) {
        event.messages.push(`🧠 Error querying memory: ${err.message}`);
    }
}

/**
 * Run the sync-memory-context script to update AGENTS.md
 */
async function runSyncScript() {
    const syncScript = path.join(os.homedir(), "clawd/scripts/sync-memory-context.sh");
    return new Promise((resolve, reject) => {
        const proc = spawn("bash", [syncScript], { timeout: 5000 });
        let stdout = "";
        let stderr = "";
        proc.stdout.on("data", (data) => { stdout += data.toString(); });
        proc.stderr.on("data", (data) => { stderr += data.toString(); });
        proc.on("close", (code) => {
            if (code === 0) resolve(stdout.trim());
            else reject(new Error(stderr || `sync script exited with code ${code}`));
        });
        proc.on("error", reject);
    });
}

/**
 * Handle /context command - load MEMORY.md + db memories
 * This gives clawdbot the same context that claude code gets at session start
 * Also syncs memory into AGENTS.md for future sessions
 */
async function handleContext(event) {
    // First, sync memory to AGENTS.md
    try {
        await runSyncScript();
        console.log("[agent-memory] Synced memory context to AGENTS.md");
    } catch (err) {
        console.warn("[agent-memory] Failed to sync memory to AGENTS.md:", err.message);
    }

    try {
        const result = await runMemoryScript([
            "load",
            "--mode", "session-start",
            "--project", os.homedir()
        ]);
        if (result) {
            event.messages.push(`🧠 **Memory Context Loaded**\n\n${result}`);
        } else {
            event.messages.push("🧠 No memory context available.");
        }
    } catch (err) {
        // fallback: try to read MEMORY.md directly
        try {
            const currentMd = await fs.readFile(MEMORY_MD_PATH, "utf-8");
            if (currentMd.trim()) {
                event.messages.push(`🧠 **Memory Context**\n\n${currentMd.trim()}`);
            } else {
                event.messages.push("🧠 No memory context available.");
            }
        } catch {
            event.messages.push(`🧠 Error loading context: ${err.message}`);
        }
    }
}

/**
 * Handle /new command - save session context before reset
 */
async function handleNew(event) {
    const context = event.context || {};
    const sessionEntry = context.previousSessionEntry || context.sessionEntry || {};
    const sessionFile = sessionEntry.sessionFile;

    if (!sessionFile) {
        console.log("[agent-memory] No session file found, skipping auto-save");
        return;
    }

    try {
        const sessionContent = await getRecentSessionContent(sessionFile);
        if (!sessionContent || sessionContent.length < 100) {
            console.log("[agent-memory] Session too short for auto-extraction");
            return;
        }

        // Save a summary marker that this session ended
        const now = new Date().toISOString();
        const summaryContent = `[clawdbot-session-end]: Session ended at ${now}. Last conversation:\n${sessionContent.slice(0, 500)}`;
        
        // Detect harness from session key
        const harness = event.sessionKey?.includes("claude") ? "claude-code" 
                      : event.sessionKey?.includes("opencode") ? "opencode"
                      : "openclaw";
        
        await runMemoryScript([
            "save",
            "--mode", "explicit", 
            "--who", harness,
            "--project", context.cwd || "global",
            "--content", summaryContent
        ]);

        console.log("[agent-memory] Session context saved to memory database");
    } catch (err) {
        console.error("[agent-memory] Failed to save session memory:", err.message);
    }
}

/**
 * Main hook handler
 */
const agentMemoryHandler = async (event) => {
    // Check if memory script exists
    try {
        await fs.access(MEMORY_SCRIPT);
    } catch {
        console.warn("[agent-memory] Memory script not found at", MEMORY_SCRIPT);
        return;
    }

    if (event.type !== "command") {
        return;
    }

    switch (event.action) {
        case "remember":
            await handleRemember(event);
            break;
        case "recall":
            await handleRecall(event);
            break;
        case "context":
            await handleContext(event);
            break;
        case "new":
            await handleNew(event);
            break;
    }
};

export default agentMemoryHandler;
