const t = require('tap')
const { join } = require('path')
const walk = require('ignore-walk')
const fs = require('fs/promises')
const yaml = require('yaml')
const {
  paths: { content: CONTENT_DIR, nav: NAV, template: TEMPLATE },
} = require('../lib/index.js')

// Helper to generate nav entries from content structure
const generateNavFromContent = (content, prefix = '') => {
  const entries = []
  for (const [key, value] of Object.entries(content)) {
    if (key.endsWith('.md')) {
      const name = key.replace('.md', '')
      const url = prefix ? `${prefix}/${name}` : `/${name}`
      entries.push({ url })
    } else if (typeof value === 'object') {
      const children = generateNavFromContent(value, `/${key}`)
      if (children.length > 0) {
        entries.push(...children)
      }
    }
  }
  return entries
}

const testBuildDocs = async (t, { verify, commandLoader, ...opts } = {}) => {
  const mockedBuild = require('../lib/build.js')

  const fixtures = {
    man: {},
    html: {},
    md: {},
    ...opts,
  }

  // Ensure commands directory exists if content is provided
  if (fixtures.content && !fixtures.content.commands) {
    fixtures.content.commands = {}
  }

  // If custom content is provided but not custom nav, auto-generate nav from content
  if (fixtures.content && !fixtures.nav) {
    const navEntries = generateNavFromContent(fixtures.content)
    fixtures.nav = yaml.stringify(navEntries)
  }

  const root = t.testdir(fixtures)

  const paths = {
    content: fixtures.content ? join(root, 'content') : CONTENT_DIR,
    template: fixtures.template ? join(root, 'template') : TEMPLATE,
    nav: fixtures.nav ? join(root, 'nav') : NAV,
    man: join(root, 'man'),
    html: join(root, 'html'),
    md: join(root, 'md'),
    // Skip auto-generation of missing docs when using test fixtures
    skipAutoGenerate: !!fixtures.content,
    // Skip nav generation when using test fixtures with custom content
    skipGenerateNav: !!fixtures.content,
    // Custom command loader for testing
    commandLoader,
  }

  return {
    results: await mockedBuild({ ...paths, verify }),
    root,
    ...paths,
  }
}

// Helper to create a standard command doc with placeholders
const createCommandDoc = (title, description) => `---
title: ${title}
section: 1
description: ${description}
---

### Synopsis

<!-- AUTOGENERATED USAGE DESCRIPTIONS -->

### Configuration

<!-- AUTOGENERATED CONFIG DESCRIPTIONS -->
`

// Helper to read and return HTML content from a built doc
const readHtmlDoc = async (htmlPath, commandName) => {
  const htmlFile = join(htmlPath, `commands/${commandName}.html`)
  return await fs.readFile(htmlFile, 'utf-8')
}

// Helper to test a command doc with common assertions
const testCommandDoc = async (t, commandName, description, assertions = {}) => {
  const doc = createCommandDoc(commandName, description)
  const { html } = await testBuildDocs(t, {
    content: {
      commands: { [`${commandName}.md`]: doc },
    },
    nav: `- url: /commands/${commandName}`,
  })

  const htmlContent = await readHtmlDoc(html, commandName)

  // Default assertions
  t.ok(htmlContent.length > 0, `generates HTML for ${commandName} command`)

  // Custom assertions
  if (assertions.match) {
    for (const pattern of assertions.match) {
      t.match(htmlContent, pattern, `contains expected pattern: ${pattern}`)
    }
  }

  return { html, htmlContent }
}

// Helper to create test directory structure for autoGenerateMissingDocs tests
const createAutoGenTestDir = (t, { existingDocs = {}, navEntries = [], commandFiles = {} }) => {
  const navYml = `
- title: CLI Commands
  children:
    - title: npm
      url: /commands/npm
${navEntries.map(entry => `    - title: ${entry.title}\n      url: ${entry.url}\n      description: ${entry.description}`).join('\n')}
`

  return t.testdir({
    content: {
      commands: existingDocs,
    },
    'nav.yml': navYml,
    lib: {
      commands: commandFiles,
    },
  })
}

// Helper to verify nav structure after auto-generation
const verifyNavStructure = async (navPath) => {
  const navContent = await fs.readFile(navPath, 'utf-8')
  const navData = yaml.parse(navContent)
  const commandsSection = navData.find(s => s.title === 'CLI Commands')

  return { navContent, navData, commandsSection }
}

t.test('builds and verifies the real docs', async (t) => {
  const { man, html, md, results } = await testBuildDocs(t, { verify: true })

  const allFiles = (await Promise.all([
    walk({ path: man }).then(r => r.length),
    walk({ path: html }).then(r => r.length),
    walk({ path: md }).then(r => r.length),
  ])).reduce((a, b) => a + b, 0)

  t.equal(allFiles, results.length)
})

t.test('fails on mismatched nav', async t => {
  await t.rejects(() => testBuildDocs(t, {
    content: { 'test.md': '' },
    nav: '- url: /test2',
  }), 'Documentation navigation (nav.yml) does not match filesystem')
})

