#!/usr/bin/env npx tsx

/**
 * Generates skill/SKILL.md and skill/AGENTS.md from the shared component
 * catalog and W3C DTCG token files. Run via `pnpm skill:build`.
 *
 * Both the MCP server and this script read from lib/catalog.ts and
 * tokens/*.json, so all outputs stay in sync.
 *
 * Outputs:
 *   skill/SKILL.md   — Full design system reference (tokens, components, rules)
 *   skill/AGENTS.md  — Project-level instructions for any AI coding agent
 */

import * as fs from 'fs'
import * as path from 'path'
import { fileURLToPath } from 'url'
import {
  COMPONENT_CATALOG,
  loadTokenFile,
  flattenTokens,
  TOKEN_CATEGORIES,
  type FlatToken,
} from '../lib/catalog.js'

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

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function tokenTable(tokens: FlatToken[]): string {
  const lines = ['| Token | Value | Description |', '|-------|-------|-------------|']
  for (const t of tokens) {
    const val = typeof t.value === 'object' ? JSON.stringify(t.value) : String(t.value)
    lines.push(`| \`${t.path}\` | \`${val}\` | ${t.description || ''} |`)
  }
  return lines.join('\n')
}

function componentCount(): number {
  return COMPONENT_CATALOG.length
}

// ---------------------------------------------------------------------------
// Shared content blocks (used by both SKILL.md and AGENTS.md)
// ---------------------------------------------------------------------------

function buildDesignPhilosophy(): string {
  return `## Design Philosophy

- **TypeScript only**: All code MUST be written in TypeScript (\`.tsx\` / \`.ts\`). Never generate plain JavaScript (\`.jsx\` / \`.js\`).
- **Minimal and restrained**: Off-white + off-black + warm greys. One single accent color (orange). No gradients, no decorative color, no multiple accent hues.
- **Typography-driven**: Source Serif 4/Pro (serif) for headings and body content. Aspekta (sans, self-hosted) for UI labels, buttons, navigation, and form elements.
- **Calm, professional aesthetic**: Tight border-radii, subtle shadows, generous whitespace.
- **Accessibility-first**: Built on Radix UI primitives for keyboard navigation, focus management, screen reader support. Visible focus rings, disabled states, ARIA attributes.
- **Dark mode native**: Thoughtful dark theme using inverted warm greys. Orange accent persists across both modes. Toggled via \`.dark\` class.
- **Framework-agnostic**: Pure React + Radix + Tailwind. No Next.js, no framework-specific imports.
`
}

function buildFontSetup(): string {
  return `## Font Setup

This design system uses two typefaces:

| Role | Font | Usage |
|------|------|-------|
| **Sans (UI)** | Aspekta (self-hosted) | Buttons, nav, labels, forms, metadata |
| **Serif (Content)** | Source Serif 4/Pro | Headings, body text, reading content |

### Aspekta (required)

Aspekta font files live in \`public/fonts/\`. Add \`@font-face\` declarations to your global CSS:

\`\`\`css
/* Minimum set (covers font-weight 400-700) */
@font-face { font-family: 'Aspekta'; font-weight: 400; font-display: swap; src: url('/fonts/Aspekta-400.woff2') format('woff2'); }
@font-face { font-family: 'Aspekta'; font-weight: 500; font-display: swap; src: url('/fonts/Aspekta-500.woff2') format('woff2'); }
@font-face { font-family: 'Aspekta'; font-weight: 600; font-display: swap; src: url('/fonts/Aspekta-600.woff2') format('woff2'); }
@font-face { font-family: 'Aspekta'; font-weight: 700; font-display: swap; src: url('/fonts/Aspekta-700.woff2') format('woff2'); }

/* Or import all weights: */
@import url('/fonts/font-face.css');
\`\`\`

### Font stack CSS variables

\`\`\`css
--font-sans: 'Aspekta', ui-sans-serif, system-ui, sans-serif;
--font-serif: 'Source Serif 4', 'Source Serif Pro', Georgia, serif;
\`\`\`

### Tailwind usage

- \`font-sans\` — Aspekta (UI elements)
- \`font-serif\` — Source Serif (content)

Install fonts via: \`./skill/install.sh /path/to/your/project\`
`
}

