package cmd

import (
	"sort"
	"strconv"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	"git.secluded.site/keld/internal/restic"
)

func init() {
	registerRootFlags()
	registerGlobalFlags()
	registerSubcommands()

	if err := rootCmd.RegisterFlagCompletionFunc("preset", completePreset); err != nil {
		panic(err)
	}
}

func registerSubcommands() {
	names := make([]string, 0, len(restic.Commands))
	for name := range restic.Commands {
		if name == "global" {
			continue
		}
		names = append(names, name)
	}
	sort.Strings(names)

	for _, name := range names {
		def := restic.Commands[name]
		commandName := name

		subCmd := &cobra.Command{
			Use:                commandName + " [flags...] [args...]",
			Short:              def.Description,
			DisableFlagParsing: true,
			SilenceUsage:       true,
			SilenceErrors:      true,
			RunE: func(cmd *cobra.Command, args []string) error {
				if wantsCommandHelp(args) {
					return cmd.Help()
				}

				// When invoked with no flags or arguments and the command
				// is wrapped (has TUI screens), enter the interactive
				// session with the command pre-selected, skipping the
				// menu screen. Only do this when running interactively.
				if len(args) == 0 && isWrappedCommand(commandName) && isInteractive() {
					cmdName, preset, overrides, err := runInteractive(commandName)
					if err != nil {
						return err
					}
					if cmdName == "" {
						return nil
					}
					flagPreset = preset
					return runCommand(cmdName, cmd, nil, overrides, &preset)
				}

				return runCommand(commandName, cmd, args, nil, nil)
			},
		}

		registerResticOptions(subCmd.Flags(), def.Options)
		rootCmd.AddCommand(subCmd)
	}
}

func registerGlobalFlags() {
	global, ok := restic.Commands["global"]
	if !ok {
		panic("restic command metadata missing global command")
	}

	flags := rootCmd.PersistentFlags()
	for _, opt := range global.Options {
		switch opt.Name {
		case "help":
			continue
		case "verbose":
			registerCountOption(flags, opt)
			continue
		}

		registerResticOption(flags, opt)
	}
}

func registerResticOptions(flags *pflag.FlagSet, options []restic.Option) {
	for _, opt := range options {
		if opt.Name == "help" {
			continue
		}
		registerResticOption(flags, opt)
	}
}

func registerResticOption(flags *pflag.FlagSet, opt restic.Option) {
	shorthand := sanitizeShorthand(opt.Alias)

	switch {
	case opt.IsBool:
		flags.BoolP(opt.Name, shorthand, optionDefaultBool(opt.Default), opt.Description)
	case opt.Repeatable:
		flags.StringArrayP(opt.Name, shorthand, nil, opt.Description)
	default:
		flags.StringP(opt.Name, shorthand, opt.Default, opt.Description)
	}
}

func registerCountOption(flags *pflag.FlagSet, opt restic.Option) {
	shorthand := sanitizeShorthand(opt.Alias)
	value := flags.CountP(opt.Name, shorthand, opt.Description)

	level, err := strconv.Atoi(opt.Default)
	if err != nil || level == 0 {
		return
	}

	*value = level
	_ = flags.Set(opt.Name, strconv.Itoa(level))
}

func sanitizeShorthand(alias string) string {
	if len(alias) == 1 {
		return alias
	}
	return ""
}

func optionDefaultBool(raw string) bool {
	v, err := strconv.ParseBool(raw)
	if err != nil {
		return false
	}
	return v
}

func wantsCommandHelp(args []string) bool {
	for _, arg := range args {
		if arg == "--" {
			return false
		}
		if arg == "--help" || arg == "-h" {
			return true
		}
	}
	return false
}
