#!/usr/bin/env node

/**
 * Greyhaven Design System MCP Server
 *
 * Provides programmatic access to design tokens, component specs, and
 * validation tools for any MCP-compatible AI agent.
 */

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
import * as fs from 'fs'
import * as path from 'path'
import { fileURLToPath } from 'url'
import {
  COMPONENT_CATALOG,
  getTokens,
  type ComponentSpec,
} from '../lib/catalog.js'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const ROOT = path.resolve(__dirname, '..')

// ---------------------------------------------------------------------------
// Server setup
// ---------------------------------------------------------------------------

const server = new McpServer({
  name: 'greyhaven-design-system',
  version: '1.0.0',
})

// Tool: get_tokens
server.tool(
  'get_tokens',
  'Returns design token values. Optionally filter by category: color, typography, spacing, radii, shadows, motion.',
  { category: z.string().optional().describe('Token category to filter by') },
  async ({ category }) => {
    const tokens = getTokens(ROOT, category)
    return {
      content: [
        {
          type: 'text' as const,
          text: JSON.stringify(tokens, null, 2),
        },
      ],
    }
  },
)

// Tool: get_component
server.tool(
  'get_component',
  'Returns the full spec for a named component: props, variants, usage example, and when to use it.',
  { name: z.string().describe('Component name (case-insensitive)') },
  async ({ name }) => {
    const component = COMPONENT_CATALOG.find(
      (c) => c.name.toLowerCase() === name.toLowerCase(),
    )
    if (!component) {
      return {
        content: [
          {
            type: 'text' as const,
            text: `Component "${name}" not found. Use list_components to see available components.`,
          },
        ],
        isError: true,
      }
    }

    let source = ''
    try {
      source = fs.readFileSync(path.join(ROOT, component.file), 'utf-8')
    } catch {
      source = '(source file not readable)'
    }

    return {
      content: [
        {
          type: 'text' as const,
          text: JSON.stringify({ ...component, source }, null, 2),
        },
      ],
    }
  },
)

// Tool: list_components
server.tool(
  'list_components',
  'Lists all available design system components, optionally filtered by category.',
  {
    category: z
      .string()
      .optional()
      .describe(
        'Category filter: primitives, layout, overlay, navigation, data, feedback, form, composition',
      ),
  },
  async ({ category }) => {
    let components = COMPONENT_CATALOG
    if (category) {
      components = components.filter(
        (c) => c.category.toLowerCase() === category.toLowerCase(),
      )
    }

    const summary = components.map((c) => ({
      name: c.name,
      category: c.category,
      file: c.file,
      description: c.description,
    }))

    return {
      content: [
        {
          type: 'text' as const,
          text: JSON.stringify(summary, null, 2),
        },
      ],
    }
  },
)

// Tool: validate_colors
server.tool(
  'validate_colors',
  'Checks if a code snippet uses valid design system colors. Returns warnings for raw hex values that should use tokens instead.',
  { code: z.string().describe('Code string to validate') },
  async ({ code }) => {
    const hexRegex = /#[0-9a-fA-F]{3,8}/g
    const matches = code.match(hexRegex) || []

    const validHexValues = new Set([
      '#f9f9f7', '#F9F9F7', '#161614', '#d95e2a', '#D95E2A',
      '#b43232', '#B43232', '#f0f0ec', '#F0F0EC', '#ddddd7', '#DDDDD7',
      '#c4c4bd', '#C4C4BD', '#a6a69f', '#A6A69F', '#7f7f79', '#7F7F79',
      '#575753', '#2f2f2c', '#2F2F2C',
      '#fff', '#FFF', '#ffffff', '#FFFFFF', '#000', '#000000',
    ])

    const warnings: string[] = []
    const seen = new Set<string>()

    for (const hex of matches) {
      const lower = hex.toLowerCase()
      if (seen.has(lower)) continue
      seen.add(lower)

      if (validHexValues.has(hex)) {
        warnings.push(
          `${hex} -- valid Greyhaven primitive, but prefer semantic CSS variables (e.g., var(--primary)).`,
        )
      } else {
        warnings.push(
          `${hex} -- NOT a Greyhaven design token. Use semantic tokens: bg-primary, text-foreground, border-border, etc.`,
        )
      }
    }

    if (warnings.length === 0) {
      return {
        content: [{
          type: 'text' as const,
          text: 'No raw hex colors found. The code uses design system tokens correctly.',
        }],
      }
    }

    return {
      content: [{
        type: 'text' as const,
        text: `Found ${matches.length} hex color(s):\n\n${warnings.join('\n')}`,
      }],
    }
  },
)

