/**
 * Channel tools — reply to channel events and send chat messages.
 */

import { makeEntryMap } from "@abraca/dabra";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { populateYDocFromMarkdown } from "../converters/markdownToYjs.ts";
import type { AbracadabraMCPServer } from "../server.ts";

export function registerChannelTools(
	mcp: McpServer,
	server: AbracadabraMCPServer,
) {
	mcp.tool(
		"reply",
		"Create a document reply. Creates a child document under doc_id with the reply content as a rich-text document. Use this for structured, persistent responses. For conversational chat, use send_chat_message instead.",
		{
			doc_id: z.string().describe("Document ID to create the reply under."),
			text: z.string().describe("Markdown content for the reply."),
			task_id: z
				.string()
				.optional()
				.describe(
					"If replying to an ai:task, the task ID to clear from awareness.",
				),
		},
		async ({ doc_id, text, task_id }) => {
			try {
				server.setAutoStatus("writing", doc_id);
				server.setActiveToolCall({ name: "reply", target: doc_id });

				const treeMap = server.getTreeMap();
				const rootDoc = server.rootDocument;
				if (!treeMap || !rootDoc) {
					return {
						content: [{ type: "text" as const, text: "Not connected" }],
						isError: true,
					};
				}

				const timestamp = new Date()
					.toISOString()
					.replace("T", " ")
					.slice(0, 19);
				const snippet = text.slice(0, 40).replace(/\n/g, " ");
				const label = `AI Reply — ${timestamp}: ${snippet}`;
				const replyId = crypto.randomUUID();
				const now = Date.now();

				rootDoc.transact(() => {
					treeMap.set(
						replyId,
						makeEntryMap({
							label,
							parentId: doc_id,
							order: now,
							type: "doc",
							createdAt: now,
							updatedAt: now,
						}),
					);
				});

				const replyProvider = await server.getChildProvider(replyId);
				const fragment = replyProvider.document.getXmlFragment("default");
				populateYDocFromMarkdown(fragment, text);

				if (task_id) {
					server.clearAiTask(task_id);
				}

				return {
					content: [
						{
							type: "text" as const,
							text: JSON.stringify({ replyDocId: replyId, parentId: doc_id }),
						},
					],
				};
			} catch (error: any) {
				return {
					content: [{ type: "text" as const, text: `Error: ${error.message}` }],
					isError: true,
				};
			}
		},
	);

	mcp.tool(
		"send_chat_message",
		'Send a chat message visible in the dashboard chat UI. Pass channel_doc_id — the UUID of a doc with kind "channel" (group chat under a Space) or "dm" (direct message). Use list_connected_users + the doc tree to discover channels.',
		{
			channel_doc_id: z
				.string()
				.describe('Channel doc UUID (kind = "channel" or "dm")'),
			text: z.string().describe("Message text"),
		},
		async ({ channel_doc_id, text }) => {
			try {
				const rootProvider = server.rootYProvider;
				if (!rootProvider) {
					return {
						content: [{ type: "text" as const, text: "Not connected" }],
						isError: true,
					};
				}

				// Normalize literal escape sequences. Some LLM outputs emit the
				// 2-char `\n` / `\t` / `\r` instead of the real control chars, which
				// then render literally in the chat (markdown renderer can't see a
				// newline where there is just "\n"). Convert them back to real chars.
				const normalized = text
					.replace(/\\r\\n/g, "\n")
					.replace(/\\n/g, "\n")
					.replace(/\\t/g, "\t")
					.replace(/\\r/g, "\n");

				// Show a brief "writing" phase so the dashboard renders typing dots
				// just before the message lands (the reasoning phase showed the
				// incantation; this is the distinct compose/send phase). Clear any
				// tool pill, mark status 'writing' (the dashboard maps this to typing
				// dots, not the phrase), emit a typing frame, and pause briefly so
				// the dots are actually visible before the bubble appears.
				server.setActiveToolCall(null);
				server.setWritingStatus(channel_doc_id);
				await new Promise((r) => setTimeout(r, 650));

				rootProvider.sendStateless(
					JSON.stringify({
						type: "messages:send",
						channel_doc_id,
						content: normalized,
						mentions: [],
					}),
				);

				// End the turn AFTER the send: clears status, tool pill, turn id and
				// stops the typing heartbeat so nothing lingers after the reply.
				server.setAutoStatus(null);

				return {
					content: [
						{ type: "text" as const, text: `Sent to ${channel_doc_id}` },
					],
				};
			} catch (error: any) {
				return {
					content: [{ type: "text" as const, text: `Error: ${error.message}` }],
					isError: true,
				};
			}
		},
	);
}
