/**
 * Plugin cache doctor — detects pathologies in the on-disk plugin cache.
 *
 * Cache layout: `<cacheRoot>/<marketplace>/<plugin>/<versionId>/`
 * Each version dir should contain either `.claude-plugin/plugin.json` or
 * `.sandcode-plugin/plugin.json`, plus the plugin's content directories
 * (agents, commands, hooks, skills, etc).
 *
 * Known pathologies:
 *   - missing manifest: version dir has no plugin.json anywhere
 *   - recursive plugin-name nesting: version dir contains a subdir named
 *     after the plugin itself (seen when an install re-runs on top of
 *     an existing install, producing `cache/M/P/V/P/V/P/...`)
 *   - recursive marketplace-name nesting: similar but for the marketplace
 */

import { readdir, rm } from 'fs/promises'
import { join } from 'path'

export type VersionEntry = {
  /** Relative path inside the cache root, e.g. `marketplace/plugin/versionId` */
  versionPath: string
  marketplace: string
  plugin: string
  versionId: string
  hasClaudePlugin: boolean
  hasSandcodePlugin: boolean
  /** Names of immediate subdirectories inside the version dir. */
  subdirs: string[]
}

export type CacheIssueKind =
  | 'missing-manifest'
  | 'recursive-plugin-name'
  | 'recursive-marketplace-name'

export type CacheIssue = {
  kind: CacheIssueKind
  path: string
  detail?: string
}

const MANIFEST_DIRS = new Set(['.claude-plugin', '.sandcode-plugin'])

/**
 * Inspect a list of version entries and return the issues found.
 * Pure — no I/O. Unit tested against fabricated entries.
 */
export function diagnoseVersionEntries(entries: VersionEntry[]): CacheIssue[] {
  const issues: CacheIssue[] = []

  for (const e of entries) {
    if (!e.hasClaudePlugin && !e.hasSandcodePlugin) {
      issues.push({
        kind: 'missing-manifest',
        path: e.versionPath,
        detail: 'no .claude-plugin/plugin.json or .sandcode-plugin/plugin.json found',
      })
    }

    for (const name of e.subdirs) {
      if (MANIFEST_DIRS.has(name)) continue
      if (name === e.plugin && e.plugin !== '') {
        issues.push({
          kind: 'recursive-plugin-name',
          path: e.versionPath,
          detail: `subdir "${name}" matches plugin id — likely nested install`,
        })
      }
      if (name === e.marketplace && e.marketplace !== '') {
        issues.push({
          kind: 'recursive-marketplace-name',
          path: e.versionPath,
          detail: `subdir "${name}" matches marketplace id — likely nested install`,
        })
      }
    }
  }

  return issues
}

/**
 * Walk the plugin cache root and build VersionEntry[]. Separated from
 * diagnoseVersionEntries so the diagnosis logic stays pure and testable.
 */
export async function scanPluginCache(cacheRoot: string): Promise<VersionEntry[]> {
  const entries: VersionEntry[] = []

  const marketplaces = await safeReadDirs(cacheRoot)
  for (const marketplace of marketplaces) {
    const mpPath = join(cacheRoot, marketplace)
    const plugins = await safeReadDirs(mpPath)
    for (const plugin of plugins) {
      const pluginPath = join(mpPath, plugin)
      const versions = await safeReadDirs(pluginPath)
      for (const versionId of versions) {
        const versionPath = join(pluginPath, versionId)
        const subdirs = await safeReadDirs(versionPath)
        entries.push({
          versionPath: `${marketplace}/${plugin}/${versionId}`,
          marketplace,
          plugin,
          versionId,
          hasClaudePlugin: subdirs.includes('.claude-plugin'),
          hasSandcodePlugin: subdirs.includes('.sandcode-plugin'),
          subdirs,
        })
      }
    }
  }

  return entries
}

async function safeReadDirs(dir: string): Promise<string[]> {
  try {
    const entries = await readdir(dir, { withFileTypes: true })
    return entries.filter((e) => e.isDirectory()).map((e) => e.name)
  } catch {
    return []
  }
}

/**
 * Remove a recursive nested install by deleting the inner directory
 * that matches the plugin or marketplace id. Returns paths that were
 * actually removed (empty list if nothing matched).
 */
export async function repairRecursiveNesting(
  cacheRoot: string,
  issue: CacheIssue,
): Promise<string[]> {
  if (issue.kind !== 'recursive-plugin-name' && issue.kind !== 'recursive-marketplace-name') {
    return []
  }
  const versionDir = join(cacheRoot, issue.path)
  const subdirs = await safeReadDirs(versionDir)
  const removed: string[] = []
  for (const name of subdirs) {
    if (MANIFEST_DIRS.has(name)) continue
    const versionPathParts = issue.path.split('/')
    const [mp, plug] = versionPathParts
    if (name === plug || name === mp) {
      const target = join(versionDir, name)
      try {
        await rm(target, { recursive: true, force: true })
        removed.push(target)
      } catch {
        // Swallow — doctor reports best-effort
      }
    }
  }
  return removed
}
