import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
//#region extensions/crypto/src/services/thread-bindings.ts
/**
* Thread Bindings Service — bind agent configurations to Telegram topics.
*
* Each Telegram topic can have its own:
* - Persona override (e.g. "degen" in Trading, "technical" in Research)
* - Tool restrictions (e.g. only read-only tools in Research)
* - Safety mode override (e.g. readonly in Research, safe in Trading)
* - Custom system prompt additions
*
* This enables multi-persona or multi-task separation within a single
* Telegram group. Pairs with ForumTopicsService for topic management.
*
* Bindings are keyed by chatId + threadId, stored in memory (reset on restart).
* Future: persist to MEMORY.md or a dedicated config file.
*
* Usage:
*   const bindings = getThreadBindings();
*   bindings.bind(chatId, threadId, { persona: 'degen', safetyMode: 'danger' });
*   const config = bindings.getBinding(chatId, threadId);
*/
/** Default bindings for well-known topic purposes */
const DEFAULT_BINDINGS = {
	trading: {
		persona: "professional",
		safetyMode: "safe"
	},
	portfolio: {
		safetyMode: "readonly",
		systemPromptExtra: "This topic is for portfolio overview. Show balances, positions, PnL. Do not execute trades here."
	},
	research: {
		persona: "technical",
		safetyMode: "readonly",
		systemPromptExtra: "This topic is for research and analysis. Provide data-heavy responses with charts, metrics, and comparisons."
	},
	alerts: {
		safetyMode: "readonly",
		systemPromptExtra: "This topic receives automated alerts and notifications. Keep responses brief."
	},
	governance: {
		safetyMode: "safe",
		systemPromptExtra: "This topic is for DAO governance. Focus on proposal analysis, voting power, and delegation."
	},
	social: {
		safetyMode: "safe",
		systemPromptExtra: "This topic is for social media management (Farcaster, X/Twitter). Draft posts, check engagement."
	},
	admin: {
		safetyMode: "safe",
		systemPromptExtra: "This topic is for bot administration. Handle /setup, /flykeys, mode changes, and configuration."
	}
};
var ThreadBindingsService = class {
	/** chatId:threadId → binding */
	bindings = /* @__PURE__ */ new Map();
	/**
	* Create or update a binding for a specific topic.
	*/
	bind(chatId, threadId, config) {
		const key = this.key(chatId, threadId);
		const merged = {
			...this.bindings.get(key) ?? {},
			...config
		};
		this.bindings.set(key, merged);
		return merged;
	}
	/**
	* Get the binding for a specific topic.
	* Falls back to default bindings based on topic purpose.
	*/
	getBinding(chatId, threadId) {
		const key = this.key(chatId, threadId);
		return this.bindings.get(key);
	}
	/**
	* Get the effective binding — user override merged over defaults for the purpose.
	*/
	getEffectiveBinding(chatId, threadId, purpose) {
		const userBinding = this.getBinding(chatId, threadId) ?? {};
		return {
			...purpose ? DEFAULT_BINDINGS[purpose] ?? {} : {},
			...userBinding,
			purpose: userBinding.purpose ?? purpose
		};
	}
	/**
	* Remove a binding.
	*/
	unbind(chatId, threadId) {
		return this.bindings.delete(this.key(chatId, threadId));
	}
	/**
	* Check if a tool is allowed in a given topic.
	*/
	isToolAllowed(chatId, threadId, toolName, purpose) {
		const binding = this.getEffectiveBinding(chatId, threadId, purpose);
		if (binding.blockedTools?.includes(toolName)) return false;
		if (binding.allowedTools && !binding.allowedTools.includes(toolName)) return false;
		return true;
	}
	/**
	* List all bindings for a chat.
	*/
	listBindings(chatId) {
		const results = [];
		const prefix = `${chatId}:`;
		for (const [key, binding] of this.bindings) if (key.startsWith(prefix)) {
			const threadId = parseInt(key.slice(prefix.length), 10);
			if (!isNaN(threadId)) results.push({
				threadId,
				binding
			});
		}
		return results;
	}
	/**
	* Apply default bindings for a purpose to a topic (if no user override exists).
	*/
	applyDefaults(chatId, threadId, purpose) {
		const key = this.key(chatId, threadId);
		if (this.bindings.has(key)) return this.bindings.get(key);
		const defaults = DEFAULT_BINDINGS[purpose];
		if (defaults) {
			const binding = {
				...defaults,
				purpose
			};
			this.bindings.set(key, binding);
			return binding;
		}
		return { purpose };
	}
	/**
	* Reset all bindings (for testing).
	*/
	reset() {
		this.bindings.clear();
	}
	/**
	* Export all bindings for persistence.
	*/
	exportAll() {
		const results = [];
		for (const [key, binding] of this.bindings) {
			const [chatId, threadIdStr] = key.split(":");
			const threadId = parseInt(threadIdStr, 10);
			if (!isNaN(threadId)) results.push({
				chatId,
				threadId,
				binding
			});
		}
		return results;
	}
	key(chatId, threadId) {
		return `${chatId}:${threadId}`;
	}
};
let _instance = null;
function getThreadBindings() {
	if (!_instance) _instance = new ThreadBindingsService();
	return _instance;
}
function resetThreadBindings() {
	_instance = null;
}
function getBindingsStateDir() {
	return process.env.OPENCLAWNCH_TX_DIR ? join(process.env.OPENCLAWNCH_TX_DIR, "..", "thread-bindings") : join(process.env.HOME ?? "/tmp", ".openclawnch", "thread-bindings");
}
function getBindingsStatePath() {
	return join(getBindingsStateDir(), "bindings.json");
}
/** Persist all thread bindings to disk. Called on graceful shutdown. */
function persistThreadBindings() {
	if (!_instance) return;
	const entries = _instance.exportAll();
	if (entries.length === 0) return;
	const dir = getBindingsStateDir();
	if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
	writeFileSync(getBindingsStatePath(), JSON.stringify(entries, null, 2), "utf8");
}
/** Restore thread bindings from disk. Called on startup. */
function restoreThreadBindings() {
	const path = getBindingsStatePath();
	try {
		if (!existsSync(path)) return;
		const data = JSON.parse(readFileSync(path, "utf8"));
		const svc = getThreadBindings();
		for (const entry of data) svc.bind(entry.chatId, entry.threadId, entry.binding);
	} catch {}
}
//#endregion
export { ThreadBindingsService, getThreadBindings, persistThreadBindings, resetThreadBindings, restoreThreadBindings };

//# sourceMappingURL=thread-bindings.mjs.map