package carapace

import (
	
	
	
	
	
	

	shlex 
	
	
	pkgcache 
	
	
	pkgtraverse 
)

// Action indicates how to complete a flag or positional argument.
type Action struct {
	meta      common.Meta
	rawValues common.RawValues
	callback  CompletionCallback
}

// ActionMap maps Actions to an identifier.
type ActionMap map[string]Action

// CompletionCallback is executed during completion of associated flag or positional argument.
type CompletionCallback func(c Context) Action

// Cache cashes values of a CompletionCallback for given duration and keys.
func ( Action) ( time.Duration,  ...pkgcache.Key) Action {
	if .callback != nil { // only relevant for callback actions
		 := .callback
		, , ,  := runtime.Caller(1) // generate uid from wherever Cache() was called
		.callback = func( Context) Action {
			,  := cache.File(, , ...)
			if  != nil {
				return ()
			}

			if ,  := cache.Load(, );  == nil {
				return Action{meta: .Meta, rawValues: .Values}
			}

			 := (Action{callback: }).Invoke()
			if .meta.Messages.IsEmpty() {
				if ,  := cache.File(, , ...);  == nil { // regenerate as cache keys might have changed due to invocation
					_ = cache.Write(, .export())
				}
			}
			return .ToA()
		}
	}
	return 
}

// Chdir changes the current working directory to the named directory for the duration of invocation.
func ( Action) ( string) Action {
	return ActionCallback(func( Context) Action {
		,  := .Abs()
		if  != nil {
			return ActionMessage(.Error())
		}
		if ,  := os.Stat();  != nil {
			return ActionMessage(.Error())
		} else if !.IsDir() {
			return ActionMessage("not a directory: %v", )
		}
		.Dir = 
		return .Invoke().ToA()
	})
}

// ChdirF is like Chdir but uses a function.
func ( Action) ( func( pkgtraverse.Context) (string, error)) Action {
	return ActionCallback(func( Context) Action {
		,  := ()
		if  != nil {
			return ActionMessage(.Error())
		}
		return .Chdir()
	})
}

// Filter filters given values.
//
//	carapace.ActionValues("A", "B", "C").Filter("B") // ["A", "C"]
func ( Action) ( ...string) Action {
	return ActionCallback(func( Context) Action {
		return .Invoke().Filter(...).ToA()
	})
}

// FilterArgs filters Context.Args.
func ( Action) () Action {
	return ActionCallback(func( Context) Action {
		return .Filter(.Args...)
	})
}

// FilterArgs filters Context.Parts.
func ( Action) () Action {
	return ActionCallback(func( Context) Action {
		return .Filter(.Parts...)
	})
}

// Invoke executes the callback of an action if it exists (supports nesting).
func ( Action) ( Context) InvokedAction {
	if .Args == nil {
		.Args = []string{}
	}
	if .Env == nil {
		.Env = []string{}
	}
	if .Parts == nil {
		.Parts = []string{}
	}

	if .rawValues == nil && .callback != nil {
		 := .callback().()
		.meta.Merge(.meta)
		return 
	}
	return InvokedAction{}
}

// List wraps the Action in an ActionMultiParts with given divider.
func ( Action) ( string) Action {
	return ActionMultiParts(, func( Context) Action {
		return .Invoke().ToA().NoSpace()
	})
}

// MultiParts splits values of an Action by given dividers and completes each segment separately.
func ( Action) ( ...string) Action {
	return ActionCallback(func( Context) Action {
		return .Invoke().ToMultiPartsA(...)
	})
}

