package options

import (
	"fmt"
	"iter"
	"slices"
	"strings"

	"github.com/buildbuddy-io/buildbuddy/cli/log"
	"github.com/buildbuddy-io/buildbuddy/cli/parser/arguments"
	"github.com/buildbuddy-io/buildbuddy/cli/parser/bazelrc"
	"github.com/buildbuddy-io/buildbuddy/cli/parser/options/flag_form"
	"github.com/buildbuddy-io/buildbuddy/server/util/lib/seq"
	"github.com/buildbuddy-io/buildbuddy/server/util/lib/set"

	bfpb "github.com/buildbuddy-io/buildbuddy/proto/bazel_flags"
)

const (
	NativeBuiltinPluginID   = "//builtin/native"
	StarlarkBuiltinPluginID = "//builtin/starlark"
	UnknownBuiltinPluginID  = "//builtin/unknown"
)

// These are the starlark flag prefixes
var StarlarkSkippedPrefixes = map[string]struct{}{
	"//":   {},
	"no//": {},
	"@":    {},
	"no@":  {},
}

// Before Bazel 7, the flag protos did not contain the `RequiresValue` field,
// so there is no way to identify expansion options, which must be parsed
// differently. Since there are only nineteen such options (and bazel 6 is
// currently only receiving maintenance support and thus unlikely to add new
// expansion options), we can just enumerate them here so that we can correctly
// identify them in the absence of that field.
var preBazel7ExpansionOptions = map[string]struct{}{
	"noincompatible_genquery_use_graphless_query":     {},
	"incompatible_genquery_use_graphless_query":       {},
	"persistent_android_resource_processor":           {},
	"persistent_multiplex_android_resource_processor": {},
	"persistent_android_dex_desugar":                  {},
	"persistent_multiplex_android_dex_desugar":        {},
	"persistent_multiplex_android_tools":              {},
	"start_app":                                       {},
	"debug_app":                                       {},
	"java_debug":                                      {},
	"remote_download_minimal":                         {},
	"remote_download_toplevel":                        {},
	"host_jvm_debug":                                  {},
	"long":                                            {},
	"short":                                           {},
	"expunge_async":                                   {},
	"experimental_spawn_scheduler":                    {},
	"experimental_persistent_javac":                   {},
	"null":                                            {},
	"order_results":                                   {},
	"noorder_results":                                 {},
}

type Defined interface {
	Name() string
	ShortName() string
	Multi() bool
	HasNegative() bool
	RequiresValue() bool
	HasSupportedCommands() bool
	Supports(string) bool
	PluginID() string
}

// Definition defines a single Bazel option for the parser.
//
// TODO: Allow plugins to define their own option definitions.
type Definition struct {
	// name is the long-form name of this flag. Example: "compilation_mode"
	name string

	// oldName is the old name for this flag, if one exists.
	oldName string

	// shortName is the short-form name of this flag, if one exists. Bazel only
	// allows single letters for these, like "c". Note that the "=" assignment
	// syntax cannot be used with short names. For example,
	// "--compilation_mode=opt" would be specified as "-c opt", not "-c=opt".
	shortName string

	// multi specifies whether the flag can be passed more than once.
	// Each occurrence of the flag value is accumulated in a list.
	multi bool

	// hasNegative specifies whether the flag allows a "no" prefix" to be used in
	// order to set the value to false.
	hasNegative bool

	// Flags that do not require a value must be parsed differently. Their name
	// and value, if any, must appear as a single token, which means the "="
	// syntax has to be used when assigning a value. For example, "bazel build
	// --subcommands false" is actually equivalent to "bazel build
	// --subcommands=true //false:false".
	requiresValue bool

	// The list of commands that support this option.
	supportedCommands set.Set[string]

	// pluginID is the ID of the bb cli plugin associated with this option
	// definition, if applicable (or a pseudo-plugin ID for so-called "built-in"
	// plugins).
	pluginID string
}

func (d *Definition) Name() string {
	return d.name
}

