# frozen_string_literal: true

module FiniteMachine
  # A class responsible for converting transition arguments to states
  #
  # Used by {TransitionBuilder} to parse user input state transitions.
  #
  # @api private
  class StateParser
    NON_STATE_KEYS = %i[name if unless silent].freeze

    STATE_KEYS = %i[from to].freeze

    # Extract states from user defined attributes
    #
    # @example
    #   StateParser.parse({from: [:green, :blue], to: :red})
    #   # => {green: :red, green: :blue}
    #
    # @param [Proc] block
    #
    # @yield [Hash[Symbol]] the resolved states
    #
    # @return [Hash[Symbol]] the resolved states
    #
    # @api public
    def self.parse(attributes, &block)
      attrs  = ensure_only_states!(attributes)
      states = extract_states(attrs)
      block ? states.each(&block) : states
    end

    # Extract only states from attributes
    #
    # @return [Hash[Symbol]]
    #
    # @api private
    def self.ensure_only_states!(attrs)
      attributes = attrs.dup
      NON_STATE_KEYS.each { |key| attributes.delete(key) }
      raise_not_enough_transitions unless attributes.any?
      attributes
    end
    private_class_method :ensure_only_states!

    # Perform extraction of states from user supplied definitions
    #
    # @return [Hash[Symbol]] the resolved states
    #
    # @api private
    def self.extract_states(attrs)
      if contains_from_to_keys?(attrs)
        convert_from_to_attributes_to_states_hash(attrs)
      else
        convert_attributes_to_states_hash(attrs)
      end
    end
    private_class_method :extract_states

    # Check if attributes contain :from or :to key
    #
    # @example
    #   StateParser.contains_from_to_keys?({from: :green, to: :red})
    #   # => true
    #
    # @example
    #   StateParser.contains_from_to_keys?({:green => :red})
    #   # => false
    #
    # @return [Boolean]
    #
    # @api public
    def self.contains_from_to_keys?(attrs)
      STATE_KEYS.any? { |key| attrs.keys.include?(key) }
    end
    private_class_method :contains_from_to_keys?

    # Convert attrbiutes with :from, :to keys to states hash
    #
    # @return [Hash[Symbol]]
    #
    # @api private
    def self.convert_from_to_attributes_to_states_hash(attrs)
      Array(attrs[:from] || ANY_STATE).each_with_object({}) do |state, hash|
        hash[state] = attrs[:to] || state
      end
    end
    private_class_method :convert_from_to_attributes_to_states_hash

    # Convert collapsed attributes to states hash
    #
    # @example
    #   StateParser.convert_attributes_to_states_hash([:green, :red] => :yellow)
    #   # => {green: :yellow, red: :yellow}
    #
    # @param [Hash] attrs
    #   the attributes to convert to a simple hash
    #
    # @return [Hash[Symbol]]
    #
    # @api private
    def self.convert_attributes_to_states_hash(attrs)
      attrs.each_with_object({}) do |(k, v), hash|
        if k.respond_to?(:to_ary)
          k.each { |el| hash[el] = v }
        else
          hash[k] = v
        end
      end
    end
    private_class_method :convert_attributes_to_states_hash

    # Raise error when not enough transitions are provided
    #
    # @raise [NotEnoughTransitionsError]
    #   if the event has no transitions
    #
    # @return [nil]
    #
    # @api private
    def self.raise_not_enough_transitions
      raise NotEnoughTransitionsError, "please provide state transitions"
    end
    private_class_method :raise_not_enough_transitions
  end # StateParser
end # FiniteMachine
