class Inspec::Input

def self.infer_event(options)

3. By options[:default] (deprecated)
2. By options[:value]
1. By event.value (preferred)
We can determine a value:
def self.infer_event(options)
  # Don't rely on this working; you really should be passing a proper Input::Event
  # with the context information you have.
  location = Input::Event.probe_stack
  event = Input::Event.new(
    action: :set,
    provider: options[:provider] || :unknown,
    priority: options[:priority] || Inspec::Input::DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER,
    file: location.path,
    line: location.lineno
  )
  if options.key?(:default)
    Inspec.deprecate(:attrs_value_replaces_default, "attribute name: '#{name}'")
    if options.key?(:value)
      Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default"
      options.delete(:default)
    else
      options[:value] = options.delete(:default)
    end
  end
  event.value = options[:value] if options.key?(:value)
  options[:event] = event
end

def _update_set_metadata(options)

def _update_set_metadata(options)
  # Basic metadata
  @title = options[:title] if options.key?(:title)
  @description = options[:description] if options.key?(:description)
  @required = options[:required] if options.key?(:required)
  @identifier = options[:identifier] if options.key?(:identifier) # TODO: determine if this is ever used
  @type = options[:type] if options.key?(:type)
  @sensitive = options[:sensitive] if options.key?(:sensitive)
  @pattern = options[:pattern] if options.key?(:pattern)
end

def current_value(warn_on_missing = true)

Determine the current winning value, but don't validate it
def current_value(warn_on_missing = true)
  # Examine the events to determine highest-priority value. Tie-break
  # by using the last one set.
  events_that_set_a_value = events.select(&:value_has_been_set?)
  winning_priority = events_that_set_a_value.map(&:priority).max
  winning_events = events_that_set_a_value.select { |e| e.priority == winning_priority }
  winning_event = winning_events.last # Last for tie-break
  if winning_event.nil?
    # No value has been set - return special no value object
    NO_VALUE_SET.new(name, warn_on_missing)
  else
    winning_event.value # May still be nil
  end
end

def diagnostic_string

def diagnostic_string
  "Input #{name}, with history:\n" +
    events.map(&:diagnostic_string).map { |line| "  #{line}" }.join("\n")
end

def enforce_pattern_restriction!

def enforce_pattern_restriction!
  return unless pattern
  return unless has_value?
  string_value = current_value(false).to_s
  valid_pattern = string_value.match?(pattern)
  unless valid_pattern
    error = Inspec::Input::ValidationError.new
    error.input_name = @name
    error.input_value = string_value
    error.input_pattern = pattern
    raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to pattern '#{error.input_pattern}'."
  end
end

def enforce_required_validation!

def enforce_required_validation!
  return unless required
  # skip if we are not doing an exec call (archive/vendor/check)
  return unless Inspec::BaseCLI.inspec_cli_command == :exec
  proposed_value = current_value(false)
  if proposed_value.nil? || proposed_value.is_a?(NO_VALUE_SET)
    error = Inspec::Input::RequiredError.new
    error.input_name = name
    raise error, "Input '#{error.input_name}' is required and does not have a value."
  end
end

def enforce_type_restriction! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def enforce_type_restriction! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
  return unless type
  return unless has_value?
  type_req = type
  return if type_req == "Any"
  proposed_value = current_value(false)
  invalid_type = false
  if type_req == "Regexp"
    invalid_type = true unless valid_regexp?(proposed_value)
  elsif type_req == "Numeric"
    invalid_type = true unless valid_numeric?(proposed_value)
  elsif type_req == "Boolean"
    invalid_type = true unless [true, false].include?(proposed_value)
  elsif proposed_value.is_a?(Module.const_get(type_req)) == false
    # TODO: why is this case here?
    invalid_type = true
  end
  if invalid_type == true
    error = Inspec::Input::ValidationError.new
    error.input_name = @name
    error.input_value = proposed_value
    error.input_type = type_req
    raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to type '#{error.input_type}'."
  end
end

def has_value?

def has_value?
  !current_value(false).is_a? NO_VALUE_SET
end

def initialize(name, options = {})

