#!/usr/bin/env node
// Publish-time gate: every PLUGIN.md with a non-empty `tools:` frontmatter
// block must also declare an `mcp:` block. The `tools:` block populates the
// `--allowed-tools mcp__<plugin>__*` spawn argv; without a registered MCP
// server the agent boots with permissions for tools that have no backend.
//
// The session manager throws the same defect at boot
// (tool-surface.ts 'mcp-block-missing'), but boot is on the operator's
// device — by then the bad payload has already been published and installed.
// This gate fires before `npm publish`, in the prepublishOnly chain.
//
// Live repro: Task 357 added visitor-graph tools to buyers/PLUGIN.md without
// the matching mcp: block; the slip-through is what motivates Task 368.

import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = dirname(fileURLToPath(import.meta.url))
const MAXY_CODE_ROOT = resolve(__dirname, '..', '..')

const PLUGIN_ROOTS = [
  // platform/plugins/<plugin>/PLUGIN.md
  { root: join(MAXY_CODE_ROOT, 'platform', 'plugins'), depth: 1 },
  // premium-plugins/<bundle>/PLUGIN.md           (shape b)
  { root: join(MAXY_CODE_ROOT, 'premium-plugins'), depth: 1 },
  // premium-plugins/<bundle>/plugins/<sub>/PLUGIN.md  (shape a)
  { root: join(MAXY_CODE_ROOT, 'premium-plugins'), depth: 2, mid: 'plugins' },
]

function collectPluginDirs() {
  const dirs = []
  for (const spec of PLUGIN_ROOTS) {
    if (!existsSync(spec.root)) continue
    for (const entry of readdirSync(spec.root)) {
      if (entry.startsWith('.')) continue
      const lvl1 = join(spec.root, entry)
      if (!statSync(lvl1).isDirectory()) continue
      if (spec.depth === 1) {
        dirs.push(lvl1)
        continue
      }
      const midDir = join(lvl1, spec.mid)
      if (!existsSync(midDir)) continue
      if (!statSync(midDir).isDirectory()) continue
      for (const sub of readdirSync(midDir)) {
        if (sub.startsWith('.')) continue
        const lvl2 = join(midDir, sub)
        if (!statSync(lvl2).isDirectory()) continue
        dirs.push(lvl2)
      }
    }
  }
  return dirs
}

function extractFrontmatter(md) {
  if (!md.startsWith('---')) return null
  const end = md.indexOf('\n---', 3)
  if (end < 0) return null
  return md.slice(3, end)
}

// Detect a top-level key in YAML frontmatter (key at column 0, followed by `:`).
// The key may be bare (`tools:`) — empty value means "block follows on
// subsequent indented lines" OR "empty list".
function findTopLevelKey(frontmatter, key) {
  const re = new RegExp(`^${key}\\s*:`, 'm')
  return re.test(frontmatter)
}

// Returns true when the `tools:` block has at least one list entry. A bare
// `tools:` with no children, or `tools: []`, is treated as empty (no surface).
function hasNonEmptyToolsBlock(frontmatter) {
  const lines = frontmatter.split('\n')
  let inTools = false
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]
    if (/^tools\s*:/.test(line)) {
      const after = line.replace(/^tools\s*:/, '').trim()
      // Inline form: `tools: []` or `tools: [a, b]`
      if (after.startsWith('[')) {
        return /\[\s*[^\]\s]/.test(after)
      }
      if (after) {
        // Single-line scalar (unusual for tools, but treat as non-empty).
        return true
      }
      inTools = true
      continue
    }
    if (inTools) {
      if (/^\S/.test(line) && line.trim() !== '') {
        // Top-level key after tools: — block ended.
        return false
      }
      if (/^\s*-\s+/.test(line)) return true
    }
  }
  return false
}

function checkPlugin(pluginDir) {
  const manifestPath = join(pluginDir, 'PLUGIN.md')
  if (!existsSync(manifestPath)) return null
  const raw = readFileSync(manifestPath, 'utf-8')
  const fm = extractFrontmatter(raw)
  if (fm === null) return null
  if (!hasNonEmptyToolsBlock(fm)) return null
  if (findTopLevelKey(fm, 'mcp')) return null
  return manifestPath
}

function main() {
  const offenders = []
  for (const dir of collectPluginDirs()) {
    const offender = checkPlugin(dir)
    if (offender) offenders.push({ dir, manifestPath: offender })
  }
  if (offenders.length === 0) {
    console.log('[gate] check-plugin-tools-mcp-consistency: PASS')
    return
  }
  for (const { dir, manifestPath } of offenders) {
    const plugin = dir.split('/').pop()
    console.error(
      `[gate] FATAL: plugin=${plugin} has tools: block but no mcp: block.\n` +
        `  manifest: ${manifestPath}\n` +
        `  Spawns would pass --allowed-tools mcp__${plugin}__* with no registered server.\n` +
        `  Add mcp: block or remove tools: entries.`,
    )
  }
  process.exit(1)
}

main()