// Tool: suggest_component
server.tool(
  'suggest_component',
  'Suggests the best Greyhaven component(s) for a described UI need.',
  { description: z.string().describe('Natural language description of what UI you need') },
  async ({ description }) => {
    const desc = description.toLowerCase()
    const suggestions: ComponentSpec[] = []

    const keywords: Record<string, string[]> = {
      button: ['button', 'click', 'action', 'submit', 'cta'],
      card: ['card', 'container', 'box', 'panel', 'tile'],
      dialog: ['dialog', 'modal', 'popup', 'overlay', 'confirm'],
      input: ['input', 'text field', 'textbox', 'form field'],
      table: ['table', 'grid', 'data', 'list', 'rows', 'columns'],
      navbar: ['navbar', 'navigation bar', 'header', 'top bar', 'nav'],
      hero: ['hero', 'banner', 'landing', 'splash', 'headline'],
      'cta-section': ['cta', 'call to action', 'signup', 'sign up'],
      footer: ['footer', 'bottom', 'copyright'],
      form: ['form', 'registration', 'login', 'sign in', 'contact'],
      select: ['select', 'dropdown', 'choose', 'pick'],
      tabs: ['tabs', 'tab', 'sections', 'switch between'],
      accordion: ['accordion', 'expandable', 'collapsible', 'faq'],
      alert: ['alert', 'warning', 'error', 'notification', 'message'],
      badge: ['badge', 'tag', 'label', 'status', 'chip'],
      avatar: ['avatar', 'profile', 'user image', 'photo'],
      tooltip: ['tooltip', 'hint', 'hover info'],
      progress: ['progress', 'loading', 'bar', 'percentage'],
      skeleton: ['skeleton', 'loading', 'placeholder', 'shimmer'],
      drawer: ['drawer', 'sheet', 'bottom sheet', 'slide'],
      popover: ['popover', 'floating', 'popup content'],
      separator: ['separator', 'divider', 'line', 'hr'],
      breadcrumb: ['breadcrumb', 'trail', 'path'],
      pagination: ['pagination', 'pages', 'next', 'previous'],
      section: ['section', 'content block', 'area'],
      'page-layout': ['layout', 'page', 'shell', 'scaffold', 'template'],
      logo: ['logo', 'brand', 'greyhaven'],
      calendar: ['calendar', 'date', 'date picker'],
    }

    for (const [componentName, kw] of Object.entries(keywords)) {
      if (kw.some((k) => desc.includes(k))) {
        const match = COMPONENT_CATALOG.find(
          (c) => c.name.toLowerCase() === componentName.toLowerCase() ||
                 c.name.toLowerCase().replace(/\s+/g, '-') === componentName,
        )
        if (match) suggestions.push(match)
      }
    }

    if (suggestions.length === 0) {
      return {
        content: [{
          type: 'text' as const,
          text: `No strong match for "${description}". Use list_components() to browse all ${COMPONENT_CATALOG.length} components.`,
        }],
      }
    }

    return {
      content: [{
        type: 'text' as const,
        text: JSON.stringify(
          suggestions.map((s) => ({
            name: s.name,
            category: s.category,
            description: s.description,
            example: s.example,
          })),
          null, 2,
        ),
      }],
    }
  },
)

// ---------------------------------------------------------------------------
// Brand tools & resources
// ---------------------------------------------------------------------------

const BRAND_SKILL_PATH = path.join(ROOT, 'skill', 'BRAND.md')

function readBrandSkill(): string {
  try {
    return fs.readFileSync(BRAND_SKILL_PATH, 'utf-8')
  } catch {
    return '(skill/BRAND.md not found — hand-curated brand skill is missing)'
  }
}

server.tool(
  'get_brand_rules',
  'Returns the Greyhaven brand voice, tone, and messaging rules. Use this BEFORE generating any user-facing marketing copy, CTAs, landing page content, or product explanations. Covers positioning, brand axes, tone, writing rules, reasoning patterns, CTA guidance, logo usage, and a self-check list.',
  {
    section: z
      .enum([
        'all',
        'positioning',
        'axes',
        'tone',
        'writing-rules',
        'reasoning-patterns',
        'cta',
        'logo',
        'self-check',
      ])
      .optional()
      .describe('Optional section filter. Default returns the full brand skill.'),
  },
  async ({ section }) => {
    const full = readBrandSkill()

    if (!section || section === 'all') {
      return {
        content: [{ type: 'text' as const, text: full }],
      }
    }

    // Section anchors in BRAND.md (markdown H2 headings)
    const anchors: Record<string, RegExp> = {
      positioning: /## 2\. Core Positioning[\s\S]*?(?=\n## |\n---)/,
      axes: /## 3\. The Three Brand Axes[\s\S]*?(?=\n## |\n---)/,
      tone: /## 4\. Tone of Voice[\s\S]*?(?=\n## |\n---)/,
      'writing-rules': /## 5\. Writing Rules[\s\S]*?(?=\n## |\n---)/,
      'reasoning-patterns': /## 6\. Patterns for Reasoning[\s\S]*?(?=\n## |\n---)/,
      cta: /## 7\. CTA Guidance[\s\S]*?(?=\n## |\n---)/,
      logo: /## 10\. Logo Usage[\s\S]*?(?=\n## |\n---)/,
      'self-check': /## 11\. Self-check[\s\S]*?(?=\n## |\n---)/,
    }

    const re = anchors[section]
    const match = re ? full.match(re) : null

    if (!match) {
      return {
        content: [{
          type: 'text' as const,
          text: `Section "${section}" not found. Returning full brand skill instead.\n\n${full}`,
        }],
      }
    }

    return {
      content: [{ type: 'text' as const, text: match[0] }],
    }
  },
)

