/**
 * Metadata tools — read/update PageMeta on tree entries.
 */
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod'
import type { AbracadabraMCPServer } from '../server.ts'
import type { SchemaBundleValidator } from '../schema/validator.ts'
import { toPlain, patchEntry } from '@abraca/dabra'

export function registerMetaTools(
  mcp: McpServer,
  server: AbracadabraMCPServer,
  validator: SchemaBundleValidator | null = null,
) {
  mcp.tool(
    'get_metadata',
    'Read the metadata (PageMeta) of a document from the tree.',
    {
      docId: z.string().describe('Document ID.'),
    },
    async ({ docId }) => {
      server.setAutoStatus('reading', docId)
      server.setActiveToolCall({ name: 'get_metadata', target: docId })
      const treeMap = server.getTreeMap()
      if (!treeMap) {
        return { content: [{ type: 'text', text: 'Not connected' }] }
      }

      const entry = treeMap.get(docId)
      if (!entry) {
        return { content: [{ type: 'text', text: `Document ${docId} not found` }] }
      }

      return {
        content: [{
          type: 'text',
          text: JSON.stringify({
            id: docId,
            label: entry.label,
            type: entry.type,
            meta: entry.meta ?? {},
          }, null, 2),
        }],
      }
    }
  )

  mcp.tool(
    'update_metadata',
    'Update metadata fields on a document. Merges the provided fields into existing metadata.',
    {
      docId: z.string().describe('Document ID.'),
      meta: z.record(z.string(), z.unknown()).describe('Metadata fields to update (merged with existing). Universal keys: color (hex), icon (Lucide kebab-case — NEVER emoji), dateStart/dateEnd, datetimeStart/datetimeEnd, allDay, timeStart/timeEnd, tags (string[]), checked (bool), priority (0=none,1=low,2=med,3=high,4=urgent), status, rating (0-5), url, email, phone, number, unit, subtitle, note, taskProgress (0-100), members ({id,label}[]), coverUploadId, coverDocId, dateTaken. Geo/Map (children): geoType ("marker"|"line"|"measure"), geoLat, geoLng, geoDescription. Spatial 3D (children, plugin: spatial): spShape ("box"|"sphere"|"cylinder"|"cone"|"plane"|"torus"|"glb"), spX/spY/spZ, spRX/spRY/spRZ (deg), spSX/spSY/spSZ (scale), spOpacity (0-100), spModelUploadId, spModelDocId — spatial uses the universal `color` key, NOT spColor. Dashboard (children): deskX, deskY, deskZ, deskMode ("icon"|"widget-sm"|"widget-lg"). Mindmap-layout (children): mmX, mmY. Graph-layout (children): graphX, graphY, graphPinned. Slides (children): slidesTransition ("none"|"fade"|"slide"). Coder (children, plugin: coder): fileType ("vue"|"ts"|"js"|"css"|"json"|"folder"), entry (bool). Cell formatting (sheets cells): bold, italic, textColor, bgColor, align ("left"|"center"|"right"), formula. Renderer config (on the PAGE doc itself, not children): kanbanColumnWidth ("narrow"|"default"|"wide"), galleryColumns (1-6), galleryAspect ("square"|"4:3"|"3:2"|"16:9"|"free"), galleryCardStyle ("default"|"compact"|"detailed"), galleryShowLabels, gallerySortBy ("manual"|"date"|"name"|"rating"), calendarView ("month"|"week"|"day"), calendarWeekStart ("sun"|"mon"), calendarShowWeekNumbers, tableMode ("hierarchy"|"flat"), tableSortKey, tableSortDir ("asc"|"desc"), timelineZoom ("week"|"month"|"quarter"), timelinePixelsPerDay, timelineCenterDate (ISO date), checklistFilter ("all"|"active"|"completed"), checklistSort ("manual"|"priority"|"due"), mapShowLabels, graphSpacing ("compact"|"default"|"spacious"), graphShowLabels, graphEdgeThickness ("thin"|"normal"|"thick"), showRefEdges, mmSpacing, spatialGridVisible, slidesTheme ("dark"|"light"), chartType ("bar"|"stacked bar"|"line"|"donut"|"treemap"), chartMetric ("value"|"type"|"tag"|"status"|"priority"|"activity"|"completion"), chartColorScheme ("default"|"warm"|"cool"|"mono"), chartLimit (3-30), chartShowLegend, chartShowValues, sheetsDefaultColWidth (40-500), sheetsDefaultRowHeight (20-100), sheetsShowGridlines, sheetsFreezeRows, sheetsFreezeCols, mediaRepeat ("off"|"all"|"one"), mediaShuffle. Set a key to null to clear it.'),
    },
    async ({ docId, meta }) => {
      server.setAutoStatus('writing', docId)
      server.setActiveToolCall({ name: 'update_metadata', target: docId })
      const treeMap = server.getTreeMap()
      if (!treeMap) {
        return { content: [{ type: 'text', text: 'Not connected' }] }
      }

      const rawEntry = treeMap.get(docId)
      if (!rawEntry) {
        return { content: [{ type: 'text', text: `Document ${docId} not found` }] }
      }
      // Read via toPlain: entries are nested Y.Maps (the Rust provider
      // already writes them so), so raw `.meta`/`.type` access would be
      // undefined — skipping validation and dropping existing meta.
      const entry = toPlain(rawEntry) as { meta?: Record<string, unknown>; type?: string }

      const mergedMeta = { ...(entry.meta ?? {}), ...meta }

      if (validator && entry.type) {
        const result = validator.validateMeta(entry.type, mergedMeta)
        if (!result.ok) {
          const detail = result.errors.map((e) => `${e.path} ${e.message}`).join('; ')
          return {
            content: [{
              type: 'text',
              text: `Metadata validation failed for type "${entry.type}": ${detail}`,
            }],
            isError: true,
          }
        }
      }

      patchEntry(treeMap, docId, {
        meta: mergedMeta,
        updatedAt: Date.now(),
      })

      return { content: [{ type: 'text', text: `Metadata updated for ${docId}` }] }
    }
  )
}
