/**
 * context-board-tool.js — Registration for the context_board tool.
 *
 * Exposes add + prune operations as a single `context_board` tool. The tool's
 * `add` operation auto-fills `repository` and `branch` from the current git
 * context so the LLM never sets those fields directly.
 *
 * Pattern mirrors bootstrap/memory-tools.js.
 */
import { Type } from "@sinclair/typebox";
import { nativeGetCurrentBranch } from "../native-git-bridge.js";
import {
	executeContextBoardAdd,
	executeContextBoardPrune,
} from "../tools/context-board-tool.js";
import { ensureDbOpen } from "./dynamic-tools.js";

/** Resolve a stable repository identifier (absolute project root path). */
function resolveRepository(basePath) {
	return basePath;
}

/** Resolve the current branch; falls back to "unknown" if git is unavailable. */
function resolveBranch(basePath) {
	try {
		const branch = nativeGetCurrentBranch(basePath);
		return branch || "unknown";
	} catch {
		return "unknown";
	}
}

export function registerContextBoardTool(pi) {
	pi.registerTool({
		name: "context_board",
		label: "Context Board",
		description:
			"Manage the always-in-context invariants board for this repo/branch. " +
			"Use 'add' to record an invariant (architectural rule, in-flight migration state, " +
			"known broken path, etc.) that should appear in every system prompt. " +
			"Use 'prune' to remove a stale entry by its id. " +
			"Add and prune only — to change an entry, prune it then add a new one.",
		promptSnippet:
			"Add or prune an invariant on the always-in-context board (op: add | prune)",
		promptGuidelines: [
			"Use 'add' for invariants the agent must never operate without: arch rules, deprecation notices, in-flight migration state.",
			"Keep board entries short — 1-3 sentences each. The board has a 4 KB cap.",
			"Use 'prune' when an invariant is no longer true or no longer relevant.",
			"Do NOT add task-specific details or one-off facts — use capture_thought for those.",
			"The board is scoped per repo and per branch. Different branches have separate boards.",
		],
		parameters: Type.Object({
			op: Type.Union([Type.Literal("add"), Type.Literal("prune")], {
				description:
					"Operation: 'add' to create an entry, 'prune' to remove one by id",
			}),
			content: Type.Optional(
				Type.String({
					description: "Invariant text (1-3 sentences). Required for op=add.",
				}),
			),
			category: Type.Optional(
				Type.String({
					description:
						"Optional label (e.g. milestone, arch, gotcha, migration, deprecation)",
				}),
			),
			id: Type.Optional(
				Type.String({
					description: "Entry id to remove. Required for op=prune.",
				}),
			),
		}),
		async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
			const ok = await ensureDbOpen();
			if (!ok) {
				return {
					content: [
						{
							type: "text",
							text: "Error: SF database is not available. context_board requires an initialized .sf/ project.",
						},
					],
					details: { operation: "context_board", error: "db_unavailable" },
					isError: true,
				};
			}

			const { op } = params;

			if (op === "add") {
				const basePath = process.cwd();
				const repository = resolveRepository(basePath);
				const branch = resolveBranch(basePath);
				return executeContextBoardAdd({
					content: params.content ?? "",
					category: params.category ?? null,
					repository,
					branch,
				});
			}

			if (op === "prune") {
				return executeContextBoardPrune({ id: params.id ?? "" });
			}

			return {
				content: [
					{
						type: "text",
						text: `Error: unknown op "${op}". Must be "add" or "prune".`,
					},
				],
				details: { operation: "context_board", error: "unknown_op" },
				isError: true,
			};
		},
	});
}