server.tool(
  'validate_copy',
  'Checks a piece of user-facing copy against Greyhaven brand rules. Flags hype words, sales language, vague superlatives, and other brand violations. Use on marketing copy, CTAs, headlines, product descriptions before shipping.',
  { text: z.string().describe('The copy to validate') },
  async ({ text }) => {
    const lower = text.toLowerCase()

    const bannedWords = [
      'unleash', 'transform', 'revolutionary', 'revolutionize',
      'seamless', 'seamlessly', 'game-changing', 'cutting-edge',
      'next-gen', 'next-generation', 'leverage', 'synergy', 'unlock',
      'supercharge', 'empower', 'empowered', 'unprecedented',
      'best-in-class', 'industry-leading', 'world-class',
      'lightning-fast', 'blazing fast',
    ]

    const vagueSuperlatives = [
      'amazing', 'incredible', 'awesome', 'stunning', 'beautiful',
      'powerful', 'robust', 'cutting edge', 'state-of-the-art',
    ]

    const urgencyPhrases = [
      'limited time', 'act now', "don't miss out", 'hurry',
      'last chance', 'today only',
    ]

    const findings: string[] = []

    for (const w of bannedWords) {
      if (lower.includes(w)) {
        findings.push(`⚠ Banned hype/sales word: "${w}"`)
      }
    }
    for (const w of vagueSuperlatives) {
      if (lower.includes(w)) {
        findings.push(`⚠ Vague superlative: "${w}" — replace with specifics`)
      }
    }
    for (const p of urgencyPhrases) {
      if (lower.includes(p)) {
        findings.push(`⚠ Urgency framing: "${p}" — Greyhaven does not use urgency`)
      }
    }

    // Exclamation marks
    const exclamations = (text.match(/!/g) || []).length
    if (exclamations > 0) {
      findings.push(`⚠ Found ${exclamations} exclamation mark(s) — Greyhaven copy does not use them`)
    }

    if (findings.length === 0) {
      return {
        content: [{
          type: 'text' as const,
          text: 'No obvious brand violations found. Still run the self-check list from get_brand_rules({section: "self-check"}) before shipping.',
        }],
      }
    }

    return {
      content: [{
        type: 'text' as const,
        text: `Found ${findings.length} potential brand violation(s):\n\n${findings.join('\n')}\n\nFor detailed guidance, call get_brand_rules() or get_brand_rules({section: "tone"}).`,
      }],
    }
  },
)

// ---------------------------------------------------------------------------
// Resources
// ---------------------------------------------------------------------------

server.resource('brand://guidelines', 'brand://guidelines', async (uri) => {
  return {
    contents: [{
      uri: uri.href,
      mimeType: 'text/markdown',
      text: readBrandSkill(),
    }],
  }
})

server.resource('tokens://all', 'tokens://all', async (uri) => {
  const tokens = getTokens(ROOT)
  return {
    contents: [{
      uri: uri.href,
      mimeType: 'application/json',
      text: JSON.stringify(tokens, null, 2),
    }],
  }
})

for (const component of COMPONENT_CATALOG) {
  const uri = `component://${component.name.toLowerCase()}`
  server.resource(uri, uri, async (resourceUri) => {
    let source = ''
    try {
      source = fs.readFileSync(path.join(ROOT, component.file), 'utf-8')
    } catch {
      source = '(source not readable)'
    }

    return {
      contents: [{
        uri: resourceUri.href,
        mimeType: 'application/json',
        text: JSON.stringify({ ...component, source }, null, 2),
      }],
    }
  })
}

// ---------------------------------------------------------------------------
// Start
// ---------------------------------------------------------------------------

async function main() {
  const transport = new StdioServerTransport()
  await server.connect(transport)
}

main().catch(console.error)
