/**
 * MCP tools for rNotes (vault browser — Obsidian/Logseq sync).
 *
 * Tools: rnotes_list_vaults, rnotes_browse_vault, rnotes_search_vault,
 *        rnotes_get_vault_note, rnotes_sync_status
 */

import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import type { SyncServer } from "../local-first/sync-server";
import { vaultDocId } from "../../modules/rnotes/schemas";
import type { VaultDoc } from "../../modules/rnotes/schemas";
import { resolveAccess, accessDeniedResponse } from "./_auth";
import { filterArrayByVisibility, isVisibleTo } from "../../shared/membrane";
import { readFile } from "fs/promises";
import { join } from "path";

const VAULT_PREFIX = ":rnotes:vaults:";
const VAULT_DIR = "/data/files/uploads/vaults";

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

/** Read a single file from a vault ZIP on disk. */
async function readVaultFile(vaultId: string, filePath: string): Promise<string | null> {
	try {
		const zipPath = join(VAULT_DIR, `${vaultId}.zip`);
		const JSZip = (await import("jszip")).default;
		const data = await readFile(zipPath);
		const zip = await JSZip.loadAsync(data);
		const entry = zip.file(filePath);
		if (!entry) return null;
		return await entry.async("string");
	} catch {
		return null;
	}
}

export function registerNotesTools(server: McpServer, syncServer: SyncServer) {
	server.tool(
		"rnotes_list_vaults",
		"List all synced vaults (Obsidian/Logseq) in a space",
		{
			space: z.string().describe("Space slug"),
			token: z.string().optional().describe("JWT auth token"),
		},
		async ({ space, token }) => {
			const access = await resolveAccess(token, space, false);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			const docIds = findVaultDocIds(syncServer, space);
			const vaults = [];
			for (const docId of docIds) {
				const doc = syncServer.getDoc<VaultDoc>(docId);
				if (!doc?.vault) continue;
				vaults.push({
					id: doc.vault.id,
					name: doc.vault.name,
					source: doc.vault.source,
					totalNotes: doc.vault.totalNotes,
					lastSyncedAt: doc.vault.lastSyncedAt,
					createdAt: doc.vault.createdAt,
				});
			}
			return { content: [{ type: "text", text: JSON.stringify(vaults, null, 2) }] };
		},
	);

	server.tool(
		"rnotes_browse_vault",
		"Browse notes in a vault, optionally filtered by folder path",
		{
			space: z.string().describe("Space slug"),
			token: z.string().optional().describe("JWT auth token"),
			vault_id: z.string().describe("Vault ID"),
			folder: z.string().optional().describe("Folder path prefix (e.g. 'daily/')"),
			limit: z.number().optional().describe("Max results (default 50)"),
		},
		async ({ space, token, vault_id, folder, limit }) => {
			const access = await resolveAccess(token, space, false);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			const doc = syncServer.getDoc<VaultDoc>(vaultDocId(space, vault_id));
			if (!doc) return { content: [{ type: "text", text: JSON.stringify({ error: "Vault not found" }) }] };

			let notes = filterArrayByVisibility(Object.values(doc.notes || {}), access.role);
			if (folder) {
				notes = notes.filter(n => n.path.startsWith(folder));
			}

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

			const summary = notes.map(n => ({
				path: n.path,
				title: n.title,
				tags: n.tags,
				sizeBytes: n.sizeBytes,
				lastModifiedAt: n.lastModifiedAt,
				syncStatus: n.syncStatus,
			}));

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

	server.tool(
		"rnotes_search_vault",
		"Search notes across all vaults by title or tags",
		{
			space: z.string().describe("Space slug"),
			token: z.string().optional().describe("JWT auth token"),
			search: z.string().describe("Search term (matches title and tags)"),
			vault_id: z.string().optional().describe("Limit to specific vault"),
			limit: z.number().optional().describe("Max results (default 20)"),
		},
		async ({ space, token, search, vault_id, limit }) => {
			const access = await resolveAccess(token, space, false);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			const docIds = vault_id
				? [vaultDocId(space, vault_id)]
				: findVaultDocIds(syncServer, space);

			const q = search.toLowerCase();
			const results: Array<{ vaultName: string; path: string; title: string; tags: string[] }> = [];

			for (const docId of docIds) {
				const doc = syncServer.getDoc<VaultDoc>(docId);
				if (!doc?.notes) continue;
				const vaultName = doc.vault?.name || "Unknown";
				const visibleNotes = filterArrayByVisibility(Object.values(doc.notes), access.role);
				for (const note of visibleNotes) {
					if (
						note.title.toLowerCase().includes(q) ||
						note.path.toLowerCase().includes(q) ||
						note.tags.some(t => t.toLowerCase().includes(q))
					) {
						results.push({ vaultName, path: note.path, title: note.title, tags: note.tags });
					}
				}
			}

			const maxResults = limit || 20;
			return { content: [{ type: "text", text: JSON.stringify(results.slice(0, maxResults), null, 2) }] };
		},
	);

	server.tool(
		"rnotes_get_vault_note",
		"Get the full content of a vault note (reads from disk ZIP)",
		{
			space: z.string().describe("Space slug"),
			token: z.string().optional().describe("JWT auth token"),
			vault_id: z.string().describe("Vault ID"),
			path: z.string().describe("Note file path within vault"),
		},
		async ({ space, token, vault_id, path: notePath }) => {
			const access = await resolveAccess(token, space, false);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			const doc = syncServer.getDoc<VaultDoc>(vaultDocId(space, vault_id));
			if (!doc) return { content: [{ type: "text", text: JSON.stringify({ error: "Vault not found" }) }] };

			const meta = doc.notes?.[notePath];
			if (!meta || !isVisibleTo(meta.visibility, access.role)) {
				return { content: [{ type: "text", text: JSON.stringify({ error: "Note not found in vault" }) }] };
			}

			const content = await readVaultFile(vault_id, notePath);
			if (content === null) {
				return { content: [{ type: "text", text: JSON.stringify({ error: "Could not read note from vault ZIP" }) }] };
			}

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

	server.tool(
		"rnotes_sync_status",
		"Get sync status summary for a vault",
		{
			space: z.string().describe("Space slug"),
			token: z.string().optional().describe("JWT auth token"),
			vault_id: z.string().describe("Vault ID"),
		},
		async ({ space, token, vault_id }) => {
			const access = await resolveAccess(token, space, false);
			if (!access.allowed) return accessDeniedResponse(access.reason!);

			const doc = syncServer.getDoc<VaultDoc>(vaultDocId(space, vault_id));
			if (!doc) return { content: [{ type: "text", text: JSON.stringify({ error: "Vault not found" }) }] };

			const notes = Object.values(doc.notes || {});
			const synced = notes.filter(n => n.syncStatus === 'synced').length;
			const modified = notes.filter(n => n.syncStatus === 'local-modified').length;
			const conflicts = notes.filter(n => n.syncStatus === 'conflict').length;

			return {
				content: [{
					type: "text",
					text: JSON.stringify({
						vaultId: vault_id,
						vaultName: doc.vault?.name,
						source: doc.vault?.source,
						totalNotes: notes.length,
						synced,
						modified,
						conflicts,
						lastSyncedAt: doc.vault?.lastSyncedAt,
					}, null, 2),
				}],
			};
		},
	);
}