func (d *Definition) OldName() string {
	return d.oldName
}

func (d *Definition) ShortName() string {
	return d.shortName
}

func (d *Definition) Multi() bool {
	return d.multi
}

func (d *Definition) HasNegative() bool {
	return d.hasNegative
}

func (d *Definition) RequiresValue() bool {
	return d.requiresValue
}

func (d *Definition) HasSupportedCommands() bool {
	return len(d.supportedCommands) != 0
}

func (d *Definition) SupportedCommands() iter.Seq[string] {
	return func(yield func(string) bool) {
		for k := range d.supportedCommands {
			if !yield(k) {
				return
			}
		}
	}
}

func (d *Definition) Supports(command string) bool {
	for cmd, ok := command, true; ok; cmd, ok = bazelrc.Parent(cmd) {
		if d.supportedCommands.Contains(cmd) {
			return true
		}
	}

	return false
}

func (d *Definition) AddSupportedCommand(commands ...string) {
	if d.supportedCommands == nil {
		d.supportedCommands = make(set.Set[string], 1)
	}
	d.supportedCommands.AddSeq(slices.Values(commands))
}

func (d *Definition) PluginID() string {
	return d.pluginID
}

type DefinitionOpt func(d *Definition)

func WithOldName(oldName string) DefinitionOpt {
	return func(d *Definition) { d.oldName = oldName }
}
func WithShortName(shortName string) DefinitionOpt {
	return func(d *Definition) { d.shortName = shortName }
}
func WithMulti() DefinitionOpt         { return func(d *Definition) { d.multi = true } }
func WithNegative() DefinitionOpt      { return func(d *Definition) { d.hasNegative = true } }
func WithRequiresValue() DefinitionOpt { return func(d *Definition) { d.requiresValue = true } }
func WithPluginID(pluginID string) DefinitionOpt {
	return func(d *Definition) { d.pluginID = pluginID }
}

func WithSupportFor(commands ...string) DefinitionOpt {
	return func(d *Definition) {
		d.AddSupportedCommand(commands...)
	}
}

func NewDefinition(name string, opts ...DefinitionOpt) *Definition {
	d := &Definition{name: name}
	for _, opt := range opts {
		opt(d)
	}
	return d
}

// DefinitionFrom takes a FlagInfo proto message and converts it into a
// Definition.
func DefinitionFrom(info *bfpb.FlagInfo) *Definition {
	switch info.GetName() {
	case "bazelrc":
		// `bazel help flags-as-proto` incorrectly reports `bazelrc` as not
		// allowing multiple values.
		// See https://github.com/bazelbuild/bazel/issues/24730 for more info.
		v := true
		info.AllowsMultiple = &v
	case "block_for_lock":
		// `bazel help flags-as-proto` incorrectly reports `block_for_lock` as
		// supporting non-startup commands, but in actuality it only has an effect
		// as a startup option.
		// See https://github.com/bazelbuild/bazel/pull/24953 for more info.
		info.Commands = []string{"startup"}
	case "watchfs":
		// `bazel help flags-as-proto` can report `watchfs` as being supported
		// as a startup option, despite it being deprecated as a startup option
		// and moved to only be supported as a command option.
		//
		// If it is supported as a command option, we remove "startup" from its
		// list of supported commands. In newer versions of bazel (v8.0.0+), this
		// is already true and thus this step is unnecessary.
		if len(info.GetCommands()) > 1 {
			commands := []string{}
			for _, c := range info.GetCommands() {
				if c != "startup" {
					commands = append(commands, c)
				}
			}
			info.Commands = commands
		}
	case "experimental_convenience_symlinks":
		fallthrough
	case "subcommands":
		// `bazel help flags-as-proto` incorrectly reports `subcommands` as not
		// having a negative form.
		// See https://github.com/bazelbuild/bazel/issues/24882 for more info.
		v := true
		info.HasNegativeFlag = &v
	}
	if info.RequiresValue == nil {
		// If flags-as-proto does not support RequiresValue, mark flags with
		// negative forms and known expansion flags as not requiring values, and
		// mark all other flags as requiring values.
		if info.GetHasNegativeFlag() {
			v := false
			info.RequiresValue = &v
		} else if _, ok := preBazel7ExpansionOptions[info.GetName()]; ok {
			v := false
			info.RequiresValue = &v
		} else {
			v := true
			info.RequiresValue = &v
		}
	}
	d := &Definition{
		name:              info.GetName(),
		shortName:         info.GetAbbreviation(),
		oldName:           info.GetOldName(),
		multi:             info.GetAllowsMultiple(),
		hasNegative:       info.GetHasNegativeFlag(),
		requiresValue:     info.GetRequiresValue(),
		supportedCommands: make(set.Set[string], len(info.GetCommands())),
	}
	d.AddSupportedCommand(info.GetCommands()...)
	return d
}