function buildTokenReference(): string {
  const lines: string[] = []
  lines.push('## Token Quick Reference\n')
  lines.push('Source of truth: `tokens/*.json` (W3C DTCG format).\n')

  for (const cat of TOKEN_CATEGORIES) {
    try {
      const data = loadTokenFile(ROOT, cat)
      const tokens = flattenTokens(data)
      if (tokens.length === 0) continue

      const title = cat.charAt(0).toUpperCase() + cat.slice(1)
      lines.push(`### ${title}\n`)
      lines.push(tokenTable(tokens))
      lines.push('')
    } catch {
      // skip missing
    }
  }

  return lines.join('\n')
}

function buildComponentCatalog(): string {
  const lines: string[] = []
  lines.push(`## Component Catalog (${componentCount()} components)\n`)
  lines.push('All components live in `components/ui/`. Import with `@/components/ui/<name>`.\n')

  const categories = new Map<string, typeof COMPONENT_CATALOG>()
  for (const c of COMPONENT_CATALOG) {
    if (!categories.has(c.category)) categories.set(c.category, [])
    categories.get(c.category)!.push(c)
  }

  const categoryOrder = ['primitives', 'layout', 'overlay', 'navigation', 'data', 'feedback', 'form', 'composition']

  for (const cat of categoryOrder) {
    const components = categories.get(cat)
    if (!components) continue

    const title = cat.charAt(0).toUpperCase() + cat.slice(1)
    lines.push(`### ${title}\n`)

    for (const c of components) {
      lines.push(`#### ${c.name}`)
      lines.push(`- **File**: \`${c.file}\``)
      lines.push(`- **Exports**: \`${c.exports.join('`, `')}\``)
      lines.push(`- **Description**: ${c.description}`)
      lines.push(`- **Props**: \`${c.props}\``)
      lines.push(`- **Example**:`)
      lines.push('```tsx')
      lines.push(c.example)
      lines.push('```')
      lines.push('')
    }
  }

  return lines.join('\n')
}

function buildCompositionRules(): string {
  return `## Composition Rules

- **Never override component sizing via \`className\`**: Each component exposes \`size\` / \`variant\` props for a reason. Reach for those first. Overriding font-size, padding, or height with arbitrary Tailwind classes (\`text-sm\`, \`px-3\`, \`py-1\`, etc.) fragments the design system. If no variant fits, add a new \`size\`/\`variant\` to the component — don't one-off patch it at the call site.
- **Minimum font size is \`text-xs\` (12px)**: Anything smaller fails accessibility/readability minimums. If you genuinely need smaller text for a specific reason (e.g., a data-dense legend), add an explicit \`// justification: ...\` comment at the call site. Default answer is: use \`text-xs\`.
- **Card spacing**: \`gap-6\` between cards, \`p-6\` internal padding
- **Section rhythm**: \`py-10\` internal padding per section. Colored sections add \`my-8\` to detach from neighbors
- **Button placement**: Primary action right, secondary left
- **Form layout**: Vertical stack with \`gap-4\`, labels above inputs
- **Navbar**: Fixed top, \`z-50\`, \`h-16\`, logo left, nav center, actions right
- **Typography pairing**: Serif (\`font-serif\`) for content headings, sans (\`font-sans\`) for UI labels/buttons
- **Color restraint**: Trust the default component variants for orange accent -- they apply it at the right scale. Don't apply \`bg-primary\` to large surfaces, containers, or section backgrounds
- **Focus pattern**: \`focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]\`
- **Disabled pattern**: \`disabled:pointer-events-none disabled:opacity-50\`
- **Aria-invalid pattern**: \`aria-invalid:ring-destructive/20 aria-invalid:border-destructive\`
- **Slot naming**: All components use \`data-slot="component-name"\`
- **Icon sizing**: \`[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\`
`
}