t.test('missing placeholders', async t => {
  t.test('command', async t => {
    await t.rejects(testBuildDocs(t, {
      content: {
        commands: { 'npm-access.md': '<!-- AUTOGENERATED USAGE DESCRIPTIONS -->' },
      },
    }), /npm-access\.md/)
    await t.rejects(testBuildDocs(t, {
      content: {
        commands: { 'npm-access.md': '<!-- AUTOGENERATED CONFIG DESCRIPTIONS -->' },
      },
    }), /npm-access\.md/)
  })

  t.test('definitions', async t => {
    await t.rejects(testBuildDocs(t, {
      content: {
        'using-npm': { 'config.md': '<!-- AUTOGENERATED CONFIG SHORTHANDS -->' },
      },
    }), /config\.md/)
    await t.rejects(testBuildDocs(t, {
      content: {
        'using-npm': { 'config.md': '<!-- AUTOGENERATED CONFIG DESCRIPTIONS -->' },
      },
    }), /config\.md/)
  })
})

t.test('html', async t => {
  // these don't happen anywhere in the docs so test this for coverage
  // but we test for coverage
  t.test('files can link to root pages', async t => {
    await testBuildDocs(t, {
      content: { 'test.md': '[Test](/test)' },
      nav: '- url: /test',
    })
  })

  t.test('succeeds with empty content', async t => {
    await testBuildDocs(t, {
      content: { 'test.md': '' },
      nav: '- url: /test',
      template: '{{ content }}',
    })
  })

  t.test('fails on missing vars in template', async t => {
    await t.rejects(() => testBuildDocs(t, {
      template: '{{ hello }}',
    }), /\{\{ hello \}\}/)
  })

  t.test('rewrites img src', async t => {
    await testBuildDocs(t, {
      content: { 'test.md': '![](/src)' },
      nav: '- url: /test',
    })
  })
})

t.test('command-specific definitions and exclusive parameters', async t => {
  // Test through the actual doc building process with real commands
  t.test('config command uses params correctly', async t => {
    await testCommandDoc(t, 'npm-config', 'Manage the npm configuration files')
  })

  t.test('install command includes exclusive save parameters', async t => {
    const { htmlContent } = await testCommandDoc(t, 'npm-install', 'Install a package', {
      match: [/save/],
    })

    // The install command should have save-related params due to exclusive expansion
    t.match(htmlContent, /save/, 'includes save-related configuration')
  })
})

