package main

import (
	"errors"
	"fmt"
	"go/importer"
	"go/token"
	"go/types"
	"os"
	"strings"

	"github.com/spf13/cobra"
)

func main() {
	if err := newCommand().Execute(); err != nil {
		os.Exit(1)
	}
}

const (
	packagePrefix = "k8s.io/api/"
)

func newCommand() *cobra.Command {
	var docsOutputPath string = ""
	command := &cobra.Command{
		Use:     "go run github.com/argoproj/argo-cd/hack/known_types ALIAS PACKAGE_PATH OUTPUT_PATH",
		Example: "go run github.com/argoproj/argo-cd/hack/known_types corev1 k8s.io/api/core/v1 corev1_known_types.go",
		RunE: func(c *cobra.Command, args []string) error {
			if len(args) < 3 {
				return errors.New("alias and package are not specified")
			}
			alias := args[0]
			packagePath := args[1]
			outputPath := args[2]

			if !strings.HasPrefix(packagePath, packagePrefix) {
				return fmt.Errorf("package must be under %s", packagePrefix)
			}

			imprt := importer.ForCompiler(token.NewFileSet(), "source", nil)
			pkg, err := imprt.Import(packagePath)
			if err != nil {
				return err
			}

			shortPackagePath := strings.TrimPrefix(packagePath, packagePrefix)

			var mapItems []string
			var docs []string

			names := pkg.Scope().Names()
			for i := range names {
				obj := pkg.Scope().Lookup(names[i])
				typeName, ok := obj.(*types.TypeName)
				if !ok || !typeName.Exported() {
					continue
				}
				_, isStruct := typeName.Type().Underlying().(*types.Struct)
				_, isMap := typeName.Type().Underlying().(*types.Map)
				if !isStruct && !isMap {
					continue
				}

				docs = append(docs, fmt.Sprintf("%s/%s", shortPackagePath, typeName.Name()))
				mapItems = append(mapItems, fmt.Sprintf(`
	knownTypes["%s/%s"] = func() interface{} {
		return &%s.%s{}
	}`, shortPackagePath, typeName.Name(), alias, typeName.Name()))
			}
			res := fmt.Sprintf(`// Code generated by github.com/argoproj/argo-cd/hack/known_types. DO NOT EDIT.
package normalizers

import corev1 "k8s.io/api/core/v1"

func init() {%s
}`, strings.Join(mapItems, ""))
			if docsOutputPath != "" {
				if err = os.WriteFile(docsOutputPath, []byte(strings.Join(docs, "\n")), 0o644); err != nil {
					return err
				}
			}

			return os.WriteFile(outputPath, []byte(res+"\n"), 0o644)
		},
	}
	command.Flags().StringVar(&docsOutputPath, "docs", "", "Docs output file path")
	return command
}