function buildHtmxLayer(): string {
  return `## HTMX / Server-Rendered Usage

For projects that cannot use React (HTMX, Django templates, Rails ERB, Go \`html/template\`, Astro SSR, etc.), the design system ships a framework-agnostic CSS layer: \`dist/greyhaven.htmx.css\`.

### What it is

An auto-generated stylesheet derived from \`components/ui/*.tsx\`. Every \`data-slot\` attribute gets a \`@layer components\` rule. \`cva\` variants become attribute selectors (\`[data-variant=...]\`, \`[data-size=...]\`). Default variants apply via \`:not([data-variant])\` so consumers can omit the attribute.

### Usage

1. Install: \`./skill/install.sh /path/to/project --htmx-css\`
2. Import in your Tailwind v4 input CSS: \`@import "./greyhaven.htmx.css";\`
3. Emit HTML with \`data-slot\` / \`data-variant\` / \`data-size\` attributes:

\`\`\`html
<div data-slot="card">
  <div data-slot="card-header">
    <div data-slot="card-title">Title</div>
    <div data-slot="card-description">Description</div>
  </div>
  <div data-slot="card-content">Body</div>
</div>

<button data-slot="button" data-variant="default">Save</button>
<button data-slot="button" data-variant="outline" data-size="sm">Cancel</button>

<span data-slot="badge" data-variant="success">Active</span>
\`\`\`

### Scope

- **Fully static** (pure CSS, no JS): Card, Button, Badge, Input, Label, Textarea, Table, Separator, Code, Kbd, Progress, Avatar, Skeleton, Alert, Pagination, Breadcrumb, Navbar (solid variant), Spinner, AspectRatio, Empty, Hero, Section, Footer, CtaSection, ButtonGroup, InputGroup, Toast.
- **Visual-only** (CSS is correct but needs your own state JS): Dialog, Dropdown, Popover, Select, Combobox, Accordion, Tabs, Tooltip, Drawer, Sheet, Sidebar, Collapsible, NavigationMenu, Menubar, ContextMenu, HoverCard, Command, AlertDialog, InputOtp, Carousel. Pair with Alpine.js (\`x-data\`, \`x-show\`, \`@click\`) or native HTML primitives (\`<dialog>\`, \`<details>\`).

### Regenerate

\`\`\`bash
pnpm htmx-css:build
\`\`\`

`
}

function buildExtensionProtocol(): string {
  return `## Extension Protocol

When adding new components to the system:

1. **Use CVA** for variants (\`class-variance-authority\`)
2. **Accept HTML element props** via spread: \`React.ComponentProps<'div'>\`
3. **Use \`data-slot\`** attribute: \`data-slot="component-name"\`
4. **Use \`cn()\`** from \`@/lib/utils\` for class merging
5. **Follow focus/disabled/aria patterns** from existing components
6. **Use semantic tokens only** -- never raw hex colors
7. **Support \`asChild\`** via \`@radix-ui/react-slot\` for polymorphism where appropriate
8. **Add to Storybook** with \`tags: ['autodocs']\` and all variant stories
9. **Add to \`lib/catalog.ts\`** so MCP server and SKILL.md pick it up automatically
10. **Run \`pnpm skill:build\`** to regenerate this file

### Template

\`\`\`tsx
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'

const myComponentVariants = cva('base-classes', {
  variants: {
    variant: { default: 'default-classes' },
    size: { default: 'size-classes' },
  },
  defaultVariants: { variant: 'default', size: 'default' },
})

function MyComponent({
  className, variant, size, ...props
}: React.ComponentProps<'div'> & VariantProps<typeof myComponentVariants>) {
  return (
    <div
      data-slot="my-component"
      className={cn(myComponentVariants({ variant, size, className }))}
      {...props}
    />
  )
}

export { MyComponent, myComponentVariants }
\`\`\`
`
}

// ---------------------------------------------------------------------------
// SKILL.md (Claude Code)
// ---------------------------------------------------------------------------

function generateSkill(): string {
  return [
    `# Greyhaven Design System -- Claude Skill

> **Auto-generated** by \`scripts/generate-skill.ts\` -- DO NOT EDIT by hand.
> Re-generate: \`pnpm skill:build\`
>
> **Components**: ${componentCount()} | **Style**: shadcn/ui "new-york"
> **Stack**: React 19, Radix UI, Tailwind CSS v4, CVA, tailwind-merge, clsx, Lucide icons
> **Framework-agnostic**: No Next.js imports. Works with Vite, Remix, Astro, CRA, or any React setup.

This skill gives you full context to generate pixel-perfect, on-brand UI using the Greyhaven Design System. Every component lives in \`components/ui/\`. Use semantic tokens, never raw colors. Follow the patterns exactly. **ALWAYS use TypeScript (.tsx/.ts) — never plain JavaScript.**
`,
    '---\n',
    buildDesignPhilosophy(),
    '---\n',
    buildFontSetup(),
    '---\n',
    buildTokenReference(),
    '---\n',
    buildComponentCatalog(),
    '---\n',
    buildCompositionRules(),
    '---\n',
    buildHtmxLayer(),
    '---\n',
    buildExtensionProtocol(),
  ].join('\n')
}