t.test('autoGenerateMissingDocs', async t => {
  const { autoGenerateMissingDocs } = require('../lib/build.js')

  t.test('generates docs for missing commands', async t => {
    const testDir = createAutoGenTestDir(t, {
      existingDocs: {
        'npm-access.md': createCommandDoc('npm-access', 'Set access level on published packages'),
      },
      navEntries: [
        { title: 'npm access', url: '/commands/npm-access', description: 'Set access level on published packages' },
      ],
      commandFiles: {
        'access.js': `
class AccessCommand {
  static description = 'Set access level on published packages'
}
module.exports = AccessCommand
`,
        'testcmd.js': `
class TestCommand {
  static description = 'A test command'
}
module.exports = TestCommand
`,
      },
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')
    const commandsPath = join(testDir, 'lib', 'commands')

    await autoGenerateMissingDocs(contentPath, navPath, commandsPath)

    // Verify the doc was created
    const testcmdDocPath = join(contentPath, 'commands', 'npm-testcmd.md')
    const docExists = await fs.access(testcmdDocPath).then(() => true).catch(() => false)
    t.ok(docExists, 'creates documentation file for missing command')

    // Verify the doc has correct content
    const docContent = await fs.readFile(testcmdDocPath, 'utf-8')
    t.match(docContent, /title: npm-testcmd/, 'doc has correct title')
    t.match(docContent, /description: A test command/, 'doc has correct description')
    t.match(docContent, /<!-- AUTOGENERATED USAGE DESCRIPTIONS -->/, 'doc has usage placeholder')
    t.match(docContent, /<!-- AUTOGENERATED CONFIG DESCRIPTIONS -->/, 'doc has config placeholder')
    t.match(docContent, /A test command/, 'doc has description in body')
  })

  t.test('updates nav.yml for new commands', async t => {
    const testDir = createAutoGenTestDir(t, {
      existingDocs: {
        'npm-existing.md': `---
title: npm-existing
section: 1
description: Existing command
---`,
      },
      navEntries: [
        { title: 'npm existing', url: '/commands/npm-existing', description: 'Existing command' },
      ],
      commandFiles: {
        'existing.js': `
class ExistingCommand {
  static description = 'Existing command'
}
module.exports = ExistingCommand
`,
        'newcmd.js': `
class NewCommand {
  static description = 'New command'
}
module.exports = NewCommand
`,
      },
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')
    const commandsPath = join(testDir, 'lib', 'commands')

    await autoGenerateMissingDocs(contentPath, navPath, commandsPath)

    // Read and verify nav.yml was updated
    const { commandsSection } = await verifyNavStructure(navPath)

    t.ok(commandsSection, 'nav has CLI Commands section')
    const newCmdEntry = commandsSection.children.find(c => c.url === '/commands/npm-newcmd')
    t.ok(newCmdEntry, 'nav has entry for new command')
    t.equal(newCmdEntry.title, 'npm newcmd', 'nav entry has correct title')
    t.equal(newCmdEntry.description, 'New command', 'nav entry has correct description')
  })

  t.test('sorts nav children alphabetically', async t => {
    const testDir = createAutoGenTestDir(t, {
      existingDocs: {},
      navEntries: [
        { title: 'npm zebra', url: '/commands/npm-zebra', description: 'Zebra command' },
        { title: 'npm alpha', url: '/commands/npm-alpha', description: 'Alpha command' },
      ],
      commandFiles: {
        'zebra.js': `module.exports = { description: 'Zebra command' }`,
        'alpha.js': `module.exports = { description: 'Alpha command' }`,
        'beta.js': `module.exports = { description: 'Beta command' }`,
      },
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')
    const commandsPath = join(testDir, 'lib', 'commands')

    await autoGenerateMissingDocs(contentPath, navPath, commandsPath)

    // Verify sorting
    const { commandsSection } = await verifyNavStructure(navPath)

    const titles = commandsSection.children.map(c => c.title)

    // npm should be first
    t.equal(titles[0], 'npm', 'npm command is first')

    // Rest should be alphabetically sorted
    const rest = titles.slice(1)
    const sorted = [...rest].sort()
    t.same(rest, sorted, 'remaining commands are alphabetically sorted')
    t.ok(titles.includes('npm alpha'), 'includes alpha')
    t.ok(titles.includes('npm beta'), 'includes beta')
    t.ok(titles.includes('npm zebra'), 'includes zebra')
  })

  t.test('handles commands without description', async t => {
    const testDir = createAutoGenTestDir(t, {
      existingDocs: {},
      navEntries: [],
      commandFiles: {
        'nodesc.js': `module.exports = {}`,
      },
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')
    const commandsPath = join(testDir, 'lib', 'commands')

    await autoGenerateMissingDocs(contentPath, navPath, commandsPath)

    // Verify fallback description
    const docPath = join(contentPath, 'commands', 'npm-nodesc.md')
    const docContent = await fs.readFile(docPath, 'utf-8')
    t.match(docContent, /description: The nodesc command/, 'uses fallback description in frontmatter')
    t.match(docContent, /The nodesc command/, 'uses fallback description in body')
  })

  t.test('does not add duplicate entries to nav', async t => {
    const testDir = createAutoGenTestDir(t, {
      existingDocs: {},
      navEntries: [
        { title: 'npm duplicate', url: '/commands/npm-duplicate', description: 'Already exists' },
      ],
      commandFiles: {
        'duplicate.js': `module.exports = { description: 'Already exists' }`,
      },
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')
    const commandsPath = join(testDir, 'lib', 'commands')

    await autoGenerateMissingDocs(contentPath, navPath, commandsPath)

    // Verify no duplicate
    const { commandsSection } = await verifyNavStructure(navPath)

    const duplicateEntries = commandsSection.children.filter(c => c.url === '/commands/npm-duplicate')
    t.equal(duplicateEntries.length, 1, 'does not create duplicate nav entries')
  })

  t.test('skips update when no missing docs', async t => {
    const testDir = createAutoGenTestDir(t, {
      existingDocs: {
        'npm-complete.md': `---
title: npm-complete
section: 1
description: Complete command
---`,
      },
      navEntries: [
        { title: 'npm complete', url: '/commands/npm-complete', description: 'Complete command' },
      ],
      commandFiles: {
        'complete.js': `module.exports = { description: 'Complete command' }`,
      },
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')
    const commandsPath = join(testDir, 'lib', 'commands')

    const navBefore = await fs.readFile(navPath, 'utf-8')
    await autoGenerateMissingDocs(contentPath, navPath, commandsPath)
    const navAfter = await fs.readFile(navPath, 'utf-8')

    t.equal(navBefore, navAfter, 'does not modify nav when no missing docs')
  })

  t.test('handles nav without CLI Commands section', async t => {
    const testDir = t.testdir({
      content: {
        commands: {},
      },
      'nav.yml': `
- title: Other Section
  children: []
`,
      lib: {
        commands: {
          'test.js': `module.exports = { description: 'Test command' }`,
        },
      },
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')
    const commandsPath = join(testDir, 'lib', 'commands')

    // Should not throw, just skip nav update
    await autoGenerateMissingDocs(contentPath, navPath, commandsPath)

    // Doc should still be created
    const docPath = join(contentPath, 'commands', 'npm-test.md')
    const docExists = await fs.access(docPath).then(() => true).catch(() => false)
    t.ok(docExists, 'creates doc even when nav section missing')
  })

  t.test('handles nav with CLI Commands but no children', async t => {
    const testDir = t.testdir({
      content: {
        commands: {},
      },
      'nav.yml': `
- title: CLI Commands
`,
      lib: {
        commands: {
          'test.js': `module.exports = { description: 'Test command' }`,
        },
      },
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')
    const commandsPath = join(testDir, 'lib', 'commands')

    // Should not throw, just skip nav children update
    await autoGenerateMissingDocs(contentPath, navPath, commandsPath)

    // Doc should still be created
    const docPath = join(contentPath, 'commands', 'npm-test.md')
    const docExists = await fs.access(docPath).then(() => true).catch(() => false)
    t.ok(docExists, 'creates doc even when children missing')
  })

  t.test('handles npm command not in first position', async t => {
    const testDir = createAutoGenTestDir(t, {
      existingDocs: {},
      navEntries: [
        { title: 'npm alpha', url: '/commands/npm-alpha', description: 'Alpha command' },
      ],
      commandFiles: {
        'alpha.js': `module.exports = { description: 'Alpha command' }`,
        'beta.js': `module.exports = { description: 'Beta command' }`,
      },
    })

    // Manually adjust nav to put npm not first
    const navPath = join(testDir, 'nav.yml')
    await fs.writeFile(navPath, `
- title: CLI Commands
  children:
    - title: npm alpha
      url: /commands/npm-alpha
    - title: npm
      url: /commands/npm
`)

    const contentPath = join(testDir, 'content')
    const commandsPath = join(testDir, 'lib', 'commands')

    await autoGenerateMissingDocs(contentPath, navPath, commandsPath)

    // Verify npm moved to first position
    const { commandsSection } = await verifyNavStructure(navPath)

    const titles = commandsSection.children.map(c => c.title)
    t.equal(titles[0], 'npm', 'npm command moved to first position')
  })

  t.test('calls autoGenerateMissingDocs via run with default skipAutoGenerate', async t => {
    // This test ensures the default parameter path is covered
    const build = require('../lib/build.js')
    const testDir = t.testdir({
      content: {
        commands: {
          'npm-test.md': createCommandDoc('npm-test', 'Test'),
        },
      },
      'nav.yml': `
- title: CLI Commands
  url: /commands/npm-test
`,
      lib: {
        commands: {
          'test.js': `module.exports = { description: 'Test' }`,
        },
      },
    })

    const template = '<html>{{ content }}</html>'
    await fs.writeFile(join(testDir, 'template.html'), template)

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')
    const templatePath = join(testDir, 'template.html')
    const manPath = join(testDir, 'man')
    const htmlPath = join(testDir, 'html')
    const mdPath = join(testDir, 'md')

    // Call run with skipAutoGenerate set to true to avoid hitting real commands
    const results = await build({
      content: contentPath,
      template: templatePath,
      nav: navPath,
      man: manPath,
      html: htmlPath,
      md: mdPath,
      skipAutoGenerate: true,
    })

    t.ok(results.length > 0, 'build runs successfully')
  })
})

t.test('command-specific definitions with missing command file', async t => {
  // This test targets the catch block in index.js lines 110-113
  // Use a command that exists and has params - the catch block is for safety
  // when command-specific definitions can't be loaded
  await testCommandDoc(t, 'npm-install', 'Install a package', {
    match: [/install/],
  })
})

t.test('generateNav', async t => {
  const { generateNav } = require('../lib/build.js')

  t.test('commands directory does not exist', async t => {
    // Tests line 63: await dirExists(docsCommandsPath) ? await fs.readdir(docsCommandsPath) : []
    // When commands directory doesn't exist, should return empty array
    const testDir = t.testdir({
      content: {
        // No commands directory
        'configuring-npm': {
          'install.md': `---
title: Install
section: 5
description: Download and install node and npm
---`,
        },
      },
      'nav.yml': '',
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')

    await generateNav(contentPath, navPath)

    const navContent = await fs.readFile(navPath, 'utf-8')
    const navData = yaml.parse(navContent)

    // Should NOT have CLI Commands section since commands dir doesn't exist
    const commandsSection = navData.find(s => s.title === 'CLI Commands')
    t.notOk(commandsSection, 'no CLI Commands section when commands directory is missing')

    // Should still have configuring-npm section
    const configuringSection = navData.find(s => s.title === 'Configuring npm')
    t.ok(configuringSection, 'has configuring-npm section')
  })

  t.test('command title fallback to name', async t => {
    // Tests line 72: (attributes.title || name).replace(/^npm-/, 'npm ')
    // When command doc has no title, should use filename as title
    const testDir = t.testdir({
      content: {
        commands: {
          // Command doc WITHOUT title in frontmatter - should use name
          'npm-test-cmd.md': `---
section: 1
description: A test command
---

Content here`,
        },
      },
      'nav.yml': '',
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')

    await generateNav(contentPath, navPath)

    const navContent = await fs.readFile(navPath, 'utf-8')
    const navData = yaml.parse(navContent)

    const commandsSection = navData.find(s => s.title === 'CLI Commands')
    t.ok(commandsSection, 'has CLI Commands section')

    const testCmdEntry = commandsSection.children.find(c => c.url === '/commands/npm-test-cmd')
    t.ok(testCmdEntry, 'has test-cmd entry')
    // Should use name ('npm-test-cmd') and replace 'npm-' with 'npm '
    t.equal(testCmdEntry.title, 'npm test-cmd', 'uses name with npm- replaced to npm space')
  })

  t.test('command description fallback to empty string', async t => {
    // Tests line 77: description: attributes.description || ''
    // When command doc has no description, should use empty string
    const testDir = t.testdir({
      content: {
        commands: {
          // Command doc WITHOUT description in frontmatter - should use ''
          'npm-no-desc.md': `---
title: npm-no-desc
section: 1
---

Content here`,
        },
      },
      'nav.yml': '',
    })

    const contentPath = join(testDir, 'content')
    const navPath = join(testDir, 'nav.yml')

    await generateNav(contentPath, navPath)

    const navContent = await fs.readFile(navPath, 'utf-8')
    const navData = yaml.parse(navContent)

    const commandsSection = navData.find(s => s.title === 'CLI Commands')
    t.ok(commandsSection, 'has CLI Commands section')

    const noDescEntry = commandsSection.children.find(c => c.url === '/commands/npm-no-desc')
    t.ok(noDescEntry, 'has no-desc entry')
    // Should use empty string since no description in frontmatter
    t.equal(noDescEntry.description, '', 'uses empty string when no description')
  })
})

t.test('replaceParams with name edge cases', async t => {
  // Test the conditions around the catch block more explicitly
  t.test('npm command (no params)', async t => {
    await testCommandDoc(t, 'npm', 'javascript package manager')
  })

  t.test('npx command (special case)', async t => {
    await testCommandDoc(t, 'npx', 'Run a command from a local or remote npm package', {
      match: [/package/],
    })
  })

  t.test('regular command with params (access)', async t => {
    // Tests line 110: name && name !== 'npm' && name !== 'npx'
    await testCommandDoc(t, 'npm-access', 'Set access level on published packages', {
      match: [/registry/],
    })
  })

  t.test('command with subcommands and aliases (trust)', async t => {
    // Tests subcommand code path including line 184 (aliases in subcommand definitions)
    // npm trust has subcommands with definitions that include aliases (repo, env)
    await testCommandDoc(t, 'npm-trust', 'Create a trusted relationship between a package and a OIDC provider', {
      match: [/--repo/, /--env/],
    })
  })
})

// Helper to create a command loader from a map of command names to command objects
const createCommandLoader = (commands) => (name) => {
  if (commands[name]) {
    return commands[name]
  }
  // Fall back to real commands
  return require(`../../lib/commands/${name}`)
}

// Test harness for custom commands to test edge cases
t.test('custom command loader tests', async t => {
  t.test('command without description', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-nodesc': {
        usage: ['<pkg>'],
        params: ['registry'],
      },
    })

    const doc = createCommandDoc('npm-testcmd-nodesc', 'Test command without description')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-nodesc.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-nodesc')
    t.ok(htmlContent.length > 0, 'generates HTML for command without description')
    t.match(htmlContent, /registry/, 'includes registry param')
  })

  t.test('command without usage', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-nousage': {
        params: ['registry'],
      },
    })

    const doc = createCommandDoc('npm-testcmd-nousage', 'Test command without usage')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-nousage.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-nousage')
    t.ok(htmlContent.length > 0, 'generates HTML for command without usage')
    t.match(htmlContent, /npm testcmd-nousage/, 'includes command name in usage')
  })

  t.test('command without params (no definitions)', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-noparams': {
        usage: ['<pkg>'],
      },
    })

    const doc = `---
title: npm-testcmd-noparams
section: 1
description: Test command without params
---

### Synopsis

<!-- AUTOGENERATED USAGE DESCRIPTIONS -->
`

    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-noparams.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-noparams')
    t.ok(htmlContent.length > 0, 'generates HTML for command without params')
    t.match(htmlContent, /npm testcmd-noparams/, 'includes command name')
  })

  t.test('command with empty definitions (has config placeholder)', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-empty-defs': {
        usage: ['<pkg>'],
        definitions: [],
      },
    })

    const doc = createCommandDoc('npm-testcmd-empty-defs', 'Test command with empty definitions')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-empty-defs.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-empty-defs')
    t.ok(htmlContent.length > 0, 'generates HTML for command with empty definitions')
    t.match(htmlContent, /npm testcmd-empty-defs/, 'includes command name')
  })

  t.test('command with params referencing non-existent global definition', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-missing-param': {
        usage: ['<pkg>'],
        params: ['non-existent-param', 'registry'],
      },
    })

    const doc = createCommandDoc('npm-testcmd-missing-param', 'Test command with missing param')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-missing-param.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-missing-param')
    t.ok(htmlContent.length > 0, 'generates HTML even with missing param definition')
    t.match(htmlContent, /registry/, 'includes valid param')
  })

  t.test('command with exclusive param already resolved', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-exclusive-resolved': {
        usage: ['<pkg>'],
        definitions: {
          'save-dev': {
            key: 'save-dev',
            default: false,
            type: Boolean,
            description: 'Save as devDependency',
            exclusive: ['save-optional'],
            describe: () => '#### `save-dev`\n\n* Default: false\n* Type: Boolean\n\nSave as devDependency',
          },
          'save-optional': {
            key: 'save-optional',
            default: false,
            type: Boolean,
            description: 'Save as optionalDependency',
            describe: () => '#### `save-optional`\n\n* Default: false\n* Type: Boolean\n\nSave as optionalDependency',
          },
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-exclusive-resolved', 'Test command with exclusive already resolved')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-exclusive-resolved.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-exclusive-resolved')
    t.ok(htmlContent.length > 0, 'generates HTML with exclusive params')
    t.match(htmlContent, /save-dev/, 'includes save-dev')
    t.match(htmlContent, /save-optional/, 'includes save-optional')
  })

  t.test('command with exclusive param that does not exist in global definitions', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-exclusive-missing': {
        usage: ['<pkg>'],
        definitions: {
          'my-flag': {
            key: 'my-flag',
            default: false,
            type: Boolean,
            description: 'A flag with non-existent exclusive',
            exclusive: ['non-existent-global-param'],
            describe: () => '#### `my-flag`\n\n* Default: false\n* Type: Boolean\n\nA flag with non-existent exclusive',
          },
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-exclusive-missing', 'Test command with missing exclusive')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-exclusive-missing.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-exclusive-missing')
    t.ok(htmlContent.length > 0, 'generates HTML even with missing exclusive param')
    t.match(htmlContent, /my-flag/, 'includes the defined flag')
  })

  t.test('command with one definition with short flag', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-short': {
        usage: ['<pkg>'],
        params: ['custom-flag'],
        definitions: {
          'custom-flag': {
            key: 'custom-flag',
            default: false,
            type: Boolean,
            short: 'c',
            description: 'A custom flag with a short version',
            describe: () => '#### `custom-flag`\n\n* Default: false\n* Type: Boolean\n\nA custom flag with a short version',
          },
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-short', 'Test command with short flag')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-short.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-short')
    t.ok(htmlContent.length > 0, 'generates HTML for command with short flag')
    t.match(htmlContent, /custom-flag/, 'includes custom flag')
    t.match(htmlContent, /-c/, 'includes short flag')
  })

  t.test('command with definition with aliases', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-alias': {
        usage: ['<pkg>'],
        definitions: {
          'aliased-flag': {
            key: 'aliased-flag',
            default: '',
            type: String,
            alias: ['af', 'alias-flag'],
            description: 'A flag with aliases',
            describe: () => '#### `aliased-flag`\n\n* Default: ""\n* Type: String\n* Alias: --af, --alias-flag\n\nA flag with aliases',
          },
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-alias', 'Test command with aliased flag')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-alias.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-alias')
    t.ok(htmlContent.length > 0, 'generates HTML for command with aliases')
    t.match(htmlContent, /aliased-flag/, 'includes aliased flag')
    t.match(htmlContent, /--af/, 'includes first alias')
    t.match(htmlContent, /--alias-flag/, 'includes second alias')
  })

  t.test('command with subcommands', async t => {
    class SubA {
      static description = 'Subcommand A description'
      static usage = ['<arg>']
      static definitions = {
        'sub-a-flag': {
          key: 'sub-a-flag',
          default: false,
          type: Boolean,
          describe: () => '#### `sub-a-flag`\n\n* Default: false\n* Type: Boolean\n\nFlag for subcommand A',
        },
      }
    }

    class SubB {
      static description = 'Subcommand B description'
      static usage = ['[options]']
      static params = ['registry']
    }

    const commandLoader = createCommandLoader({
      'testcmd-subs': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          'sub-a': SubA,
          'sub-b': SubB,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-subs', 'Test command with subcommands')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-subs.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-subs')
    t.ok(htmlContent.length > 0, 'generates HTML for command with subcommands')
    t.match(htmlContent, /npm testcmd-subs sub-a/, 'includes sub-a subcommand')
    t.match(htmlContent, /npm testcmd-subs sub-b/, 'includes sub-b subcommand')
    t.match(htmlContent, /Subcommand A description/, 'includes sub-a description')
    t.match(htmlContent, /sub-a-flag/, 'includes sub-a specific flag')
  })

  t.test('command with exclusive params', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-exclusive': {
        usage: ['<pkg>'],
        params: ['save'],
      },
    })

    const doc = createCommandDoc('npm-testcmd-exclusive', 'Test command with exclusive params')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-exclusive.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-exclusive')
    t.ok(htmlContent.length > 0, 'generates HTML for command with exclusive params')
    t.match(htmlContent, /save/, 'includes save param')
  })

  t.test('command without workspaces', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-noworkspaces': {
        usage: ['<pkg>'],
        params: ['registry'],
        workspaces: false,
      },
    })

    const doc = createCommandDoc('npm-testcmd-noworkspaces', 'Test command without workspaces')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-noworkspaces.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-noworkspaces')
    t.ok(htmlContent.length > 0, 'generates HTML for command without workspaces')
    t.match(htmlContent, /unaware of workspaces/, 'includes workspaces note')
  })

  t.test('command with workspaces enabled', async t => {
    const commandLoader = createCommandLoader({
      'testcmd-workspaces': {
        usage: ['<pkg>'],
        params: ['registry'],
        workspaces: true,
      },
    })

    const doc = createCommandDoc('npm-testcmd-workspaces', 'Test command with workspaces')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-workspaces.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-workspaces')
    t.ok(htmlContent.length > 0, 'generates HTML for command with workspaces')
    t.notMatch(htmlContent, /unaware of workspaces/, 'does NOT include workspaces note')
  })

  t.test('subcommand without description', async t => {
    class SubNoDesc {
      static usage = ['<arg>']
      static definitions = {
        flag: {
          key: 'flag',
          default: false,
          type: Boolean,
          describe: () => '#### `flag`\n\n* Default: false\n* Type: Boolean\n\nA flag',
        },
      }
    }

    const commandLoader = createCommandLoader({
      'testcmd-sub-nodesc': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          mysub: SubNoDesc,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-sub-nodesc', 'Test command with subcommand without description')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-sub-nodesc.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-sub-nodesc')
    t.ok(htmlContent.length > 0, 'generates HTML for subcommand without description')
    t.match(htmlContent, /npm testcmd-sub-nodesc mysub/, 'includes subcommand')
  })

  t.test('subcommand without usage', async t => {
    class SubNoUsage {
      static description = 'Subcommand without usage'
      static definitions = {
        flag: {
          key: 'flag',
          default: false,
          type: Boolean,
          describe: () => '#### `flag`\n\n* Default: false\n* Type: Boolean\n\nA flag',
        },
      }
    }

    const commandLoader = createCommandLoader({
      'testcmd-sub-nousage': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          mysub: SubNoUsage,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-sub-nousage', 'Test command with subcommand without usage')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-sub-nousage.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-sub-nousage')
    t.ok(htmlContent.length > 0, 'generates HTML for subcommand without usage')
    t.match(htmlContent, /Subcommand without usage/, 'includes subcommand description')
  })

  t.test('subcommand with short flag and alias', async t => {
    class SubWithShortAlias {
      static description = 'Subcommand with short and alias'
      static usage = ['<arg>']
      static definitions = {
        'complex-flag': {
          key: 'complex-flag',
          default: '',
          type: String,
          short: 'x',
          alias: ['cf', 'cflag'],
          describe: () => '#### `complex-flag`\n\n* Default: ""\n* Type: String\n\nA complex flag',
        },
      }
    }

    const commandLoader = createCommandLoader({
      'testcmd-sub-complex': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          mysub: SubWithShortAlias,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-sub-complex', 'Test command with complex subcommand')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-sub-complex.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-sub-complex')
    t.ok(htmlContent.length > 0, 'generates HTML for subcommand with short and alias')
    t.match(htmlContent, /complex-flag/, 'includes complex flag')
    t.match(htmlContent, /-x/, 'includes short flag')
    t.match(htmlContent, /--cf/, 'includes first alias')
    t.match(htmlContent, /--cflag/, 'includes second alias')
  })

  t.test('subcommand with explicit params (not derived from definitions)', async t => {
    class SubWithParams {
      static description = 'Subcommand with explicit params'
      static usage = ['<arg>']
      static params = ['registry', 'tag']
    }

    const commandLoader = createCommandLoader({
      'testcmd-sub-params': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          mysub: SubWithParams,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-sub-params', 'Test command with subcommand with explicit params')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-sub-params.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-sub-params')
    t.ok(htmlContent.length > 0, 'generates HTML for subcommand with explicit params')
    t.match(htmlContent, /registry/, 'includes registry param')
    t.match(htmlContent, /tag/, 'includes tag param')
  })

  t.test('command with mixed command-specific and global params', async t => {
    const { definitions: globalDefs } = require('@npmcli/config/lib/definitions')

    const commandLoader = createCommandLoader({
      'testcmd-mixed': {
        usage: ['<pkg>'],
        definitions: {
          'custom-only': {
            key: 'custom-only',
            default: false,
            type: Boolean,
            description: 'A command-specific flag',
            describe: () => '#### `custom-only`\n\n* Default: false\n* Type: Boolean\n\nA command-specific flag',
          },
          registry: globalDefs.registry,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-mixed', 'Test command with mixed params')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-mixed.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-mixed')
    t.ok(htmlContent.length > 0, 'generates HTML for command with mixed params')
    t.match(htmlContent, /custom-only/, 'includes command-specific flag')
    t.match(htmlContent, /registry/, 'includes global registry param')
  })

  t.test('subcommand with command-specific and global params', async t => {
    const { definitions: globalDefs } = require('@npmcli/config/lib/definitions')

    class SubMixed {
      static description = 'Subcommand with mixed params'
      static usage = ['<arg>']
      static definitions = {
        'sub-flag': {
          key: 'sub-flag',
          default: false,
          type: Boolean,
          description: 'A subcommand-specific flag',
          describe: () => '#### `sub-flag`\n\n* Default: false\n* Type: Boolean\n\nA subcommand-specific flag',
        },
        registry: globalDefs.registry,
      }
    }

    const commandLoader = createCommandLoader({
      'testcmd-sub-mixed': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          mysub: SubMixed,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-sub-mixed', 'Test command with subcommand with mixed params')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-sub-mixed.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-sub-mixed')
    t.ok(htmlContent.length > 0, 'generates HTML for subcommand with mixed params')
    t.match(htmlContent, /sub-flag/, 'includes subcommand-specific flag')
    t.match(htmlContent, /registry/, 'includes global registry param')
  })

  t.test('subcommand with global config param that has alias in subDefinitions', async t => {
    const { definitions: globalDefs } = require('@npmcli/config/lib/definitions')

    class SubWithGlobalAlias {
      static description = 'Subcommand using global param with alias override'
      static usage = ['<pkg>']
      static params = ['registry']
      static definitions = {
        registry: {
          ...globalDefs.registry,
          alias: ['reg', 'r'],
          describe: () => globalDefs.registry.describe(),
        },
      }
    }

    const commandLoader = createCommandLoader({
      'testcmd-sub-global-alias': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          mysub: SubWithGlobalAlias,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-sub-global-alias', 'Test subcommand with global aliased param')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-sub-global-alias.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-sub-global-alias')
    t.ok(htmlContent.length > 0, 'generates HTML for subcommand with global aliased param')
    t.match(htmlContent, /registry/, 'includes registry param')
    t.match(htmlContent, /--reg/, 'includes first alias')
    t.match(htmlContent, /--r/, 'includes second alias')
  })

  t.test('subcommand with flags without describe method (fallback table format)', async t => {
    class SubWithoutDescribe {
      static description = 'Subcommand with simple flags'
      static usage = ['<arg>']
      static definitions = {
        'simple-flag': {
          key: 'simple-flag',
          default: false,
          type: Boolean,
          description: 'A simple flag without describe method',
        },
        'aliased-flag': {
          key: 'aliased-flag',
          default: 'default-val',
          type: String,
          description: 'A flag with aliases and short form',
          alias: ['af', 'alias-f'],
          short: 'a',
        },
        'custom-default': {
          key: 'custom-default',
          default: 'value',
          type: String,
          defaultDescription: 'custom description of default',
          typeDescription: 'Custom Type',
          description: 'A flag with custom descriptions',
        },
        'falsy-default-desc': {
          key: 'falsy-default-desc',
          default: 'another-value',
          type: String,
          defaultDescription: '',
          description: 'A flag with empty defaultDescription',
        },
      }
    }

    const commandLoader = createCommandLoader({
      'testcmd-sub-nodescribe': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          mysub: SubWithoutDescribe,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-sub-nodescribe', 'Test command with subcommand without describe')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-sub-nodescribe.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-sub-nodescribe')
    t.ok(htmlContent.length > 0, 'generates HTML for subcommand with simple flags')
    t.match(htmlContent, /simple-flag/, 'includes simple flag')
    t.match(htmlContent, /aliased-flag/, 'includes aliased flag')
    t.match(htmlContent, /-a/, 'includes short form')
    t.match(htmlContent, /af/, 'includes alias')
    t.match(htmlContent, /custom description of default/, 'includes custom default description')
    t.match(htmlContent, /Custom Type/, 'includes custom type description')
    t.match(htmlContent, /falsy-default-desc/, 'includes flag with falsy defaultDescription')
  })

  t.test('subcommand with command-specific params and table format (no describe)', async t => {
    class SubMixedNoDescribe {
      static description = 'Subcommand with command-specific params but no describe'
      static usage = ['<arg>']
      static definitions = {
        'sub-custom': {
          key: 'sub-custom',
          default: 'default-val',
          type: String,
          description: 'A command-specific flag without describe',
        },
        registry: {
          key: 'registry',
          default: 'https://registry.npmjs.org/',
          type: String,
          description: 'The base URL of the npm registry',
        },
      }
    }

    const commandLoader = createCommandLoader({
      'testcmd-sub-mixed-nodesc': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          mysub: SubMixedNoDescribe,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-sub-mixed-nodesc', 'Test command with subcommand with mixed params no describe')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-sub-mixed-nodesc.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-sub-mixed-nodesc')
    t.ok(htmlContent.length > 0, 'generates HTML for subcommand with command-specific params no describe')
    t.match(htmlContent, /sub-custom/, 'includes command-specific flag')
    t.match(htmlContent, /registry/, 'includes global registry param')
  })

  t.test('subcommand with no params', async t => {
    class SubNoParams {
      static description = 'Subcommand with no params'
      static usage = ['<arg>']
    }

    const commandLoader = createCommandLoader({
      'testcmd-sub-noparams': {
        usage: ['<subcommand>'],
        params: null,
        subcommands: {
          mysub: SubNoParams,
        },
      },
    })

    const doc = createCommandDoc('npm-testcmd-sub-noparams', 'Test command with subcommand with no params')
    const { html } = await testBuildDocs(t, {
      commandLoader,
      content: {
        commands: { 'npm-testcmd-sub-noparams.md': doc },
      },
    })

    const htmlContent = await readHtmlDoc(html, 'npm-testcmd-sub-noparams')
    t.ok(htmlContent.length > 0, 'generates HTML for subcommand with no params')
    t.match(htmlContent, /mysub/, 'includes subcommand')
  })
})
