/**
 * commands-agent.js — /agent command handler for persistent agent management.
 *
 * Purpose: expose persistent agent state (identity, memory blocks, archival,
 * inbox) as an internal operator/debug command so maintainers can inspect,
 * reset, and delete named agents without touching the SQLite DB directly.
 * This is not a normal user workflow; SF's product modes are assisted build,
 * plan/discuss, and autonomous operation.
 *
 * Consumer: ops.js dispatcher for the /agent slash command.
 */

import { mkdirSync } from "node:fs";
import { join } from "node:path";
import { sfRoot } from "./paths.js";
import { getDatabase, openDatabase } from "./sf-db.js";
import { UokCoordinationStore } from "./uok/coordination-store.js";

const USAGE = `Usage: /agent <subcommand>

Subcommands:
  list                     List all registered persistent agents
  inspect <name>           Show identity, memory blocks, and inbox for an agent
  reset <name>             Clear all memory blocks and archival data for an agent
  delete <name>            Remove agent identity and all associated data`;

/**
 * Ensure the SF database is open. Returns the db handle or throws.
 *
 * Purpose: guard every agent command against missing DB state so they fail
 * with a clear message rather than a cryptic internal error.
 *
 * Consumer: every handleAgent subcommand.
 */
function ensureDb(basePath) {
	const dir = sfRoot(basePath);
	mkdirSync(dir, { recursive: true });
	const dbPath = join(dir, "sf.db");
	if (!getDatabase()) {
		openDatabase(dbPath);
	}
	const db = getDatabase();
	if (!db) throw new Error(`/agent: failed to open database at ${dbPath}`);
	return db;
}

/**
 * Handle the /agent command.
 *
 * Purpose: route /agent list|inspect|reset|delete to their implementations
 * and produce human-readable output via ctx.ui.notify.
 *
 * Consumer: ops.js handleOpsCommand.
 *
 * @param {string} args - raw args string after "agent "
 * @param {object} ctx - SF command context (ctx.ui.notify)
 * @param {string} basePath - project root for DB location
 */
export async function handleAgent(args, ctx, basePath) {
	const parts = args.trim().split(/\s+/);
	const sub = parts[0] ?? "";
	const name = parts.slice(1).join(" ").trim();

	if (!sub || sub === "help") {
		ctx.ui.notify(USAGE, "info");
		return;
	}

	let db;
	try {
		db = ensureDb(basePath);
	} catch (err) {
		ctx.ui.notify(`/agent: ${err.message}`, "error");
		return;
	}

	const store = new UokCoordinationStore(db);

	if (sub === "list") {
		await handleAgentList(store, ctx);
		return;
	}

	if (!name) {
		ctx.ui.notify(`/agent ${sub}: agent name required\n\n${USAGE}`, "warning");
		return;
	}

	switch (sub) {
		case "inspect":
			await handleAgentInspect(store, name, ctx);
			break;
		case "reset":
			await handleAgentReset(store, name, ctx);
			break;
		case "delete":
			await handleAgentDelete(store, name, ctx);
			break;
		default:
			ctx.ui.notify(
				`/agent: unknown subcommand "${sub}"\n\n${USAGE}`,
				"warning",
			);
	}
}

// ─── Subcommand implementations ───────────────────────────────────────────────

async function handleAgentList(store, ctx) {
	const identities = store
		.entries("agent:")
		.filter(({ key }) => key.endsWith(":identity"));

	if (identities.length === 0) {
		ctx.ui.notify("No registered persistent agents.", "info");
		return;
	}

	const lines = ["## Persistent Agents", ""];
	for (const { key, value, updatedAt } of identities) {
		const id = value?.identity ?? value ?? {};
		const agentName = id.name ?? key.replace(/^agent:|:identity$/g, "");
		const role = id.role ?? "worker";
		const tags = (id.tags ?? []).join(", ") || "none";
		const created = id.createdAt ? id.createdAt.slice(0, 10) : "unknown";
		const updated = updatedAt
			? new Date(updatedAt).toISOString().slice(0, 10)
			: "unknown";
		lines.push(
			`**${agentName}** [${role}]  tags: ${tags}  created: ${created}  updated: ${updated}`,
		);
	}
	ctx.ui.notify(lines.join("\n"), "info");
}

async function handleAgentInspect(store, name, ctx) {
	const identityKey = `agent:${name}:identity`;
	const identity = store.get(identityKey);
	if (!identity) {
		ctx.ui.notify(`/agent inspect: no agent named "${name}" found.`, "warning");
		return;
	}

	const lines = [`## Agent: ${name}`, ""];

	// Identity
	lines.push("### Identity");
	lines.push(`- **ID**: ${identity.agentId ?? "n/a"}`);
	lines.push(`- **Role**: ${identity.role ?? "worker"}`);
	lines.push(`- **Tags**: ${(identity.tags ?? []).join(", ") || "none"}`);
	lines.push(`- **Created**: ${identity.createdAt ?? "unknown"}`);
	lines.push("");

	// Core blocks
	const blockPrefix = `agent:${name}:block:`;
	const blocks = store.entries(blockPrefix);
	lines.push("### Core Memory Blocks");
	if (blocks.length === 0) {
		lines.push("_(none)_");
	} else {
		for (const { key, value, expiresAt } of blocks) {
			const label = key.slice(blockPrefix.length);
			const rendered =
				typeof value === "string" ? value : JSON.stringify(value, null, 2);
			const expiry = expiresAt
				? ` [expires: ${new Date(expiresAt).toISOString()}]`
				: "";
			lines.push(`**${label}**${expiry}: ${rendered.slice(0, 300)}`);
		}
	}
	lines.push("");

	// Archival memory
	const archivePrefix = `agent:${name}:archive:`;
	const archive = store.entries(archivePrefix);
	lines.push("### Archival Memory");
	if (archive.length === 0) {
		lines.push("_(none)_");
	} else {
		for (const { key, value } of archive) {
			const archKey = key.slice(archivePrefix.length);
			const rendered =
				typeof value === "string" ? value : JSON.stringify(value);
			lines.push(`- **${archKey}**: ${rendered.slice(0, 200)}`);
		}
	}

	ctx.ui.notify(lines.join("\n"), "info");
}

async function handleAgentReset(store, name, ctx) {
	const identityKey = `agent:${name}:identity`;
	const identity = store.get(identityKey);
	if (!identity) {
		ctx.ui.notify(`/agent reset: no agent named "${name}" found.`, "warning");
		return;
	}

	// Delete all blocks and archival data but preserve identity
	const blockPrefix = `agent:${name}:block:`;
	const archivePrefix = `agent:${name}:archive:`;
	const toDelete = [
		...store.entries(blockPrefix).map((e) => e.key),
		...store.entries(archivePrefix).map((e) => e.key),
	];

	for (const key of toDelete) {
		store.delete(key);
	}

	ctx.ui.notify(
		`Agent "${name}" reset: ${toDelete.length} memory entries cleared. Identity preserved.`,
		"info",
	);
}

async function handleAgentDelete(store, name, ctx) {
	const identityKey = `agent:${name}:identity`;
	const identity = store.get(identityKey);
	if (!identity) {
		ctx.ui.notify(`/agent delete: no agent named "${name}" found.`, "warning");
		return;
	}

	// Delete everything under agent:<name>:
	const agentPrefix = `agent:${name}:`;
	const toDelete = store.entries(agentPrefix).map((e) => e.key);

	for (const key of toDelete) {
		store.delete(key);
	}

	ctx.ui.notify(
		`Agent "${name}" deleted: ${toDelete.length} entries removed.`,
		"info",
	);
}