// Option represents a single parsed command-line option, including any value
// that option may have, regardless of if said value was provided with an `=`,
// as a separate argument, or, in the case of boolean arguments, implicitly via
// the `--[no]option` syntax. The benefit this interface affords us is largely
// that we can implement types for specific kinds of options and thus handle
// the behavior of those options more cleanly and with greater readability by
// reducing the need for large blocks of branching conditionals.
type Option interface {
	arguments.Argument
	Defined
	HasValue() bool
	ExpectsValue() bool
	ClearValue()
	SetValue(string)
	GetDefinition() *Definition
	SetDefinition(*Definition)
	UseName()
	UseShortName()
	UseOldName()
	UsesName() bool
	UsesShortName() bool
	UsesOldName() bool
	Normalized() Option

	// Because Options wrap other Options sometimes, we cannot depend on being
	// able to use type assertion to get a BoolLike from an Option that should
	// support it. This problem could theoretically be alleviated by embedding
	// types defined in type parameters, but that is explicitly disallowed by go.
	BoolLike() BoolLike
}

// The base type for all option types. Handles a lot of formatting and option
// representation transformations.
type optionBase struct {
	*Definition

	// The name this flag will use when formatted.
	Form flag_form.Form
}

// Returns a new option base derived from the option string (without the leading
// dashes) and the provided Definition.
func NewOptionBase(opt string, d *Definition) (*optionBase, error) {
	// validate optName
	var form flag_form.Form
	switch opt {
	case d.name:
		form = flag_form.Name
	case d.oldName:
		form = flag_form.OldName
	case d.shortName:
		form = flag_form.ShortName
	default:
		if d.hasNegative {
			if n, cut := strings.CutPrefix(opt, "no"); cut {
				if n == d.name {
					form = flag_form.NegativeName
					break
				}
				if n == d.oldName {
					form = flag_form.NegativeOldName
					break
				}
			}
		}
		return nil, fmt.Errorf("option name '%s' cannot specify an option with definition '%#v'", opt, d)
	}
	return &optionBase{
		Definition: d,
		Form:       form,
	}, nil
}

// Coerce this option base to use the standard name for this option.
func (o *optionBase) UseName() {
	if o.Name() == "" {
		log.Warnf("Attempted to use name for option %s, which lacks a name.", o.Name())
		return
	}
	o.Form = o.Form.AsNameType(flag_form.Name)
}

// Coerce this option base to use the old name for this option.
func (o *optionBase) UseOldName() {
	if o.OldName() == "" {
		log.Warnf("Attempted to use old name for option %s, which lacks an old name.", o.Name())
		return
	}
	o.Form = o.Form.AsNameType(flag_form.OldName)
}

// Coerce this option base to use the short name for this option. Does not
// support coercing from a negative (`--no`) type flag.
func (o *optionBase) UseShortName() {
	if o.ShortName() == "" {
		log.Warnf("Attempted to use short name for option %s, which lacks a short name.", o.Name())
		return
	}
	if o.Form.Negative() {
		log.Warnf("Cannot coerce short form from negative form for option %s.", o.Name())
		return
	}
	o.Form = flag_form.ShortName
}

