module Padrino
  module Helpers
    module FormHelpers
      ##
      # Helpers to generate options list for select tag.
      #
      module Options
        def extract_option_tags!(options)
          state = extract_option_state!(options)
          option_tags =
            if options[:grouped_options]
              grouped_options_for_select(options.delete(:grouped_options), state)
            else
              options_for_select(extract_option_items!(options), state)
            end

          if (prompt = options.delete(:include_blank))
            option_tags.unshift(blank_option(prompt))
          end
          option_tags
        end

        private

        ##
        # Returns the blank option serving as a prompt if passed.
        #
        def blank_option(prompt)
          case prompt
          when nil, false
            nil
          when String
            content_tag(:option, prompt,       value: '')
          when Array
            content_tag(:option, prompt.first, value: prompt.last)
          else
            content_tag(:option, '',           value: '')
          end
        end

        ##
        # Returns whether the option should be selected or not.
        #
        # @example
        #   option_is_selected?('red', 'Red', ['red', 'blue'])   => true
        #   option_is_selected?('red', 'Red', ['green', 'blue']) => false
        #
        def option_is_selected?(value, caption, selected_values)
          check_value = (value || caption).to_s

          Array(selected_values).any? { |selected| check_value == selected.to_s }
        end

        ##
        # Returns the options tags for a select based on the given option items.
        #
        def options_for_select(option_items, state = {})
          return [] if option_items.none?

          option_items.map do |caption, value, attributes|
            html_attributes = { value: value || caption }.merge(attributes || {})
            html_attributes[:selected] ||= option_is_selected?(html_attributes[:value], caption, state[:selected])
            html_attributes[:disabled] ||= option_is_selected?(html_attributes[:value], caption, state[:disabled])
            content_tag(:option, caption, html_attributes)
          end
        end

        ##
        # Returns the optgroups with options tags for a select based on the given :grouped_options items.
        #
        def grouped_options_for_select(collection, state = {})
          collection.map do |item|
            caption = item.shift
            attributes = item.last.is_a?(Hash) ? item.pop : {}
            value = item.flatten(1)
            attributes = value.pop if value.last.is_a?(Hash)
            html_attributes = { label: caption }.merge(attributes || {})
            content_tag(:optgroup, options_for_select(value, state), html_attributes)
          end
        end

        def extract_option_state!(options)
          {
            selected: Array(options.delete(:value)) | Array(options.delete(:selected)) | Array(options.delete(:selected_options)),
            disabled: Array(options.delete(:disabled_options))
          }
        end

        def extract_option_items!(options)
          if options[:collection]
            fields = options.delete(:fields)
            collection = options.delete(:collection)
            collection.map { |item| [item.send(fields.first), item.send(fields.last)] }
          else
            options.delete(:options) || []
          end
        end
      end
    end
  end
end
