#!/usr/bin/env node
/**
 * Abracadabra MCP Server — entry point.
 *
 * Environment variables:
 *   ABRA_URL                    (required) — Server URL (e.g. http://localhost:1234)
 *   ABRA_AGENT_NAME             — Display name (default: "AI Assistant")
 *   ABRA_AGENT_COLOR            — HSL color for presence (default: "hsl(270, 80%, 60%)")
 *   ABRA_INVITE_CODE            — Invite code for first-run registration (grants role)
 *   ABRA_KEY_FILE               — Path to Ed25519 key file (default: ~/.abracadabra/agent.key)
 *   ABRA_AGENT_TRIGGER_MODE     — When to respond in group chats:
 *                                   all          → every message (legacy)
 *                                   mention      → only when @<alias> is used
 *                                   task         → only ai:task awareness events
 *                                   mention+task → mention OR ai:task (default)
 *                                 DMs always trigger regardless of mode.
 *   ABRA_AGENT_MENTION_ALIASES  — Comma-separated aliases for @mentions
 *                                 (default: [ABRA_AGENT_NAME])
 *   ABRA_MCP_SCHEMA_BUNDLE      — (optional) Path to a JSON Schema bundle
 *                                 emitted by `@abraca/schema`. When set,
 *                                 update_metadata + create_document
 *                                 reject `meta` payloads that fail
 *                                 validation against the resolved doc-type's
 *                                 schema before forwarding to the server.
 *                                 When unset, behaviour is unchanged.
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { HookBridge } from "./hook-bridge.ts";
import { registerAgentGuide } from "./resources/agent-guide.ts";
import { registerServerInfoResource } from "./resources/server-info.ts";
import { registerTreeResource } from "./resources/tree-resource.ts";
import { loadSchemaBundle, SchemaBundleLoadError } from "./schema/loader.ts";
import type { SchemaBundleValidator } from "./schema/validator.ts";
import { AbracadabraMCPServer, type TriggerMode } from "./server.ts";
import { wrapToolsWithDetail } from "./tool-detail.ts";
import { registerAwarenessTools } from "./tools/awareness.ts";
import { registerChannelTools } from "./tools/channel.ts";
import { registerContentTools } from "./tools/content.ts";
import { registerFileTools } from "./tools/files.ts";
import { registerHookTools } from "./tools/hooks.ts";
import { registerMetaTools } from "./tools/meta.ts";
import { registerSvgTools } from "./tools/svg.ts";
import { registerTreeTools } from "./tools/tree.ts";

async function main() {
	const url = process.env.ABRA_URL;

	if (!url) {
		console.error("Missing required environment variable: ABRA_URL");
		process.exit(1);
	}

	// Parse trigger mode (defaults to mention+task)
	const rawMode = (process.env.ABRA_AGENT_TRIGGER_MODE ?? "mention+task")
		.trim()
		.toLowerCase();
	const validModes: TriggerMode[] = ["all", "mention", "task", "mention+task"];
	const triggerMode = (validModes as string[]).includes(rawMode)
		? (rawMode as TriggerMode)
		: "mention+task";
	if (rawMode && !(validModes as string[]).includes(rawMode)) {
		console.error(
			`[abracadabra-mcp] Invalid ABRA_AGENT_TRIGGER_MODE="${rawMode}", falling back to "mention+task"`,
		);
	}

	const aliasEnv = process.env.ABRA_AGENT_MENTION_ALIASES;
	const mentionAliases: string[] | undefined = aliasEnv
		? aliasEnv
				.split(",")
				.map((a: string) => a.trim())
				.filter((a: string) => a.length > 0)
		: undefined;

	// Create the Abracadabra connection manager
	const server = new AbracadabraMCPServer({
		url,
		agentName: process.env.ABRA_AGENT_NAME,
		agentColor: process.env.ABRA_AGENT_COLOR,
		inviteCode: process.env.ABRA_INVITE_CODE,
		keyFile: process.env.ABRA_KEY_FILE,
		triggerMode,
		mentionAliases,
	});

	console.error(
		`[abracadabra-mcp] Trigger mode: ${triggerMode}; aliases: ${server.mentionAliases.join(", ")}`,
	);

	// Optional schema bundle. When set, update_metadata + create_document validate
	// `meta` against the resolved doc-type's schema before forwarding. When unset,
	// behaviour is identical to today (Rules 2 + 4 in feedback_schema_free_core.md).
	let schemaValidator: SchemaBundleValidator | null = null;
	const schemaBundlePath = process.env.ABRA_MCP_SCHEMA_BUNDLE;
	if (schemaBundlePath && schemaBundlePath.trim().length > 0) {
		try {
			schemaValidator = loadSchemaBundle(schemaBundlePath);
			console.error(
				`[abracadabra-mcp] Loaded schema bundle from ${schemaBundlePath}; known doc-types: ${schemaValidator.knownTypes.join(", ")}`,
			);
		} catch (err) {
			const msg =
				err instanceof SchemaBundleLoadError ? err.message : String(err);
			console.error(`[abracadabra-mcp] ${msg}`);
			process.exit(1);
		}
	}

	// Create MCP server with channel capability
	const mcp = new McpServer(
		{ name: "abracadabra", version: "1.0.0" },
		{
			capabilities: { experimental: { "claude/channel": {} } },
			instructions: `Abracadabra is a CRDT collaboration platform where everything is a document in a tree.

## CRITICAL: Responding to Channel Events
The user CANNOT see your plain text output — they only see messages sent via MCP tools. When you receive a channel event:
- **Chat messages**: Use send_chat_message with the SAME channel for ALL replies AND progress updates.
- **AI tasks**: Use reply tool for the final response; use send_chat_message for progress updates.
- For multi-step work, send brief status updates via send_chat_message so the user sees progress.
- NEVER output plain text as a response or status update — it is invisible to the user.

## Quick Start Workflow
1. list_spaces → note the active space's doc_id (this is the hub/root document)
2. get_document_tree(rootId: hubDocId) → see the FULL hierarchy before doing anything
3. find_document(query) → search for a document by name if you know what you're looking for
4. read_document / write_document / create_document → operate on specific documents

## Key Concepts
- Documents form a tree. A kanban board's columns are child documents; cards are grandchildren.
- A document's label IS its display name everywhere. Children ARE the content (not just the body text).
- Page types are views over the SAME tree — switching types preserves data.
- An empty markdown body does NOT mean empty content — always check the children array.
- Use ![[docId]] in content to embed another document, or [[docId|label]] for inline links.

## Finding Documents
- list_documents only shows ONE level of children. Use find_document to search by name, or get_document_tree to see the full hierarchy.
- NEVER conclude a document doesn't exist after only checking root-level documents.

## Rules
- Always call list_spaces first to get the hub doc ID before creating documents.
- Always call get_document_tree to understand existing structure before creating documents.
- Use Lucide icon names in kebab-case (e.g. "star", "code-2") for meta.icon — NEVER emoji.
- Never rename or write content to the hub document itself.
- Use universal meta keys (color, icon, dateStart) — never prefix with page type names.

## Full Reference
Read the resource at abracadabra://agent-guide for the complete guide covering page type schemas, metadata reference, awareness/presence, content structure, and detailed examples.`,
		},
	);

	// Wrap mcp.tool ONCE so every tool invocation records an expandable detail
	// (the arguments it was called with + the text it returned) onto its
	// tool-history entry. This is what lets the cou-sh chat cards expand to show
	// "which doc / which tree / what it found" — the same way built-in Claude
	// tools (bash / read / edit) already expand via the hook-bridge. The pill
	// itself is still emitted by each tool's own setActiveToolCall; we only
	// enrich it after the handler resolves. Must run before the register* calls.
	wrapToolsWithDetail(mcp, server);

	// Register tools
	registerTreeTools(mcp, server, schemaValidator);
	registerContentTools(mcp, server);
	registerMetaTools(mcp, server, schemaValidator);
	registerFileTools(mcp, server);
	registerAwarenessTools(mcp, server);
	registerChannelTools(mcp, server);
	registerSvgTools(mcp, server);

	// Register resources
	registerAgentGuide(mcp);
	registerTreeResource(mcp, server);
	registerServerInfoResource(mcp, server);

	// Connect to Abracadabra server
	try {
		await server.connect();
	} catch (error: any) {
		console.error(`[abracadabra-mcp] Failed to connect: ${error.message}`);
		process.exit(1);
	}

	// Start Claude Code hook bridge (HTTP server for activity events)
	const hookBridge = new HookBridge(server);
	try {
		const hookPort = await hookBridge.start();
		console.error(
			`[abracadabra-mcp] Hook bridge listening on port ${hookPort}`,
		);
	} catch (error: any) {
		console.error(
			`[abracadabra-mcp] Hook bridge failed to start: ${error.message}`,
		);
	}

	// Register hook config tool (must be after bridge starts so port is available)
	registerHookTools(mcp, hookBridge);

	// Start MCP stdio transport
	const transport = new StdioServerTransport();
	await mcp.connect(transport);

	// Wire up real-time channel notifications (awareness → channel dispatch)
	server.startChannelNotifications(mcp);
	console.error("[abracadabra-mcp] MCP server running on stdio");

	// Graceful shutdown
	const shutdown = async () => {
		console.error("[abracadabra-mcp] Shutting down...");
		await hookBridge.destroy();
		await server.destroy();
		process.exit(0);
	};

	process.on("SIGINT", shutdown);
	process.on("SIGTERM", shutdown);
}

main().catch((error) => {
	console.error("[abracadabra-mcp] Fatal error:", error);
	process.exit(1);
});

export type { MCPServerConfig } from "./server.ts";
// Re-export for library usage
export { AbracadabraMCPServer } from "./server.ts";