// Returns whether or not this option will be represented using the standard
// name for the option.
func (o *optionBase) UsesName() bool {
	return o.Form.CompareNameType(flag_form.Name)
}

// Returns whether or not this option will be represented using the old
// name for the option.
func (o *optionBase) UsesOldName() bool {
	return o.OldName() != "" && o.Form.CompareNameType(flag_form.OldName)
}

// Returns whether or not this option will be represented using the short name
// for the option.
func (o *optionBase) UsesShortName() bool {
	return o.ShortName() != "" && o.Form == flag_form.ShortName
}

// Returns a formatted version of the option.
func (o *optionBase) Format() string {
	switch o.Form {
	case flag_form.Name:
		return "--" + o.Name()
	case flag_form.NegativeName:
		return "--" + "no" + o.Name()
	case flag_form.OldName:
		return "--" + o.OldName()
	case flag_form.NegativeOldName:
		return "--" + "no" + o.OldName()
	case flag_form.ShortName:
		return "-" + o.ShortName()
	default:
		// Just default to the standard representaation if the form is unknown.
		return "--" + o.Name()
	}
}

// Coerces this option base to be a negative (`--no`) form option. If this is
// called on option base which uses a short name representation,  it will
// default to using the standard name representation instead, so long as one
// exists.
//
// Has no effect if called on an option base with a definition that does not
// have a negative representation.
//
// Has no effect if called on option base with an unknown representation.
func (o *optionBase) SetNegative() {
	if !o.HasNegative() {
		log.Warnf("Can't negate flag '%s', which does not have a negative form.", o.Name())
		return
	}
	if o.Form.Unknown() {
		log.Warnf("Can't negate flag '%s', which does not have a known form.", o.Name())
		return
	}
	if o.Form == flag_form.ShortName {
		if o.Name() != "" {
			log.Warnf("Can't negate short name of flag '%s'; defaulting to using negative form of the standard name.", o.Name)
			o.Form = flag_form.NegativeName
			return
		}
		log.Warnf("Can't negate short name of flag '%s' and there is no known standard name to default to; flag could not be negated.", o.Name)
	}
	o.Form = o.Form.SetNegative()
}

// Coerces this option base to not be a negative (`--no`) form option.
//
// Has no effect if called on option base with an unknown representation.
func (o *optionBase) ClearNegative() error {
	if o.Form.Unknown() {
		return fmt.Errorf("Can't clear negation of flag '%s', which does not have a known form.", o.Name())
	}
	o.Form = o.Form.ClearNegative()
	return nil
}

// Returns whether or not this option base is a negative (`--no`) form option.
func (o *optionBase) Negative() bool {
	return o.Form.Negative()
}

// RequiredValueOption is used to represent an option whose definition specifies
// `RequiresValue` as `true`.
type RequiredValueOption struct {
	*optionBase

	// The string Value of this option
	Value *string

	// If this is true, the option will be formatted Joined to its value by `=`.
	// Otherwise, it will be formatted with its value as two separate tokens.
	// If `UsesShortName` is true and a valid short name exists as described above,
	// this value will be ignored.
	Joined bool
}

func (o *RequiredValueOption) GetDefinition() *Definition {
	return o.Definition
}

func (o *RequiredValueOption) SetDefinition(d *Definition) {
	o.Definition = d
}

func (o *RequiredValueOption) HasValue() bool {
	return o.Value != nil
}

func (o *RequiredValueOption) ExpectsValue() bool {
	return o.Value == nil
}

func (o *RequiredValueOption) GetValue() string {
	if o.Value == nil {
		return ""
	}
	return *o.Value
}

func (o *RequiredValueOption) ClearValue() {
	o.Value = nil
}

func (o *RequiredValueOption) SetValue(value string) {
	o.Value = &value
}