// ---------------------------------------------------------------------------
// AGENTS.md (project-level instructions for non-Claude AI agents)
// ---------------------------------------------------------------------------

function generateAgent(): string {
  // Count components by category for the summary
  const categories = new Map<string, number>()
  for (const c of COMPONENT_CATALOG) {
    categories.set(c.category, (categories.get(c.category) || 0) + 1)
  }
  const categorySummary = Array.from(categories.entries())
    .map(([cat, count]) => `${cat} (${count})`)
    .join(', ')

  return `# Project Instructions

> **Auto-generated** by the Greyhaven Design System.
> Re-generate: \`pnpm skill:build\` in the design system repo.
>
> Copy this file to your project root as \`AGENTS.md\` (standard), \`CLAUDE.md\`,
> \`.cursorrules\`, or \`.github/copilot-instructions.md\` depending on your AI tool.

This project uses the **Greyhaven Design System**.

## Rules

- **ALWAYS use TypeScript** (\`.tsx\` / \`.ts\`). NEVER generate plain JavaScript (\`.jsx\` / \`.js\`).
- Use the \`greyhaven\` SKILL.md for full design system context (tokens, components, composition rules). It should be installed at \`.claude/skills/greyhaven-design-system.md\` or accessible to your AI tool.
- If the \`greyhaven\` MCP server is available, use its tools:
  - \`list_components()\` to find the right component for a UI need
  - \`get_component(name)\` to get exact props, variants, and usage examples
  - \`validate_colors(code)\` to check code for off-brand colors
  - \`suggest_component(description)\` to get recommendations
- Import components from \`components/ui/\` (or \`@/components/ui/\` with path alias)
- Never use raw hex colors -- use semantic Tailwind classes (\`bg-primary\`, \`text-foreground\`, \`border-border\`, etc.)
- Use \`font-sans\` (Aspekta) for UI elements: buttons, nav, labels, forms
- Use \`font-serif\` (Source Serif) for content: headings, body text
- Trust the design system's default component variants for accent -- they apply orange at the right scale. Don't apply \`bg-primary\` to large surfaces, containers, or section backgrounds
- All components are framework-agnostic React (no Next.js, no framework-specific imports)
- Dark mode is toggled via the \`.dark\` class -- use semantic tokens that adapt automatically

## Component Summary

${componentCount()} components across ${categories.size} categories: ${categorySummary}.

For full component specs, props, and examples, refer to the SKILL.md file or use the MCP \`get_component(name)\` tool.

## Key Patterns

- **CVA variants**: Components use \`class-variance-authority\` for variant props
- **Slot composition**: Components use \`data-slot="name"\` attributes
- **Class merging**: Always use \`cn()\` from \`@/lib/utils\` (clsx + tailwind-merge)
- **Focus rings**: \`focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]\`
- **Disabled**: \`disabled:pointer-events-none disabled:opacity-50\`
- **Card spacing**: \`gap-6\` between cards, \`p-6\` internal padding
- **Section rhythm**: \`py-16\` between major sections
- **Form layout**: Vertical stack with \`gap-4\`, labels above inputs

## Font Setup

If fonts aren't loaded yet, add to your global CSS:
\`\`\`css
@font-face { font-family: 'Aspekta'; font-weight: 400; font-display: swap; src: url('/fonts/Aspekta-400.woff2') format('woff2'); }
@font-face { font-family: 'Aspekta'; font-weight: 500; font-display: swap; src: url('/fonts/Aspekta-500.woff2') format('woff2'); }
@font-face { font-family: 'Aspekta'; font-weight: 600; font-display: swap; src: url('/fonts/Aspekta-600.woff2') format('woff2'); }
@font-face { font-family: 'Aspekta'; font-weight: 700; font-display: swap; src: url('/fonts/Aspekta-700.woff2') format('woff2'); }
\`\`\`
`
}

// ---------------------------------------------------------------------------
// Brand addendum (appended to AGENTS.md only when --brand-skill is installed)
// ---------------------------------------------------------------------------