// MultiPartsP is like MultiParts but with placeholders.
func ( Action) ( string,  string,  func( string,  map[string]string) Action) Action {
	return ActionCallback(func( Context) Action {
		 := .Invoke()

		return ActionMultiParts(, func( Context) Action {
			 := regexp.MustCompile()
			 := make(map[string]string)
			 := make(map[string]common.RawValue)
			 := make(map[int]bool)

		:
			for ,  := range .rawValues {
				 := strings.Split(.Value, )
			:
				for ,  := range  {
					if  > len(.Parts)-1 {
						break 
					} else {
						if  != .Parts[] {
							if !.MatchString() {
								continue  // skip this path as it doesn't match and is not a placeholder
							} else {
								[] = .Parts[] // store entered data for placeholder (overwrite if duplicate)
							}
						} else {
							[] = true // static segment matches so placeholders should be ignored for this index
						}
					}
				}

				if len() < len(.Parts)+1 {
					continue  // skip path as it is shorter than what was entered (must be after staticMatches being set)
				}

				for  := range  {
					if [] != .Parts[] {
						continue  // skip this path as it has a placeholder where a static segment was matched
					}
				}

				// store segment as path matched so far and this is currently being completed
				if len() == (len(.Parts) + 1) {
					[[len(.Parts)]] = .rawValues[]
				} else {
					[[len(.Parts)]+] = common.RawValue{}
				}
			}

			 := make([]Action, 0, len())
			for ,  := range  {
				if  := strings.TrimSuffix(, ); .MatchString() {
					 := ""
					if strings.HasSuffix(, ) {
						 = 
					}
					 = append(, ActionCallback(func( Context) Action {
						 := (, ).Invoke().Suffix()
						for  := range .rawValues {
							.rawValues[].Display += 
						}
						return .ToA()
					}))
				} else {
					 = append(, ActionStyledValuesDescribed(, .Description, .Style)) // TODO tag,..
				}
			}

			 := Batch(...).ToA()
			.meta.Merge(.meta)
			return 
		})
	})
}

// NoSpace disables space suffix for given characters (or all if none are given).
func ( Action) ( ...rune) Action {
	return ActionCallback(func( Context) Action {
		if len() == 0 {
			.meta.Nospace.Add('*')
		}
		.meta.Nospace.Add(...)
		return 
	})
}

// Prefix adds a prefix to values (only the ones inserted, not the display values).
//
//	carapace.ActionValues("melon", "drop", "fall").Prefix("water")
func ( Action) ( string) Action {
	return ActionCallback(func( Context) Action {
		switch {
		case match.HasPrefix(.Value, ):
			.Value = match.TrimPrefix(.Value, )
		case match.HasPrefix(, .Value):
			.Value = ""
		default:
			return ActionValues()
		}
		return .Invoke().Prefix().ToA()
	})
}

// Retain retains given values.
//
//	carapace.ActionValues("A", "B", "C").Retain("A", "C") // ["A", "C"]
func ( Action) ( ...string) Action {
	return ActionCallback(func( Context) Action {
		return .Invoke().Retain(...).ToA()
	})
}

// Shift shifts positional arguments left `n` times.
func ( Action) ( int) Action {
	return ActionCallback(func( Context) Action {
		switch {
		case  < 0:
			return ActionMessage("invalid argument [ActionShift]: %v", )
		case len(.Args) < :
			.Args = []string{}
		default:
			.Args = .Args[:]
		}
		return .Invoke().ToA()
	})
}

// Split splits `Context.Value` lexicographically and replaces `Context.Args` with the tokens.
func ( Action) () Action {
	return .split(false)
}

// SplitP is like Split but supports pipelines.
func ( Action) () Action {
	return .split(true)
}

func ( Action) ( bool) Action {
	return ActionCallback(func( Context) Action {
		,  := shlex.Split(.Value)
		if  != nil {
			return ActionMessage(.Error())
		}

		var  Context
		if  {
			 = .CurrentPipeline()
			 = NewContext(.FilterRedirects().Words().Strings()...)
		} else {
			 = NewContext(.Words().Strings()...)
		}

		 := .Value
		 := [:.Words().CurrentToken().Index]
		.Args = .Args
		.Parts = []string{}
		.Value = .Value

		if  { // support redirects
			if len() > 1 && [len()-2].WordbreakType.IsRedirect() {
				LOG.Printf("completing files for redirect arg %#v", .Words().CurrentToken().Value)
				 = [:.CurrentToken().Index]
				.Value = .CurrentToken().Value
				 = ActionFiles()
			}
		}

		 := .Invoke()
		for ,  := range .rawValues {
			if !.meta.Nospace.Matches(.Value) || strings.Contains(.Value, " ") { // TODO special characters
				switch .CurrentToken().State {
				case shlex.QUOTING_ESCAPING_STATE:
					.rawValues[].Value = fmt.Sprintf(`"%v"`, strings.ReplaceAll(.Value, `"`, `\"`))
				case shlex.QUOTING_STATE:
					.rawValues[].Value = fmt.Sprintf(`'%v'`, strings.ReplaceAll(.Value, `'`, `'"'"'`))
				default:
					.rawValues[].Value = strings.Replace(.Value, ` `, `\ `, -1)
				}
			}
			if !.meta.Nospace.Matches(.Value) {
				.rawValues[].Value += " "
			}
		}
		return .Prefix().ToA().NoSpace()
	})
}

