package cli

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"slices"
	"unicode"
)

type helpShownKey struct{}

func (cmd *Command) parseArgsFromStdin() ([]string, error) {
	type state int
	const (
		stateSearchForToken  state = -1
		stateSearchForString state = 0
	)

	st := stateSearchForToken
	linenum := 1
	token := ""
	args := []string{}

	breader := bufio.NewReader(cmd.Reader)

outer:
	for {
		ch, _, err := breader.ReadRune()
		if err == io.EOF {
			switch st {
			case stateSearchForToken:
				if token != "--" {
					args = append(args, token)
				}
			case stateSearchForString:
				// make sure string is not empty
				for _, t := range token {
					if !unicode.IsSpace(t) {
						args = append(args, token)
					}
				}
			}
			break outer
		}
		if err != nil {
			return nil, err
		}
		switch st {
		case stateSearchForToken:
			if unicode.IsSpace(ch) || ch == '"' {
				if ch == '\n' {
					linenum++
				}
				if token != "" {
					// end the processing here
					if token == "--" {
						break outer
					}
					args = append(args, token)
					token = ""
				}
				if ch == '"' {
					st = stateSearchForString
				}
				continue
			}
			token += string(ch)
		case stateSearchForString:
			if ch != '"' {
				token += string(ch)
			} else {
				if token != "" {
					args = append(args, token)
					token = ""
				}
				/*else {
					//TODO. Should we pass in empty strings ?
				}*/
				st = stateSearchForToken
			}
		}
	}

	tracef("parsed stdin args as %v (cmd=%[2]q)", args, cmd.Name)

	return args, nil
}

// Run is the entry point to the command graph. The positional
// arguments are parsed according to the Flag and Command
// definitions and the matching Action functions are run.
func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
	_, deferErr = cmd.run(ctx, osArgs)
	return deferErr
}