func (o *RequiredValueOption) Format() []string {
	switch {
	case o.UsesShortName():
		return []string{o.optionBase.Format(), o.GetValue()}
	case o.Joined:
		return []string{o.optionBase.Format() + "=" + o.GetValue()}
	default:
		return []string{o.optionBase.Format(), o.GetValue()}
	}
}

func (o *RequiredValueOption) Normalized() Option {
	normalizedOptionBase := *o.optionBase
	normalizedOptionBase.UseName()
	return &RequiredValueOption{
		optionBase: &normalizedOptionBase,
		Value:      o.Value,
		Joined:     true,
	}
}

func (o *RequiredValueOption) BoolLike() BoolLike {
	return nil
}

type BoolLike interface {
	AsBool() (bool, error)
	Cleared() bool
	Negated() bool
	Negate()
}

// BoolOrEnumOption is used to represent an option whose definition specifies
// `HasNegative` as `true`.
type BoolOrEnumOption struct {
	*optionBase

	// The string Value of this option, if any
	Value *string
}

func (o *BoolOrEnumOption) GetDefinition() *Definition {
	return o.Definition
}

func (o *BoolOrEnumOption) SetDefinition(d *Definition) {
	o.Definition = d
}

func (o *BoolOrEnumOption) HasValue() bool {
	return o.Value != nil
}

func (o *BoolOrEnumOption) ExpectsValue() bool {
	return false
}

func (o *BoolOrEnumOption) GetValue() string {
	if o.Value != nil {
		return *o.Value
	}
	if o.optionBase.Negative() {
		return "0"
	}
	return "1"
}

func (o *BoolOrEnumOption) ClearValue() {
	o.Value = nil
	o.optionBase.ClearNegative()
}

func (o *BoolOrEnumOption) SetValue(value string) {
	o.Value = &value
	o.optionBase.ClearNegative()
}

func (o *BoolOrEnumOption) Cleared() bool {
	return o.Value == nil && !o.optionBase.Negative()
}

func (o *BoolOrEnumOption) Negated() bool {
	return o.Value == nil && o.optionBase.Negative()
}

func (o *BoolOrEnumOption) Negate() {
	o.Value = nil
	o.optionBase.SetNegative()
}

func (o *BoolOrEnumOption) Format() []string {
	if o.Value != nil {
		return []string{o.optionBase.Format() + "=" + *o.Value}
	}
	return []string{o.optionBase.Format()}
}

func (o *BoolOrEnumOption) AsBool() (bool, error) {
	switch o.GetValue() {
	case "yes", "true", "1", "":
		return true, nil
	case "no", "false", "0":
		return false, nil
	}
	return false, fmt.Errorf("Error converting to bool: flag '--%s' has non-boolean value '%s'.", o.Name(), o.GetValue())
}

func (o *BoolOrEnumOption) Normalized() Option {
	normalizedOptionBase := *o.optionBase
	normalizedOptionBase.UseName()
	if v, err := o.AsBool(); err == nil {
		if !v {
			// This accounts for the case where the value is what makes the option
			// evaluate to false.
			normalizedOptionBase.SetNegative()
		}
		return &BoolOrEnumOption{
			optionBase: &normalizedOptionBase,
			Value:      nil,
		}
	}
	return &BoolOrEnumOption{
		optionBase: &normalizedOptionBase,
		Value:      o.Value,
	}
}

func (o *BoolOrEnumOption) BoolLike() BoolLike {
	return o
}

// starlarkOption is used to represent an option that has been identified as
// having a starlark option prefix.
type starlarkOption struct {
	BoolOrEnumOption
}

func (o *starlarkOption) Normalized() Option {
	// don't normalize starlark flags
	normalizedOptionBase := *o.optionBase
	return &BoolOrEnumOption{
		optionBase: &normalizedOptionBase,
		Value:      o.Value,
	}
}