// Style sets the style.
//
//	ActionValues("yes").Style(style.Green)
//	ActionValues("no").Style(style.Red)
func ( Action) ( string) Action {
	return .StyleF(func( string,  style.Context) string {
		return 
	})
}

// Style sets the style using a function.
//
//	ActionValues("dir/", "test.txt").StyleF(style.ForPathExt)
//	ActionValues("true", "false").StyleF(style.ForKeyword)
func ( Action) ( func( string,  style.Context) string) Action {
	return ActionCallback(func( Context) Action {
		 := .Invoke()
		for ,  := range .rawValues {
			.rawValues[].Style = (.Value, )
		}
		return .ToA()
	})
}

// Style sets the style using a reference.
//
//	ActionValues("value").StyleR(&style.Carapace.Value)
//	ActionValues("description").StyleR(&style.Carapace.Value)
func ( Action) ( *string) Action {
	return ActionCallback(func( Context) Action {
		if  != nil {
			return .Style(*)
		}
		return 
	})
}

// Suffix adds a suffx to values (only the ones inserted, not the display values).
//
//	carapace.ActionValues("apple", "melon", "orange").Suffix("juice")
func ( Action) ( string) Action {
	return ActionCallback(func( Context) Action {
		return .Invoke().Suffix().ToA()
	})
}

// Suppress suppresses specific error messages using regular expressions.
func ( Action) ( ...string) Action {
	return ActionCallback(func( Context) Action {
		 := .Invoke()
		if  := .meta.Messages.Suppress(...);  != nil {
			return ActionMessage(.Error())
		}
		return .ToA()
	})
}

// Tag sets the tag.
//
//	ActionValues("192.168.1.1", "127.0.0.1").Tag("interfaces").
func ( Action) ( string) Action {
	return .TagF(func( string) string {
		return 
	})
}

// Tag sets the tag using a function.
//
//	ActionValues("192.168.1.1", "127.0.0.1").TagF(func(value string) string {
//		return "interfaces"
//	})
func ( Action) ( func( string) string) Action {
	return ActionCallback(func( Context) Action {
		 := .Invoke()
		for ,  := range .rawValues {
			.rawValues[].Tag = (.Value)
		}
		return .ToA()
	})
}

// Timeout sets the maximum duration an Action may take to invoke.
//
//	carapace.ActionCallback(func(c carapace.Context) carapace.Action {
//		time.Sleep(2*time.Second)
//		return carapace.ActionValues("done")
//	}).Timeout(1*time.Second, carapace.ActionMessage("timeout exceeded"))
func ( Action) ( time.Duration,  Action) Action {
	return ActionCallback(func( Context) Action {
		 := make(chan string, 1)

		var  InvokedAction
		go func() {
			 = .Invoke()
			 <- ""
		}()

		select {
		case <-:
		case <-time.After():
			return 
		}
		return .ToA()
	})
}

// UniqueList wraps the Action in an ActionMultiParts with given divider.
func ( Action) ( string) Action {
	return ActionMultiParts(, func( Context) Action {
		return .FilterParts().NoSpace()
	})
}

// UniqueListF is like UniqueList but uses a function to transform values before filtering.
func ( Action) ( string,  func( string) string) Action {
	return ActionMultiParts(, func( Context) Action {
		for  := range .Parts {
			.Parts[] = (.Parts[])
		}
		return .Filter(.Parts...).NoSpace()
	})
}

// Usage sets the usage.
func ( Action) ( string,  ...interface{}) Action {
	return .UsageF(func() string {
		return fmt.Sprintf(, ...)
	})
}

// Usage sets the usage using a function.
func ( Action) ( func() string) Action {
	return ActionCallback(func( Context) Action {
		if  := ();  != "" {
			.meta.Usage = 
		}
		return 
	})
}