/**
 * Content tools — read/write document content via markdown ↔ Y.js conversion.
 */
import * as Y from 'yjs'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod'
import type { AbracadabraMCPServer } from '../server.ts'
import { populateYDocFromMarkdown, parseFrontmatter } from '../converters/markdownToYjs.ts'
import { yjsToMarkdown } from '../converters/yjsToMarkdown.ts'
import { toPlain, patchEntry } from '@abraca/dabra'

export function registerContentTools(mcp: McpServer, server: AbracadabraMCPServer) {
  mcp.tool(
    'read_document',
    'Read a document\'s content as markdown, along with its immediate children. IMPORTANT: A document\'s full content is its body text PLUS all its children. An empty body does not mean empty content — the children ARE the content (sub-docs, kanban columns, table columns, calendar events, etc.). Always check the returned "children" array and read child documents too.',
    {
      docId: z.string().describe('Document ID to read.'),
    },
    async ({ docId }) => {
      try {
        server.setAutoStatus('reading', docId)
        server.setActiveToolCall({ name: 'read_document', target: docId })

        const provider = await server.getChildProvider(docId)
        const fragment = provider.document.getXmlFragment('default')

        // Auto-update presence: mark this doc as focused and place cursor at start
        server.setFocusedDoc(docId)
        server.setDocCursor(docId, 0)

        // Get tree metadata + immediate children
        const treeMap = server.getTreeMap()
        let label = 'Untitled'
        let type: string | undefined
        let meta: Record<string, unknown> | undefined
        let children: Array<{ id: string; label: string; type?: string; meta?: unknown }> = []
        if (treeMap) {
          // Entries are nested Y.Map values — raw field access returns
          // undefined; resolve via toPlain first (mirrors tree.ts:readEntries).
          const entry = toPlain(treeMap.get(docId)) as any
          if (entry && typeof entry === 'object') {
            label = entry.label || label
            type = entry.type
            meta = entry.meta
          }
          // Collect immediate children sorted by order
          const collected: Array<{ id: string; label: string; type?: string; meta?: unknown; order: number }> = []
          treeMap.forEach((raw: any, id: string) => {
            const value = toPlain(raw) as any
            if (typeof value !== 'object' || value === null) return // skip non-entry keys (e.g. ":updatedAt")
            if (value.parentId === docId) {
              collected.push({ id, label: value.label || 'Untitled', type: value.type, meta: value.meta, order: value.order ?? 0 })
            }
          })
          collected.sort((a, b) => a.order - b.order)
          children = collected.map(({ order, ...rest }) => rest)
        }

        // yjsToMarkdown returns a string and takes the resolved label/meta/type
        // so frontmatter round-trips. (Older code destructured an object and
        // passed only the fragment — that silently produced `undefined`.)
        const markdown = yjsToMarkdown(fragment, label, meta as any, type)

        const result: Record<string, unknown> = { label, type, meta, markdown, children }
        return {
          content: [{
            type: 'text',
            text: JSON.stringify(result, null, 2),
          }],
        }
      } catch (error: any) {
        return {
          content: [{ type: 'text', text: `Error reading document: ${error.message}` }],
          isError: true,
        }
      }
    }
  )

  mcp.tool(
    'write_document',
    'Write markdown content to a document. Parses the markdown and writes it to the Y.js CRDT document, which syncs in real-time to all connected clients. Supports optional YAML frontmatter for title and metadata. Use ![[docId]] to embed another document as a block, or [[docId|label]] for inline doc links.',
    {
      docId: z.string().describe('Document ID to write to.'),
      markdown: z.string().describe('Markdown content to write. Can include YAML frontmatter with title and metadata fields.'),
      mode: z.enum(['replace', 'append']).optional().describe('Write mode. "replace" clears existing content first (default). "append" adds to the end.'),
    },
    async ({ docId, markdown, mode }) => {
      try {
        server.setAutoStatus('writing', docId)
        server.setActiveToolCall({ name: 'write_document', target: docId })

        const writeMode = mode ?? 'replace'
        const provider = await server.getChildProvider(docId)
        const doc = provider.document
        const fragment = doc.getXmlFragment('default')

        // Parse optional frontmatter
        const { title, meta, body } = parseFrontmatter(markdown)

        // Update tree metadata if frontmatter provided title or meta
        if (title || Object.keys(meta).length > 0) {
          const treeMap = server.getTreeMap()
          const rootDoc = server.rootDocument
          if (treeMap && rootDoc) {
            const rawEntry = treeMap.get(docId)
            if (rawEntry) {
              const cur = toPlain(rawEntry) as { meta?: Record<string, unknown> }
              rootDoc.transact(() => {
                const patch: Record<string, unknown> = { updatedAt: Date.now() }
                if (title) patch.label = title
                if (Object.keys(meta).length > 0) {
                  patch.meta = { ...(cur.meta ?? {}), ...meta }
                }
                patchEntry(treeMap, docId, patch)
              })
            }
          }
        }

        if (writeMode === 'replace') {
          // Clear existing content
          doc.transact(() => {
            while (fragment.length > 0) {
              fragment.delete(0)
            }
          })
        }

        // Write new content
        const contentToWrite = body || markdown
        const fallbackTitle = title || 'Untitled'
        populateYDocFromMarkdown(fragment, contentToWrite, fallbackTitle)

        // Auto-update presence: mark this doc as focused and place cursor at end
        server.setFocusedDoc(docId)
        server.setDocCursor(docId, fragment.length)


        return {
          content: [{ type: 'text', text: `Document ${docId} updated (${writeMode} mode)` }],
        }
      } catch (error: any) {
        return {
          content: [{ type: 'text', text: `Error writing document: ${error.message}` }],
          isError: true,
        }
      }
    }
  )
}