func (o *starlarkOption) Format() []string {
	if o.Value != nil {
		// Starlark flags can have both a "no" prefix and a value; account for
		// that here.
		return []string{o.optionBase.Format() + "=" + *o.Value}
	}
	return o.BoolOrEnumOption.Format()
}

func (_ *starlarkOption) HasSupportedCommands() bool {
	return true
}

func (_ *starlarkOption) Supports(command string) bool {
	return command != "startup"
}

// ExpansionOption is used to represent an option that expands to other options.
// These options cannot take values and are not interpreted as booleans (true or
// false).
type ExpansionOption struct {
	*optionBase
}

func (o *ExpansionOption) GetDefinition() *Definition {
	return o.Definition
}

func (o *ExpansionOption) SetDefinition(d *Definition) {
	o.Definition = d
}

func (_ *ExpansionOption) HasValue() bool {
	return false
}

func (_ *ExpansionOption) ExpectsValue() bool {
	return false
}

func (_ *ExpansionOption) GetValue() string {
	// Expansion options do not support values.
	return ""
}

func (_ *ExpansionOption) ClearValue() {
	// Expansion options do not support values.
}

func (_ *ExpansionOption) SetValue(value string) {
	// Expansion options do not support values.
}

func (o *ExpansionOption) Format() []string {
	return []string{o.optionBase.Format()}
}

func (o *ExpansionOption) Normalized() Option {
	normalizedOptionBase := *o.optionBase
	normalizedOptionBase.UseName()
	return &ExpansionOption{
		optionBase: &normalizedOptionBase,
	}
}

func (o *ExpansionOption) BoolLike() BoolLike {
	return nil
}

// UnknownOption is used to represent an option that lacks a predetermined
// option definition. These are assumed to be plugin options, but they may also
// be misspellings of known options by users.
type UnknownOption struct {
	Option
}

func (o *UnknownOption) Normalized() Option {
	// do not normalize unknown options.
	return o
}

func Canonicalize(opts []Option) []Option {
	lastOptionIndex := map[string]int{}
	for i, opt := range opts {
		lastOptionIndex[opt.Name()] = i
	}
	// Accumulate only the last instance of a given option
	canonical := make([]Option, 0, len(lastOptionIndex))
	for i, opt := range opts {
		if !opt.Multi() && lastOptionIndex[opt.Name()] > i {
			continue
		}
		canonical = append(canonical, opt.Normalized())
	}
	return canonical
}

func NewStarlarkOptionDefinition(optName string) *Definition {
	return &Definition{
		name:        strings.TrimPrefix(optName, "no"),
		multi:       true,
		hasNegative: true,
		pluginID:    StarlarkBuiltinPluginID,
	}
}

func NewOption(optName string, v *string, d *Definition) (Option, error) {
	option, err := newOptionImpl(optName, v, d)
	if err != nil {
		return nil, err
	}
	if option.PluginID() == UnknownBuiltinPluginID {
		return &UnknownOption{Option: option}, nil
	}
	return option, nil
}

func newOptionImpl(optName string, v *string, d *Definition) (Option, error) {
	if d == nil {
		return nil, fmt.Errorf("In NewOption: definition was nil for optname %s and value %+v", optName, v)
	}
	base, err := NewOptionBase(optName, d)
	if err != nil {
		return nil, err
	}

	if d.PluginID() == StarlarkBuiltinPluginID {
		return &starlarkOption{
			BoolOrEnumOption: BoolOrEnumOption{
				optionBase: base,
				Value:      v,
			},
		}, nil
	}
	if d.RequiresValue() {
		return &RequiredValueOption{
			optionBase: base,
			Value:      v,
			Joined:     v != nil,
		}, nil
	}
	if v != nil {
		// A flag that didn't require a value had one anyway; this is normally okay if this
		// isn't a startup option, but if it's an expansion option we need to emit
		// a warning, and if it's a boolean option prefixed with "no", we need to emit an
		// error.
		if d.Supports("startup") {
			// Unlike command options, startup options don't allow specifying
			// values for options that do not require values.
			return nil, fmt.Errorf("in option --%q: option %q does not take a value", optName, d.name)
		}
		if !d.hasNegative {
			// This is an expansion option with a specified value. Expansion options
			// ignore values and output a warning. Since we canonicalize the options
			// and remove the value ourselves, we should output the warning instead.
			log.Warnf("option '%s' is an expansion option. It does not accept values, and does not change its expansion based on the value provided. Value '%s' will be ignored.", d.name, v)
		}
		if base.Form.Negative() && d.pluginID != StarlarkBuiltinPluginID {
			// This is a negative boolean value (of the form "--noNAME") with a
			// specified value, which is only supported for starlark.
			return nil, fmt.Errorf("Unexpected value after boolean option: %s", optName)
		}
	}
	if d.hasNegative {
		return &BoolOrEnumOption{
			optionBase: base,
			Value:      v,
		}, nil
	}
	return &ExpansionOption{
		optionBase: base,
	}, nil
}