func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context, deferErr error) {
	tracef("running with arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
	cmd.setupDefaults(osArgs)

	// Validate StopOnNthArg
	if cmd.StopOnNthArg != nil && *cmd.StopOnNthArg < 0 {
		return ctx, fmt.Errorf("StopOnNthArg must be non-negative, got %d", *cmd.StopOnNthArg)
	}

	if v, ok := ctx.Value(commandContextKey).(*Command); ok {
		tracef("setting parent (cmd=%[1]q) command from context.Context value (cmd=%[2]q)", v.Name, cmd.Name)
		cmd.parent = v
	}

	if cmd.parent == nil {
		if cmd.ReadArgsFromStdin {
			if args, err := cmd.parseArgsFromStdin(); err != nil {
				return ctx, err
			} else {
				osArgs = append(osArgs, args...)
			}
		}
		// handle the completion flag separately from the flagset since
		// completion could be attempted after a flag, but before its value was put
		// on the command line. this causes the flagset to interpret the completion
		// flag name as the value of the flag before it which is undesirable
		// note that we can only do this because the shell autocomplete function
		// always appends the completion flag at the end of the command
		tracef("checking osArgs %v (cmd=%[2]q)", osArgs, cmd.Name)
		cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs)

		tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name)
		cmd.shellCompletion = cmd.EnableShellCompletion && cmd.shellCompletion
	}

	tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)

	tracef("setting self as cmd in context (cmd=%[1]q)", cmd.Name)
	ctx = context.WithValue(ctx, commandContextKey, cmd)

	if cmd.parent == nil {
		cmd.setupCommandGraph()
	}

	var rargs Args = &stringSliceArgs{v: osArgs}
	var args Args = &stringSliceArgs{rargs.Tail()}

	for _, f := range cmd.allFlags() {
		if cmd.hasPersistentFlagOnAncestor(f) {
			continue
		}
		if err := f.PreParse(); err != nil {
			return ctx, err
		}
	}

	var err error

	if cmd.SkipFlagParsing {
		tracef("skipping flag parsing (cmd=%[1]q)", cmd.Name)
		cmd.parsedArgs = args
	} else {
		cmd.parsedArgs, err = cmd.parseFlags(args)
	}

	tracef("using post-parse arguments %[1]q (cmd=%[2]q)", args, cmd.Name)

	if shouldRunCompletion(cmd) {
		var beforeErr error
		if ctx, beforeErr = runBefore(ctx, commandChain(cmd)); beforeErr != nil {
			return ctx, beforeErr
		}
		runCompletion(ctx, cmd)
		return ctx, nil
	}

	if err != nil {
		tracef("setting deferErr from %[1]q (cmd=%[2]q)", err, cmd.Name)
		deferErr = err

		cmd.isInError = true
		if cmd.checkHelp() {
			ctx = context.WithValue(ctx, helpShownKey{}, true)
			if cmd.parent == nil {
				_ = ShowRootCommandHelp(cmd)
			} else {
				_ = ShowSubcommandHelp(cmd)
			}
			return ctx, nil
		}
		if cmd.OnUsageError != nil {
			err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
			err = cmd.handleExitCoder(ctx, err)
			return ctx, err
		}
		fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
		if cmd.Suggest {
			if suggestion, err := cmd.suggestFlagFromError(err, ""); err == nil {
				fmt.Fprintf(cmd.Root().ErrWriter, "%s", suggestion)
			}
		}
		if !cmd.hideHelp() {
			if cmd.parent == nil {
				tracef("running ShowRootCommandHelp")
				if err := ShowRootCommandHelp(cmd); err != nil {
					tracef("SILENTLY IGNORING ERROR running ShowRootCommandHelp %[1]v (cmd=%[2]q)", err, cmd.Name)
				}
			} else {
				tracef("running ShowSubcommandHelp for %[1]q", cmd.Name)
				_ = ShowSubcommandHelp(cmd)
			}
		}

		return ctx, err
	}

	if cmd.checkHelp() {
		ctx = context.WithValue(ctx, helpShownKey{}, true)
		return ctx, helpCommandAction(ctx, cmd)
	} else {
		tracef("no help is wanted (cmd=%[1]q)", cmd.Name)
	}

	if cmd.parent == nil && !cmd.HideVersion && checkVersion(cmd) {
		ShowVersion(cmd)
		return ctx, nil
	}

	for _, flag := range cmd.allFlags() {
		cmd.setMultiValueParsingConfig(flag)
		isSet := flag.IsSet()
		if err := flag.PostParse(); err != nil {
			return ctx, err
		}
		// add env set flags here
		if !isSet && flag.IsSet() {
			cmd.setFlags[flag] = struct{}{}
		}
	}

	if cmd.After != nil && !cmd.Root().shellCompletion {
		defer func() {
			if ctx.Value(helpShownKey{}) != nil {
				return
			}
			if err := cmd.After(ctx, cmd); err != nil {
				err = cmd.handleExitCoder(ctx, err)

				if deferErr != nil {
					deferErr = newMultiError(deferErr, err)
				} else {
					deferErr = err
				}
			}
		}()
	}

	// Walk the parent chain to check mutually exclusive flag groups
	// defined on ancestor commands, since persistent flags are inherited.
	for pCmd := cmd; pCmd != nil; pCmd = pCmd.parent {
		for _, grp := range pCmd.MutuallyExclusiveFlags {
			if err := grp.check(cmd); err != nil {
				if cmd.OnUsageError != nil {
					err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
				} else {
					fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
					if cmd.parent == nil {
						_ = ShowRootCommandHelp(cmd)
					} else {
						if err := ShowCommandHelp(ctx, cmd.parent, cmd.Name); err != nil {
							_ = ShowSubcommandHelp(cmd)
						}
					}
				}
				return ctx, err
			}
		}
	}

	var subCmd *Command
	if cmd.parsedArgs.Present() {
		tracef("checking positional args %[1]q (cmd=%[2]q)", cmd.parsedArgs, cmd.Name)

		name := cmd.parsedArgs.First()

		tracef("using first positional argument as sub-command name=%[1]q (cmd=%[2]q)", name, cmd.Name)

		if cmd.SuggestCommandFunc != nil && name != "--" {
			name = cmd.SuggestCommandFunc(cmd.Commands, name)
			tracef("suggested command name=%1[q] (cmd=%[2]q)", name, cmd.Name)
		}
		subCmd = cmd.Command(name)
		if subCmd == nil {
			hasDefault := cmd.DefaultCommand != ""

			if hasDefault {
				tracef("using default command=%[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
			}

			if hasDefault {
				argsWithDefault := cmd.argsWithDefaultCommand(cmd.parsedArgs)
				tracef("using default command args=%[1]q (cmd=%[2]q)", argsWithDefault, cmd.Name)
				subCmd = cmd.Command(argsWithDefault.First())
				cmd.parsedArgs = argsWithDefault
			}
		}
	} else if cmd.DefaultCommand != "" {
		tracef("no positional args present; checking default command %[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)

		if dc := cmd.Command(cmd.DefaultCommand); dc != cmd {
			subCmd = dc
		}
	}

	// If a subcommand has been resolved, let it handle the remaining execution.
	if subCmd != nil {
		tracef("running sub-command %[1]q with arguments %[2]q (cmd=%[3]q)", subCmd.Name, cmd.Args(), cmd.Name)

		// It is important that we overwrite the ctx variable in the current
		// function so any defer'd functions use the new context returned
		// from the sub command.
		ctx, err = subCmd.run(ctx, cmd.Args().Slice())
		return ctx, err
	}

	// This code path is the innermost command execution. Here we actually
	// perform the command action.
	//
	// First, resolve the chain of nested commands up to the parent.
	cmdChain := commandChain(cmd)

	// Run Before actions in order.
	if ctx, err = runBefore(ctx, cmdChain); err != nil {
		deferErr = err
		return ctx, deferErr
	}

	// Run flag actions in order.
	// These take a context, so this has to happen after Before actions.
	for _, cmd := range cmdChain {
		tracef("running flag actions (cmd=%[1]q)", cmd.Name)
		if err := cmd.runFlagActions(ctx); err != nil {
			deferErr = cmd.handleExitCoder(ctx, err)
			return ctx, deferErr
		}
	}

	if err := cmd.checkAllRequiredFlags(); err != nil {
		cmd.isInError = true
		if cmd.OnUsageError != nil {
			err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
		} else {
			fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
			if cmd.parent == nil {
				_ = ShowRootCommandHelp(cmd)
			} else {
				if err := ShowCommandHelp(ctx, cmd.parent, cmd.Name); err != nil {
					_ = ShowSubcommandHelp(cmd)
				}
			}
		}
		return ctx, err
	}

	// Run the command action.
	if len(cmd.Arguments) > 0 {
		rargs := cmd.Args().Slice()
		tracef("calling argparse with %[1]v", rargs)
		for _, arg := range cmd.Arguments {
			var err error
			rargs, err = arg.Parse(rargs)
			if err != nil {
				tracef("calling with %[1]v (cmd=%[2]q)", err, cmd.Name)
				if cmd.OnUsageError != nil {
					err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
				}
				err = cmd.handleExitCoder(ctx, err)
				return ctx, err
			}
		}
		cmd.parsedArgs = &stringSliceArgs{v: rargs}
	}

	if err := cmd.Action(ctx, cmd); err != nil {
		tracef("calling handleExitCoder with %[1]v (cmd=%[2]q)", err, cmd.Name)
		deferErr = cmd.handleExitCoder(ctx, err)
	}

	tracef("returning deferErr (cmd=%[1]q) %[2]q", cmd.Name, deferErr)
	return ctx, deferErr
}

func commandChain(cmd *Command) []*Command {
	var cmdChain []*Command
	for p := cmd; p != nil; p = p.parent {
		cmdChain = append(cmdChain, p)
	}
	slices.Reverse(cmdChain)
	return cmdChain
}

func runBefore(ctx context.Context, cmdChain []*Command) (context.Context, error) {
	for _, cmd := range cmdChain {
		if cmd.Before == nil {
			continue
		}
		if bctx, err := cmd.Before(ctx, cmd); err != nil {
			return ctx, cmd.handleExitCoder(ctx, err)
		} else if bctx != nil {
			ctx = bctx
		}
	}
	return ctx, nil
}