function buildBrandAddendum(): string {
  return `---

## Brand Voice and Messaging

This project generates user-facing content (marketing copy, CTAs, landing pages, product explanations, emails) and MUST follow the Greyhaven brand voice.

### Brand Rules

- **Before writing any user-facing copy**, read the brand skill:
  - Claude Code / compatible tools: \`.claude/skills/greyhaven-brand.md\` (full voice/tone/messaging reference)
  - Or via MCP: call \`get_brand_rules()\` (or a specific section: \`positioning\`, \`axes\`, \`tone\`, \`writing-rules\`, \`reasoning-patterns\`, \`cta\`, \`logo\`, \`self-check\`)
- **Before shipping any user-facing copy**, validate it:
  - Via MCP: call \`validate_copy(text)\` to lint for hype words, vague superlatives, urgency framing, and exclamation marks
  - Or manually run the 8-item self-check list from the brand skill

### Core Voice (memorize)

- **Direct. Plain-spoken technical.** Write like an engineer who explains systems cleanly, without mystique or theatrics.
- **No** hype adjectives (\`revolutionary\`, \`cutting-edge\`, \`seamless\`, \`game-changing\`, \`powerful\`).
- **No** evangelism verbs (\`unleash\`, \`transform\`, \`empower\`, \`supercharge\`, \`unlock\`).
- **No** sales language, urgency framing, exclamation marks.
- **No** jargon for its own sake. Prefer plain words: "where the data goes" over "data paths"; "things the system relies on" over "dependencies".
- **Yes** specifics, causal reasoning, concrete outcomes.

### The Three Brand Axes

Copy must land on the correct side of each:
1. **Containment** — systems run inside the perimeter, nothing leaks (not cloud/SaaS narratives)
2. **Human-centered** — built around how people actually work (not around model capabilities)
3. **Engineered** — from real deployments and constraints (not vision-first, theatrical, speculative)

### Reasoning Patterns to Use

Structure explanations as:
- **Cause → Effect**
- **Constraint → Outcome**
- **Observation → Explanation**
- **Finite Scope → Concrete Result**

### CTA Guidance

- **Good**: "Map your first process", "See how it runs in your environment", "Review the architecture", "Get a working prototype in 48 hours"
- **Avoid**: "Unleash the power of AI", "Transform your business", "Don't miss out!", "Get started today!"

### Logo Usage

Logos live in \`public/logos/\` after install. See the brand skill for the full rules (clearspace, minimum sizes, what to avoid).

- **Full logo** (symbol + wordmark): \`gh-logo-positive-full-black.svg\` (light bg), \`gh-logo-white.svg\` (dark bg), \`gh-logo-offblack.svg\` (warm-neutral)
- **Symbol only**: \`gh-symbol-full-black.svg\`, \`gh-symbol-full-white.svg\` — only when Greyhaven recognition is already established
- **Product lockups**: \`greyproxy-positive.svg\`, \`greywall-positive.svg\`
- **Never**: change opacity, apply new colors, stretch, rotate, apply gradients/shadows, alter the symbol-to-wordmark ratio

### One-Line Test

Before writing a sentence, ask: *Would an engineer who understands the system read this and feel it's accurate, direct, and free of hype?* If not, rewrite.
`
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

function main() {
  const outDir = path.join(ROOT, 'skill')
  fs.mkdirSync(outDir, { recursive: true })

  // SKILL.md
  const skill = generateSkill()
  const skillPath = path.join(outDir, 'SKILL.md')
  fs.writeFileSync(skillPath, skill, 'utf-8')
  const skillLines = skill.split('\n').length
  console.log(`skill/SKILL.md generated (${skillLines} lines, ${componentCount()} components)`)

  // AGENTS.md (design system only — default)
  const agent = generateAgent()
  const agentPath = path.join(outDir, 'AGENTS.md')
  fs.writeFileSync(agentPath, agent, 'utf-8')
  const agentLines = agent.split('\n').length
  console.log(`skill/AGENTS.md generated (${agentLines} lines, ${componentCount()} components)`)

  // AGENTS.brand.md (design system + brand voice addendum — installed via --brand-skill)
  const agentBrand = agent + '\n' + buildBrandAddendum()
  const agentBrandPath = path.join(outDir, 'AGENTS.brand.md')
  fs.writeFileSync(agentBrandPath, agentBrand, 'utf-8')
  const agentBrandLines = agentBrand.split('\n').length
  console.log(`skill/AGENTS.brand.md generated (${agentBrandLines} lines, ${componentCount()} components)`)
}

main()
