import path from "node:path";
import { pathToFileURL } from "node:url";
const services = new Map();
export function getTencentMemoryService(config, provider, state) {
    const key = `${config.dataDir}|${config.projectPath}|${provider?.model ?? "none"}`;
    let service = services.get(key);
    if (!service) {
        service = new TencentMemoryService(config, provider?.model ?? config.model, (provider ? providerConfigFromClient(provider) : null) ?? config.provider, state);
        services.set(key, service);
    }
    return service;
}
function providerConfigFromClient(provider) {
    const maybe = provider;
    return maybe.config ?? null;
}
export class TencentMemoryService {
    config;
    model;
    providerConfig;
    state;
    hooks = new Map();
    tools = new Map();
    initialized = false;
    disabledReason = "";
    initializing;
    constructor(config, model, providerConfig, state) {
        this.config = config;
        this.model = model;
        this.providerConfig = providerConfig;
        this.state = state;
    }
    get status() {
        const cfg = this.memoryPluginConfig();
        const capture = cfg.capture;
        const recall = cfg.recall;
        const extraction = cfg.extraction;
        const embedding = cfg.embedding;
        return {
            enabled: this.isEnabled(),
            initialized: this.initialized,
            dataDir: this.pluginStateDir(),
            vendor: "TencentDB-Agent-Memory 0.3.6 (MIT, vendored dist)",
            tools: [...this.tools.keys()].sort(),
            error: this.disabledReason || undefined,
            config: {
                capture: Boolean(capture?.enabled),
                recall: Boolean(recall?.enabled),
                extraction: Boolean(extraction?.enabled),
                storeBackend: cfg.storeBackend,
                embeddingProvider: String(embedding?.provider ?? "none"),
            },
        };
    }
    async recall(userText, history, runId) {
        if (!this.isEnabled())
            return undefined;
        await this.ensureInitialized(runId);
        if (!this.initialized)
            return undefined;
        const event = {
            prompt: userText,
            messages: history.map((message, index) => ({
                id: `history_${index}`,
                role: message.role,
                content: message.content,
                timestamp: Date.now() - (history.length - index + 1),
            })),
        };
        const result = await this.runHooks("before_prompt_build", event, this.hookContext());
        return isRecallResult(result) ? result : undefined;
    }
    async initialize(runId) {
        await this.ensureInitialized(runId);
    }
    async captureTurn(input) {
        if (!this.isEnabled())
            return undefined;
        await this.ensureInitialized(input.runId);
        if (!this.initialized)
            return undefined;
        const now = Date.now();
        const event = {
            success: input.success,
            messages: [
                {
                    id: `user_${now}`,
                    role: "user",
                    content: input.userText,
                    timestamp: now,
                },
                {
                    id: `assistant_${now}`,
                    role: "assistant",
                    content: input.assistantText,
                    timestamp: now + 1,
                },
            ],
        };
        return this.runHooks("agent_end", event, this.hookContext());
    }
    async searchMemory(params) {
        await this.ensureInitialized();
        const tool = this.tools.get("tdai_memory_search");
        if (!tool)
            throw new Error("tdai_memory_search is not available; TencentDB-Agent-Memory is disabled or not initialized.");
        return this.normalizeToolResult(await tool.execute("deepseekcode_memory_search", {
            query: params.query,
            limit: params.limit,
            type: params.memoryType,
            scene: params.scene,
        }));
    }
    async searchConversations(params) {
        await this.ensureInitialized();
        const tool = this.tools.get("tdai_conversation_search");
        if (!tool)
            throw new Error("tdai_conversation_search is not available; TencentDB-Agent-Memory is disabled or not initialized.");
        return this.normalizeToolResult(await tool.execute("deepseekcode_conversation_search", {
            query: params.query,
            limit: params.limit,
            session_key: params.sessionKey,
        }));
    }
    normalizeToolResult(raw) {
        if (!raw || typeof raw !== "object")
            return { text: String(raw ?? "") };
        const record = raw;
        const text = record.content
            ?.map((item) => item.type === "text" && typeof item.text === "string" ? item.text : "")
            .filter(Boolean)
            .join("\n") ?? JSON.stringify(raw, null, 2);
        return { text, details: record.details };
    }
    async ensureInitialized(runId) {
        if (this.initialized || this.initializing || !this.isEnabled()) {
            await this.initializing;
            return;
        }
        this.initializing = this.loadPlugin(runId)
            .then(() => {
            this.initialized = true;
        })
            .catch((error) => {
            this.disabledReason = error instanceof Error ? error.message : String(error);
            this.state?.appendEvent(runId ?? null, "tdai_memory_init_failed", {
                message: this.disabledReason,
            });
        })
            .finally(() => {
            this.initializing = undefined;
        });
        await this.initializing;
    }
    async loadPlugin(runId) {
        const vendorUrl = new URL("../../vendor/tencentdb-agent-memory/index.mjs", import.meta.url);
        const module = await import(vendorUrl.href);
        if (typeof module.default !== "function") {
            throw new Error("TencentDB-Agent-Memory vendor bundle does not export a plugin register function.");
        }
        module.default(this.pluginApi());
        this.state?.appendEvent(runId ?? null, "tdai_memory_initialized", this.status);
    }
    pluginApi() {
        const logger = this.logger();
        return {
            registrationMode: "runtime",
            pluginConfig: this.memoryPluginConfig(),
            config: {},
            logger,
            runtime: {
                version: "2026.4.24",
                config: {},
                state: {
                    resolveStateDir: () => this.pluginStateDir(),
                },
                agent: {
                    runEmbeddedPiAgent: async () => {
                        throw new Error("DeepSeekCode uses TencentDB-Agent-Memory standalone LLM mode, not OpenClaw embedded agents.");
                    },
                },
            },
            registerTool: (tool) => {
                if (tool?.name && typeof tool.execute === "function")
                    this.tools.set(tool.name, tool);
            },
            registerCli: () => undefined,
            on: (name, handler) => {
                const list = this.hooks.get(name) ?? [];
                list.push(handler);
                this.hooks.set(name, list);
            },
        };
    }
    memoryPluginConfig() {
        const provider = this.providerConfig;
        const llmEnabled = Boolean(provider?.apiKey && provider.baseUrl && provider.model);
        const extractionEnabled = boolEnv("DEEPSEEKCODE_TDAI_EXTRACTION", true) && llmEnabled;
        const embeddingProvider = env("DEEPSEEKCODE_TDAI_EMBEDDING_PROVIDER") ?? "none";
        return {
            capture: {
                enabled: boolEnv("DEEPSEEKCODE_TDAI_CAPTURE", true),
                l0l1RetentionDays: numberEnv("DEEPSEEKCODE_TDAI_RETENTION_DAYS", 0),
            },
            recall: {
                enabled: boolEnv("DEEPSEEKCODE_TDAI_RECALL", true),
                maxResults: numberEnv("DEEPSEEKCODE_TDAI_RECALL_RESULTS", 5),
                maxTotalRecallChars: numberEnv("DEEPSEEKCODE_TDAI_RECALL_CHARS", 3000),
            },
            extraction: {
                enabled: extractionEnabled,
                enableDedup: true,
                maxMemoriesPerSession: numberEnv("DEEPSEEKCODE_TDAI_MAX_MEMORIES", 20),
                model: `deepseek/${provider?.model ?? this.model}`,
            },
            persona: {
                triggerEveryN: numberEnv("DEEPSEEKCODE_TDAI_PERSONA_TRIGGER", 50),
                maxScenes: numberEnv("DEEPSEEKCODE_TDAI_MAX_SCENES", 20),
                model: `deepseek/${provider?.model ?? this.model}`,
            },
            pipeline: {
                everyNConversations: numberEnv("DEEPSEEKCODE_TDAI_EVERY_N", 5),
                enableWarmup: boolEnv("DEEPSEEKCODE_TDAI_WARMUP", true),
                l1IdleTimeoutSeconds: numberEnv("DEEPSEEKCODE_TDAI_L1_IDLE_SECONDS", 600),
            },
            storeBackend: env("DEEPSEEKCODE_TDAI_STORE") === "tcvdb" ? "tcvdb" : "sqlite",
            tcvdb: {
                url: env("DEEPSEEKCODE_TCVDB_URL") ?? "",
                username: env("DEEPSEEKCODE_TCVDB_USERNAME") ?? "root",
                apiKey: env("DEEPSEEKCODE_TCVDB_API_KEY") ?? "",
                database: env("DEEPSEEKCODE_TCVDB_DATABASE") ?? "",
                alias: env("DEEPSEEKCODE_TCVDB_ALIAS") ?? "DeepSeekCode",
                embeddingModel: env("DEEPSEEKCODE_TCVDB_EMBEDDING_MODEL") ?? "bge-large-zh",
            },
            embedding: {
                enabled: embeddingProvider !== "none",
                provider: embeddingProvider,
                baseUrl: env("DEEPSEEKCODE_TDAI_EMBEDDING_BASE_URL") ?? "",
                apiKey: env("DEEPSEEKCODE_TDAI_EMBEDDING_API_KEY") ?? "",
                model: env("DEEPSEEKCODE_TDAI_EMBEDDING_MODEL") ?? "",
                dimensions: numberEnv("DEEPSEEKCODE_TDAI_EMBEDDING_DIMENSIONS", 0),
                sendDimensions: boolEnv("DEEPSEEKCODE_TDAI_EMBEDDING_SEND_DIMENSIONS", true),
            },
            bm25: {
                enabled: true,
                language: "zh",
            },
            llm: {
                enabled: llmEnabled,
                baseUrl: provider?.baseUrl ?? "",
                apiKey: provider?.apiKey ?? "",
                model: provider?.model ?? this.model,
                maxTokens: numberEnv("DEEPSEEKCODE_TDAI_LLM_MAX_TOKENS", 2048),
                timeoutMs: numberEnv("DEEPSEEKCODE_TDAI_LLM_TIMEOUT_MS", 120000),
            },
            offload: {
                enabled: false,
            },
            report: {
                enabled: false,
                type: "local",
            },
        };
    }
    async runHooks(name, event, context) {
        let lastResult;
        for (const handler of this.hooks.get(name) ?? []) {
            const result = await handler(event, context);
            if (result !== undefined)
                lastResult = result;
        }
        return lastResult;
    }
    hookContext() {
        const sessionKey = this.currentSessionKey();
        return {
            sessionKey,
            sessionId: sessionKey,
            agentName: "deepseekcode",
        };
    }
    currentSessionKey() {
        const safeProject = this.config.projectPath.replace(/[^a-zA-Z0-9_.-]+/g, "_").slice(-96);
        return `deepseekcode_${safeProject}`;
    }
    pluginStateDir() {
        return path.join(this.config.dataDir, "tdai");
    }
    isEnabled() {
        return env("DEEPSEEKCODE_TDAI_MEMORY") !== "off";
    }
    logger() {
        const append = (level, message) => {
            if (level === "error" || level === "warn") {
                this.state?.appendEvent(null, "tdai_memory_log", { level, message });
            }
        };
        return {
            debug: (_message) => undefined,
            info: (_message) => undefined,
            warn: (message) => append("warn", message),
            error: (message) => append("error", message),
        };
    }
}
function isRecallResult(value) {
    return Boolean(value && typeof value === "object" && ("prependContext" in value ||
        "appendSystemContext" in value ||
        "recalledL1Memories" in value ||
        "recalledL3Persona" in value));
}
function env(name) {
    const value = process.env[name]?.trim();
    return value ? value : undefined;
}
function boolEnv(name, fallback) {
    const value = env(name);
    if (!value)
        return fallback;
    return !["0", "false", "off", "no"].includes(value.toLowerCase());
}
function numberEnv(name, fallback) {
    const value = env(name);
    if (!value)
        return fallback;
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : fallback;
}
export function tencentMemoryVendorUrl() {
    return pathToFileURL(path.join("src", "vendor", "tencentdb-agent-memory", "README.upstream.md")).href;
}