def initialize(name, options = {})
  @name = name
  @opts = options
  if @opts.key?(:default)
    Inspec.deprecate(:attrs_value_replaces_default, "input name: '#{name}'")
    if @opts.key?(:value)
      Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default"
      @opts.delete(:default)
    end
  end
  # Array of Input::Event objects.  These compete with one another to determine
  # the value of the input when value() is called, as well as providing a
  # debugging record of when and how the value changed.
  @events = []
  events.push make_creation_event(options)
  update(options)
end

def make_creation_event(options)

def make_creation_event(options)
  loc = options[:location] || Event.probe_stack
  Input::Event.new(
    action: :create,
    provider: options[:provider],
    file: loc.path,
    line: loc.lineno
  )
end

def normalize_pattern_restriction!

def normalize_pattern_restriction!
  return unless pattern
  unless valid_regexp?(pattern)
    error = Inspec::Input::PatternError.new
    error.input_pattern = pattern
    raise error, "Pattern '#{error.input_pattern}' is not a valid regex pattern."
  end
  @pattern = pattern
end

def normalize_type_restriction!

def normalize_type_restriction!
  return unless type
  type_req = type.capitalize
  abbreviations = {
    "Num" => "Numeric",
    "Regex" => "Regexp",
  }
  type_req = abbreviations[type_req] if abbreviations.key?(type_req)
  unless VALID_TYPES.include?(type_req)
    error = Inspec::Input::TypeError.new
    error.input_type = type_req
    raise error, "Type '#{error.input_type}' is not a valid input type."
  end
  @type = type_req
end

def ruby_var_identifier

def ruby_var_identifier
  identifier || "attr_" + name.downcase.strip.gsub(/\s+/, "-").gsub(/[^\w-]/, "")
end

def set_events

TODO: is this here just for testing?
def set_events
  events.select { |e| e.action == :set }
end

def to_hash

def to_hash
  as_hash = { name: name, options: {} }
  %i{description title identifier type required value sensitive pattern}.each do |field|
    val = send(field)
    next if val.nil?
    as_hash[:options][field] = val
  end
  as_hash
end

def to_hash

def to_hash
  as_hash = { name: name, options: {} }
  %i{description title identifier type required value pattern}.each do |field|
    val = send(field)
    next if val.nil?
    as_hash[:options][field] = val
  end
  as_hash
end

def to_ruby

def to_ruby
  res = ["#{ruby_var_identifier} = attribute('#{name}',{"]
  res.push "  title: '#{title}'," unless title.to_s.empty?
  res.push "  value: #{value.inspect}," unless value.to_s.empty?
  # to_ruby may generate code that is to be used by older versions of inspec.
  # Anything older than 3.4 will not recognize the value: option, so
  # send the default: option as well. See #3759
  res.push "  default: #{value.inspect}," unless value.to_s.empty?
  res.push "  description: '#{description}'," unless description.to_s.empty?
  res.push "})"
  res.join("\n")
end

def to_s

def to_s
  "Input #{name} with value " + (sensitive ? "*** (senstive)" : "#{current_value}")
end

def update(options)

def update(options)
  _update_set_metadata(options)
  normalize_type_restriction!
  normalize_pattern_restriction!
  # Values are set by passing events in; but we can also infer an event.
  if options.key?(:value) || options.key?(:default)
    if options.key?(:event)
      if options.key?(:value) || options.key?(:default)
        Inspec::Log.warn "Do not provide both an Event and a value as an option to attribute('#{name}') - using value from event"
      end
    else
      self.class.infer_event(options) # Sets options[:event]
    end
  end
  events << options[:event] if options.key? :event
  enforce_type_restriction!
  enforce_pattern_restriction!
end

def valid_numeric?(value)

def valid_numeric?(value)
  Float(value)
  true
rescue
  false
end

def valid_regexp?(value)

def valid_regexp?(value)
  # check for invalid regex syntax
  Regexp.new(value)
  true
rescue
  false
end

def value

def value
  enforce_required_validation!
  current_value
end

def value=(new_value, priority = DEFAULT_PRIORITY_FOR_VALUE_SET)

def value=(new_value, priority = DEFAULT_PRIORITY_FOR_VALUE_SET)
  # Inject a new Event with the new value.
  location = Event.probe_stack
  events << Event.new(
    action: :set,
    provider: :value_setter,
    priority: priority,
    value: new_value,
    file: location.path,
    line: location.lineno
  )
  enforce_type_restriction!
  enforce_pattern_restriction!
end