require 'json'

module ValidationHelper

  # VALIDATION_NAME_MAPPING[options_key], where `options_key` is a valid key for
  # the `options` Hash passed to `validation_for_field`, returns the corresponding
  # key for the validation_for_X.add(Validate[key], ...) JavaScript function call.
  VALIDATION_NAME_MAPPING =   {
    presence: "Presence",
    maximum_length: "Length",
    minimum_length: "Length",
    numericality: "Numericality",
    exclusion: "Exclusion",
  }

  # Set a custom error-message handler that puts the errors on 
  # their respective fields instead of on the top of the page
  ActionView::Base.field_error_proc = Proc.new {|html_tag, instance|
    %(<span class='error'>#{html_tag}</span>).html_safe
    # # don't put errors on the labels, duh
    # if !html_tag.match(/label/)
    #   %(<span class='error'>#{html_tag}</span>).html_safe
    # elsif instance.error_message.kind_of?(Array)
    #   %(<span class='error'>#{html_tag} <ul class='error'><li>#{instance.error_message.join('</li><li>')}</li></ul></span>).html_safe
    # else
    #   %(<span class='error'>#{html_tag} #{instance.error_message}</span>).html_safe
    # end
  }
  
  # much simplified and html-safed version of error_messages_for
  # error messages containing a "^" will have everything before the "^" wiped out
  def error_messages_for(object)
    if object.is_a? Symbol
      object = instance_variable_get("@#{object}")
    end
    
    if object && object.errors.any?
      errors = object.errors.full_messages
      intro = content_tag(:h4, h(ts("Sorry! We couldn't save this %{objectname} because:", objectname: object.class.model_name.human.to_s.downcase.gsub(/_/, ' '))))
      error_messages_formatted(errors, intro)
    end
  end
  
  def error_messages_formatted(errors, intro = "")
    return unless errors.present?

    error_messages = errors.map { |msg| content_tag(:li, msg.gsub(/^(.*?)\^/, "").html_safe) }
      .join("\n").html_safe
    content_tag(:div, intro.html_safe + content_tag(:ul, error_messages), id: "error", class: "error")
  end
  
  # use to make sure we have consistent name throughout
  def live_validation_varname(id)
    "validation_for_#{id}"
  end

  # puts the standard wrapper around the code and declares the LiveValidation object
  def live_validation_wrapper(id, validation_code)
    valid = "var #{live_validation_varname(id)} = new LiveValidation('#{id}', { wait: 500, onlyOnBlur: false });\n".html_safe
    valid += validation_code
    return javascript_tag valid
  end

  # Generate javascript call for live validation. All the messages have default translated values. 
  # Options:
  # presence: true/false -- ensure the field is not blank. (default TRUE)
  # failureMessage: msg -- shown if field is blank (default "Must be present.")
  # validMessage: msg -- shown when field is ok (default has been set to empty in the actual livevalidation.js file)
  # maximum_length: [max value] -- field must be no more than this many characters long 
  # tooLongMessage: msg -- shown if too long
  # minimum_length: [min value] -- field must be at least this many characters long 
  # tooShortMessage: msg -- shown if too short
  #
  # Most basic usage: 
  #   <input type=text id="field_to_validate">
  #   <%= live_validation_for_field("field_to_validate") %>
  # This will make sure this field is present and use translated error messages. 
  # 
  # More custom usage (from work form): 
  #    <%= c.text_area :content, class: "mce-editor", id: "content" %>
  #    <%= live_validation_for_field('content', 
  #        maximum_length: ArchiveConfig.CONTENT_MAX, minimum_length: ArchiveConfig.CONTENT_MIN, 
  #        tooLongMessage: 'We salute your ambition! But sadly the content must be less than %d letters long. (Maybe you want to create a multi-chaptered work?)'/ArchiveConfig.CONTENT_MAX,
  #        tooShortMessage: 'Brevity is the soul of wit, but your content does have to be at least %d letters long.'/ArchiveConfig.CONTENT_MIN,
  #        failureMessage: 'You did want to post a story here, right?')
  #    %>
  # 
  # Add more default values here! There are many more live validation options, see the code in
  # the javascripts folder for details. 
  def live_validation_for_field(id, options = {})
    defaults = {presence: true,
                failureMessage: 'Must be present.',
                validMessage: ''}                
    if options[:maximum_length]
      defaults.merge!(tooLongMessage: 'Must be less than ' + options[:maximum_length].to_s + ' letters long.') #/
    end
    if options[:minimum_length]
      defaults.merge!(tooShortMessage: 'Must be at least ' + options[:minimum_length].to_s + ' letters long.') #/
    end
    if options[:notANumberMessage]
      defaults.merge!(notANumberMessage: 'Please enter a number') #/
    end

    options = defaults.merge(options)

    # Remove things where the value is a falsey, e.g.:
    #    live_validation_for_field(id, {presence: false})
    options.reject!{|k, v| !v}

    # Generates a Hash mapping option[] keys to Hashes, each Hash being a
    # representation of the arguments passed to validation_for_X.add() in
    # JavaScript.
    validation_hashes = {}
    options.each do |key, _|
      validation_hashes[key] =
        case key
        when :presence
          {
          "failureMessage" => options[:failureMessage].to_s,
          "validMessage"   => options[:validMessage].to_s,
          }
        when :maximum_length
          {
            "maximum"        => options[:maximum_length].to_s,
            "tooLongMessage" => options[:tooLongMessage].to_s,
          }
        when :minimum_length
          {
            "minimum"         => options[:minimum_length].to_s,
            "tooShortMessage" => options[:tooShortMessage].to_s,
          }
        when :numericality
          {
            "notANumberMessage" => options[:notANumberMessage].to_s,
            "validMessage"      => options[:validMessage].to_s
          }
        when :exclusion
          {
            "within"         => options[:exclusion],
            "failureMessage" => options[:failureMessage],
            "validMessage"   => options[:validMessage],
          }
        end
    end

    # Build the validation code from the hashes created above.
    validation_code = validation_code_builder(id, validation_hashes)

    return live_validation_wrapper(id, validation_code.html_safe)
  end

  private

  # Builds validation_for_X.add(...) JavaScript calls.
  #
  # Implementation note: JSON is a subset of JavaScript, so `object.to_json`
  #                      works *brilliantly* for this.
  def validation_json(id, validate_key, object)
    validation_code = live_validation_varname(id)
    validation_code += ".add(Validate.#{validate_key}, %s);" % object.to_json

    validation_code
  end

  # Builds a sequence of validation_for_X.add(...) JavaScript calls.
  #
  # Takes `id` and a hash mapping `options` keys to a Hash that, when converted
  # converted to JSON, is the second argument for validation_for_X.add().
  #
  # For each key specified both in the hash and in `options`, it adds the
  # corresponding validation_for_X.add(...) call.
  def validation_code_builder(id, validation_hashes)
    validation_hashes.reject! {|k, v| v.nil?}

    validation_hashes.map do |option_key, object|
      validate_key = VALIDATION_NAME_MAPPING[option_key]
      validation_json(id, validate_key, object)
    end.join("\n")
  end

end
