package cli

import (
	"context"
	"errors"
	"io"
	"os"

	"github.com/spf13/pflag"

	"tsne.dev/hopper/internal/log"
)

// Run executes the CLI and returns the process exit code.
//
// Note: Help paths will call exit directly.
func Run(ctx context.Context, args []string) int {
	main := newMainCommand()
	err := runCommand(ctx, main, args)
	if err != nil {
		var exit exitError
		if errors.As(err, &exit) {
			return exit.code
		}

		var ev errorVerboser
		if errors.As(err, &ev) {
			log.Error("%s\n  %s", err.Error(), ev.ErrorVerbose())
		} else {
			log.Error(err.Error())
		}
		return 1
	}
	return 0
}

type command interface {
	Usage() string
	Run(ctx context.Context, args []string) error
}

func parseFlags(cmd command, flags *pflag.FlagSet, args []string) {
	err := flags.Parse(args)
	if err != nil {
		if errors.Is(err, pflag.ErrHelp) {
			log.Print(cmd.Usage())
			os.Exit(0)
		} else {
			log.Error("%s\n\n%s", err.Error(), cmd.Usage())
			os.Exit(2)
		}
	}
}

func runCommand(ctx context.Context, cmd command, args []string) error {
	err := cmd.Run(ctx, args)
	if err != nil {
		var usageErr usageError
		if errors.As(err, &usageErr) {
			log.Error("%s\n\n%s", usageErr.msg, cmd.Usage())
			os.Exit(2)
		}
		return err
	}
	return nil
}

func newFlagset(name string) *pflag.FlagSet {
	fs := pflag.NewFlagSet(name, pflag.ContinueOnError)
	fs.SetOutput(io.Discard)
	fs.Usage = func() {}
	return fs
}
