/**
 * MCP tools for rDocs (notebooks & docs — full TipTap editor).
 *
 * Tools: rdocs_list_notebooks, rdocs_list_docs, rdocs_get_doc,
 *        rdocs_create_doc, rdocs_update_doc
 */

import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import type { SyncServer } from "../local-first/sync-server";
import { notebookDocId, createNoteItem } from "../../modules/rdocs/schemas";
import type { NotebookDoc, NoteItem } from "../../modules/rdocs/schemas";
import { resolveAccess, accessDeniedResponse } from "./_auth";
import { filterArrayByVisibility, isVisibleTo } from "../../shared/membrane";

const NOTEBOOK_PREFIX = ":notes:notebooks:";

/** Find all notebook docIds for a space. */
function findNotebookDocIds(syncServer: SyncServer, space: string): string[] {
	const prefix = `${space}${NOTEBOOK_PREFIX}`;
	return syncServer.getDocIds().filter(id => id.startsWith(prefix));
}

export function registerDocsTools(server: McpServer, syncServer: SyncServer) {
	server.tool(
		"rdocs_list_notebooks",
		"List all notebooks in a space (rDocs)",
		{
			space: z.string().describe("Space slug"),
			token: z.string().optional().describe("JWT auth token (required for private/permissioned spaces)"),
		},
		async ({ space, token }) => {
			const access = await resolveAccess(token, space, false);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			const docIds = findNotebookDocIds(syncServer, space);
			const notebooks = [];
			for (const docId of docIds) {
				const doc = syncServer.getDoc<NotebookDoc>(docId);
				if (!doc?.notebook) continue;
				notebooks.push({
					id: doc.notebook.id,
					title: doc.notebook.title,
					slug: doc.notebook.slug,
					description: doc.notebook.description,
					noteCount: Object.keys(doc.items || {}).length,
					createdAt: doc.notebook.createdAt,
					updatedAt: doc.notebook.updatedAt,
				});
			}
			return { content: [{ type: "text", text: JSON.stringify(notebooks, null, 2) }] };
		},
	);

	server.tool(
		"rdocs_list_docs",
		"List docs, optionally filtered by notebook, search text, or tags",
		{
			space: z.string().describe("Space slug"),
			token: z.string().optional().describe("JWT auth token (required for private/permissioned spaces)"),
			notebook_id: z.string().optional().describe("Filter by notebook ID"),
			search: z.string().optional().describe("Search in title/content"),
			limit: z.number().optional().describe("Max results (default 50)"),
			tags: z.array(z.string()).optional().describe("Filter by tags"),
		},
		async ({ space, token, notebook_id, search, limit, tags }) => {
			const access = await resolveAccess(token, space, false);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			const docIds = notebook_id
				? [notebookDocId(space, notebook_id)]
				: findNotebookDocIds(syncServer, space);

			let notes: Array<NoteItem & { notebookTitle: string }> = [];
			for (const docId of docIds) {
				const doc = syncServer.getDoc<NotebookDoc>(docId);
				if (!doc?.items) continue;
				const nbTitle = doc.notebook?.title || "Untitled";
				const visibleItems = filterArrayByVisibility(Object.values(doc.items), access.role);
				for (const note of visibleItems) {
					notes.push({ ...JSON.parse(JSON.stringify(note)), notebookTitle: nbTitle });
				}
			}

			if (search) {
				const q = search.toLowerCase();
				notes = notes.filter(n =>
					n.title.toLowerCase().includes(q) ||
					(n.contentPlain && n.contentPlain.toLowerCase().includes(q)),
				);
			}

			if (tags && tags.length > 0) {
				notes = notes.filter(n =>
					n.tags && tags.some(t => n.tags.includes(t)),
				);
			}

			notes.sort((a, b) => b.updatedAt - a.updatedAt);
			const maxResults = limit || 50;
			notes = notes.slice(0, maxResults);

			const summary = notes.map(n => ({
				id: n.id,
				notebookId: n.notebookId,
				notebookTitle: n.notebookTitle,
				title: n.title,
				type: n.type,
				tags: n.tags,
				isPinned: n.isPinned,
				contentPreview: (n.contentPlain || "").slice(0, 200),
				createdAt: n.createdAt,
				updatedAt: n.updatedAt,
			}));

			return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
		},
	);

	server.tool(
		"rdocs_get_doc",
		"Get the full content of a specific doc",
		{
			space: z.string().describe("Space slug"),
			token: z.string().optional().describe("JWT auth token (required for private/permissioned spaces)"),
			note_id: z.string().describe("Doc/note ID"),
			notebook_id: z.string().optional().describe("Notebook ID (speeds up lookup)"),
		},
		async ({ space, token, note_id, notebook_id }) => {
			const access = await resolveAccess(token, space, false);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			if (notebook_id) {
				const doc = syncServer.getDoc<NotebookDoc>(notebookDocId(space, notebook_id));
				const note = doc?.items?.[note_id];
				if (note) {
					if (!isVisibleTo(note.visibility, access.role)) {
						return { content: [{ type: "text", text: JSON.stringify({ error: "Doc not found" }) }] };
					}
					return { content: [{ type: "text", text: JSON.stringify(note, null, 2) }] };
				}
			}

			for (const docId of findNotebookDocIds(syncServer, space)) {
				const doc = syncServer.getDoc<NotebookDoc>(docId);
				const note = doc?.items?.[note_id];
				if (note) {
					if (!isVisibleTo(note.visibility, access.role)) {
						return { content: [{ type: "text", text: JSON.stringify({ error: "Doc not found" }) }] };
					}
					return { content: [{ type: "text", text: JSON.stringify(note, null, 2) }] };
				}
			}

			return { content: [{ type: "text", text: JSON.stringify({ error: "Doc not found" }) }] };
		},
	);

	server.tool(
		"rdocs_create_doc",
		"Create a new doc in a notebook (requires auth token + space membership)",
		{
			space: z.string().describe("Space slug"),
			token: z.string().describe("JWT auth token"),
			notebook_id: z.string().describe("Target notebook ID"),
			title: z.string().describe("Doc title"),
			content: z.string().optional().describe("Doc content (plain text or HTML)"),
			tags: z.array(z.string()).optional().describe("Doc tags"),
			visibility: z.enum(['viewer', 'member', 'moderator', 'admin']).optional().describe("Object visibility level (default: viewer = everyone)"),
		},
		async ({ space, token, notebook_id, title, content, tags, visibility }) => {
			const access = await resolveAccess(token, space, true);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			const docId = notebookDocId(space, notebook_id);
			const doc = syncServer.getDoc<NotebookDoc>(docId);
			if (!doc) {
				return { content: [{ type: "text", text: JSON.stringify({ error: "Notebook not found" }) }], isError: true };
			}

			const noteId = `note-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
			const noteItem = createNoteItem(noteId, notebook_id, title, {
				content: content || "",
				contentPlain: content || "",
				contentFormat: "html",
				tags: tags || [],
			});

			if (visibility) noteItem.visibility = visibility;

			syncServer.changeDoc<NotebookDoc>(docId, `Create doc ${title}`, (d) => {
				if (!d.items) (d as any).items = {};
				d.items[noteId] = noteItem;
			});

			return { content: [{ type: "text", text: JSON.stringify({ id: noteId, created: true }) }] };
		},
	);

	server.tool(
		"rdocs_update_doc",
		"Update an existing doc (requires auth token + space membership)",
		{
			space: z.string().describe("Space slug"),
			token: z.string().describe("JWT auth token"),
			note_id: z.string().describe("Doc/note ID"),
			notebook_id: z.string().optional().describe("Notebook ID (speeds up lookup)"),
			title: z.string().optional().describe("New title"),
			content: z.string().optional().describe("New content"),
			tags: z.array(z.string()).optional().describe("New tags"),
			is_pinned: z.boolean().optional().describe("Pin/unpin doc"),
			visibility: z.enum(['viewer', 'member', 'moderator', 'admin']).optional().describe("Object visibility level"),
		},
		async ({ space, token, note_id, notebook_id, ...updates }) => {
			const access = await resolveAccess(token, space, true);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			const docIds = notebook_id
				? [notebookDocId(space, notebook_id)]
				: findNotebookDocIds(syncServer, space);

			for (const docId of docIds) {
				const doc = syncServer.getDoc<NotebookDoc>(docId);
				if (!doc?.items?.[note_id]) continue;

				syncServer.changeDoc<NotebookDoc>(docId, `Update doc ${note_id}`, (d) => {
					const n = d.items[note_id];
					if (updates.title !== undefined) n.title = updates.title;
					if (updates.content !== undefined) {
						n.content = updates.content;
						n.contentPlain = updates.content;
					}
					if (updates.tags !== undefined) n.tags = updates.tags;
					if (updates.is_pinned !== undefined) n.isPinned = updates.is_pinned;
					if (updates.visibility !== undefined) n.visibility = updates.visibility || undefined;
					n.updatedAt = Date.now();
				});

				return { content: [{ type: "text", text: JSON.stringify({ id: note_id, updated: true }) }] };
			}

			return { content: [{ type: "text", text: JSON.stringify({ error: "Doc not found" }) }], isError: true };
		},
	);
}
