import type { CommandConstructor } from './command.js'
import { describe, expect, it } from 'vitest'
import { Args, Flags } from './flags.js'
import { formatHelp } from './help.js'

function makeCmd(opts: {
  description?: string
  flags?: CommandConstructor['flags']
  args?: CommandConstructor['args']
  examples?: string[]
  agentGuide?: string
}): CommandConstructor {
  class Cmd {
    static description = opts.description
    static flags = opts.flags ?? {}
    static args = opts.args ?? {}
    static examples = opts.examples ?? []
    static agentGuide = opts.agentGuide

    async run(_argv: string[]) {}

    agentGuide(): string {
      return opts.agentGuide ?? ''
    }
  }
  return Cmd as unknown as CommandConstructor
}

describe('formatHelp', () => {
  it('includes description when present', () => {
    const ctor = makeCmd({ description: 'Lists all apps' })
    expect(formatHelp(ctor, 'get app')).toContain('Lists all apps')
  })

  it('omits description section when absent', () => {
    const ctor = makeCmd({})
    const out = formatHelp(ctor, 'get app')
    expect(out).not.toContain('undefined')
  })

  it('includes USAGE line with path and bin', () => {
    const ctor = makeCmd({})
    expect(formatHelp(ctor, 'get app')).toContain('$ difyctl get app')
  })

  it('includes [FLAGS] suffix when flags exist', () => {
    const ctor = makeCmd({ flags: { output: Flags.string({ description: 'format' }) } })
    expect(formatHelp(ctor, 'get app')).toContain('[FLAGS]')
  })

  it('omits [FLAGS] when no flags', () => {
    const ctor = makeCmd({})
    expect(formatHelp(ctor, 'get app')).not.toContain('[FLAGS]')
  })

  it('includes [ARGS] suffix when args exist', () => {
    const ctor = makeCmd({ args: { id: Args.string({ description: 'app id', required: true }) } })
    expect(formatHelp(ctor, 'get app')).toContain('[ARGS]')
  })

  it('omits [ARGS] when no args', () => {
    const ctor = makeCmd({})
    expect(formatHelp(ctor, 'get app')).not.toContain('[ARGS]')
  })

  it('includes FLAGS section with flag description', () => {
    const ctor = makeCmd({
      flags: { output: Flags.string({ description: 'output format', char: 'o' }) },
    })
    const out = formatHelp(ctor, 'get app')
    expect(out).toContain('FLAGS')
    expect(out).toContain('--output')
    expect(out).toContain('-o')
    expect(out).toContain('output format')
  })

  it('includes default value in flag description', () => {
    const ctor = makeCmd({
      flags: { format: Flags.string({ description: 'fmt', default: 'text' }) },
    })
    expect(formatHelp(ctor, 'get app')).toContain('[default: "text"]')
  })

  it('renders boolean flag without <type> placeholder', () => {
    const ctor = makeCmd({
      flags: { verbose: Flags.boolean({ description: 'verbose' }) },
    })
    const out = formatHelp(ctor, 'get app')
    expect(out).toContain('--verbose')
    expect(out).not.toContain('<boolean>')
  })

  it('includes ARGUMENTS section with arg description and required marker', () => {
    const ctor = makeCmd({
      args: { id: Args.string({ description: 'app id', required: true }) },
    })
    const out = formatHelp(ctor, 'get app')
    expect(out).toContain('ARGUMENTS')
    expect(out).toContain('id')
    expect(out).toContain('(required)')
    expect(out).toContain('app id')
  })

  it('omits (required) for optional args', () => {
    const ctor = makeCmd({
      args: { id: Args.string({ description: 'app id' }) },
    })
    expect(formatHelp(ctor, 'get app')).not.toContain('(required)')
  })

  it('includes EXAMPLES section', () => {
    const ctor = makeCmd({ examples: ['difyctl get app my-id'] })
    const out = formatHelp(ctor, 'get app')
    expect(out).toContain('EXAMPLES')
    expect(out).toContain('difyctl get app my-id')
  })

  it('replaces <%= config.bin %> with difyctl in examples', () => {
    const ctor = makeCmd({ examples: ['<%= config.bin %> run app my-id'] })
    const out = formatHelp(ctor, 'run app')
    expect(out).toContain('$ difyctl run app my-id')
    expect(out).not.toContain('<%= config.bin %>')
  })

  it('appends agentGuide string at the end', () => {
    const ctor = makeCmd({ agentGuide: 'WORKFLOW\n  1. do thing\n' })
    const out = formatHelp(ctor, 'run app')
    expect(out).toContain('WORKFLOW')
    expect(out).toContain('1. do thing')
  })

  it('omits agentGuide when absent', () => {
    const ctor = makeCmd({})
    expect(formatHelp(ctor, 'run app')).not.toContain('WORKFLOW')
  })
})