type BoolOrEnum struct {
	b *bool
	e *string
}

func NewBoolOrEnum[T bool | string](value T) *BoolOrEnum {
	p := any(&value)
	switch p := p.(type) {
	case *bool:
		return &BoolOrEnum{b: p}
	case *string:
		return &BoolOrEnum{e: p}
	}
	// This should never happen
	return nil
}

func (b *BoolOrEnum) GetBool() (bool, bool) {
	if b.b == nil {
		return false, false
	}
	return *b.b, true
}

func (b *BoolOrEnum) GetEnum() (string, bool) {
	if b.e == nil {
		return "", false
	}
	return *b.e, true
}

func (b *BoolOrEnum) Get() any {
	switch {
	case b.b != nil:
		v := *b.b
		return &v
	case b.e != nil:
		v := *b.e
		return &v
	default:
		return nil
	}
}

func (b *BoolOrEnum) SetBool(value bool) {
	b.e = nil
	b.b = &value
}

func (b *BoolOrEnum) SetEnum(value string) {
	b.b = nil
	b.e = &value
}

func (b *BoolOrEnum) Set(value any) {
	if value == nil {
		return
	}
	switch value := value.(type) {
	case *bool:
		v := *value
		b.b = &v
	case *string:
		v := *value
		b.e = &v
	}
}

func NameFilter[O Option](name string) func(O) bool {
	return func(opt O) bool {
		return opt.Name() == name
	}
}

// AccumulateValues accepts an initial value, acc, and a Sequenceable Option
// parameter, opts, and returns the resulting value of evaluating all of those
// options in order. It should only be called with opts that all share the same
// definition, and an inital value that matches the type of value that
// definition implies. Otherwise, its output will be nonsensical.
func AccumulateValues[O Option, T string | []string | bool | BoolOrEnum, S seq.Sequenceable[O]](acc T, opts S) (T, error) {
	p := any(&acc)
	for opt := range seq.Sequence[O](opts) {
		switch p := p.(type) {
		case *string:
			*p = opt.GetValue()
		case *[]string:
			*p = append(*p, opt.GetValue())
		case *bool:
			b := opt.BoolLike()
			if b == nil {
				return *new(T), fmt.Errorf("Option '%s' is not a boolean (or boolean-or-enum) option.", opt.Name())
			}
			v, err := b.AsBool()
			if err != nil {
				return *new(T), fmt.Errorf("Option '%s' was not convertable to a boolean value.", strings.Join(opt.Format(), " "))
			}
			*p = v
		case *BoolOrEnum:
			b := opt.BoolLike()
			if b == nil {
				return *new(T), fmt.Errorf("Option '%s' is not a boolean (or boolean-or-enum) option.", opt.Name())
			}
			v, err := b.AsBool()
			if err != nil {
				p.SetEnum(opt.GetValue())
				continue
			}
			p.SetBool(v)
		default:
			return *new(T), fmt.Errorf("Accumulator is of unsupported type %T.", acc)
		}
	}
	return acc, nil
}
