package experiments

// Experiment represents a particular experiment, which can be activated
// independently of all other experiments.
type Experiment string

// All active and defunct experiments must be represented by constants whose
// internal string values are unique.
//
// Each of these declared constants must also be registered as either a
// current or a defunct experiment in the init() function below.
//
// Each experiment is represented by a string that must be a valid HCL
// identifier so that it can be specified in configuration.
const (
	VariableValidation             = Experiment("variable_validation")
	ModuleVariableOptionalAttrs    = Experiment("module_variable_optional_attrs")
	SuppressProviderSensitiveAttrs = Experiment("provider_sensitive_attrs")
)

func init() {
	// Each experiment constant defined above must be registered here as either
	// a current or a concluded experiment.
	registerConcludedExperiment(VariableValidation, "Custom variable validation can now be used by default, without enabling an experiment.")
	registerConcludedExperiment(SuppressProviderSensitiveAttrs, "Provider-defined sensitive attributes are now redacted by default, without enabling an experiment.")
	registerCurrentExperiment(ModuleVariableOptionalAttrs)
}

// GetCurrent takes an experiment name and returns the experiment value
// representing that expression if and only if it is a current experiment.
//
// If the selected experiment is concluded, GetCurrent will return an
// error of type ConcludedError whose message hopefully includes some guidance
// for users of the experiment on how to migrate to a stable feature that
// succeeded it.
//
// If the selected experiment is not known at all, GetCurrent will return an
// error of type UnavailableError.
func GetCurrent(name string) (Experiment, error) {
	exp := Experiment(name)
	if currentExperiments.Has(exp) {
		return exp, nil
	}

	if msg, concluded := concludedExperiments[exp]; concluded {
		return Experiment(""), ConcludedError{ExperimentName: name, Message: msg}
	}

	return Experiment(""), UnavailableError{ExperimentName: name}
}

// Keyword returns the keyword that would be used to activate this experiment
// in the configuration.
func (e Experiment) Keyword() string {
	return string(e)
}

// IsCurrent returns true if the receiver is considered a currently-selectable
// experiment.
func (e Experiment) IsCurrent() bool {
	return currentExperiments.Has(e)
}

// IsConcluded returns true if the receiver is a concluded experiment.
func (e Experiment) IsConcluded() bool {
	_, exists := concludedExperiments[e]
	return exists
}

// currentExperiments are those which are available to activate in the current
// version of Terraform.
//
// Members of this set are registered in the init function above.
var currentExperiments = make(Set)

// concludedExperiments are those which were available to activate in an earlier
// version of Terraform but are no longer available, either because the feature
// in question has been implemented or because the experiment failed and the
// feature was abandoned. Each experiment maps to a message describing the
// outcome, so we can give users feedback about what they might do in modules
// using concluded experiments.
//
// After an experiment has been concluded for a whole major release span it can
// be removed, since we expect users to perform upgrades one major release at
// at time without skipping and thus they will see the concludedness error
// message as they upgrade through a prior major version.
//
// Members of this map are registered in the init function above.
var concludedExperiments = make(map[Experiment]string)

func registerCurrentExperiment(exp Experiment) {
	currentExperiments.Add(exp)
}

func registerConcludedExperiment(exp Experiment, message string) {
	concludedExperiments[exp] = message
}
