package main

import (
	"bytes"
	"io"
	"os"
	"path"
	"strings"

	"github.com/spf13/cobra"
	"github.com/spf13/cobra/doc"
)

func newGenCmd() *cobra.Command {
	var (
		includeExperimental bool
		frontmatter         string
		output              string
	)

	var cmd = &cobra.Command{
		Use:    "gen [options] <file>",
		Short:  "Generate CLI reference documentation",
		Long:   "Generate CLI reference documentation in the given file path.",
		Args:   cobra.NoArgs,
		Hidden: true,
		RunE: func(cmd *cobra.Command, args []string) error {
			// Disable "Auto generated by spf13/cobra..." footer
			rootCmd.DisableAutoGenTag = true

			if !includeExperimental {
				hideAllCommands(rootCmd, isExperimental)
			}

			// TODO: the completion Long fields  are causing issues with docusaurus
			// because of examples with `$(...)`:
			//   Unexpected character `(` (U+0028) before name, expected a
			//   character that can start a name, such as a letter, `$`, or `_`"
			// Need to wrap those examples in a code block.
			hideAllCommands(rootCmd, func(cmd *cobra.Command) bool {
				return cmd.CommandPath() == "dagger completion"
			})

			buf := new(bytes.Buffer)

			if _, err := io.WriteString(buf, frontmatter); err != nil {
				return err
			}

			if err := docGenMarkdown(rootCmd, buf); err != nil {
				return err
			}

			if output == "" {
				buf.WriteTo(cmd.OutOrStdout())
			} else {
				if err := os.WriteFile(output, buf.Bytes(), 0o600); err != nil {
					return err
				}
				cmd.Printf("Generated command-line documentation: %s\n", output)
			}
			return nil
		},
	}

	cmd.Flags().StringVarP(&output, "output", "o", "", "Save to file")
	cmd.Flags().StringVar(&frontmatter, "frontmatter", "", "Prepend content to beginning of generated file")
	cmd.Flags().BoolVar(&includeExperimental, "include-experimental", false, "Show experimental commands in the generated documentation")

	return cmd
}

// hideAllCommands hides all commands that match the condition
func hideAllCommands(cmd *cobra.Command, condition func(*cobra.Command) bool) {
	if condition(cmd) {
		cmd.Hidden = true
		// no need to visit children because this is enough for the doc generation
		return
	}
	for _, c := range cmd.Commands() {
		hideAllCommands(c, condition)
	}
}

// docGenLinkHandler links to other commands in the same document with a fragment
func docGenLinkHandler(name string) string {
	base := strings.TrimSuffix(name, path.Ext(name))
	return "#" + strings.ReplaceAll(base, "_", "-")
}

// docGenMarkdown generates reference markdown documentation for the given command
func docGenMarkdown(cmd *cobra.Command, w io.Writer) error {
	if err := doc.GenMarkdownCustom(cmd, w, docGenLinkHandler); err != nil {
		return err
	}

	for _, c := range cmd.Commands() {
		if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
			continue
		}
		if err := docGenMarkdown(c, w); err != nil {
			return err
		}
	}

	return nil
}
