class WorkSearchForm

  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  ATTRIBUTES = [
    :query,
    :title,
    :creators,
    :collected,
    :faceted,
    :revised_at,
    :language_id,
    :complete,
    :crossover,
    :single_chapter,
    :word_count,
    :hits,
    :kudos_count,
    :bookmarks_count,
    :comments_count,
    :pseud_ids,
    :collection_ids,
    :tag,
    :excluded_tag_names,
    :excluded_tag_ids,
    :other_tag_names,
    :filter_ids,
    :fandom_names,
    :fandom_ids,
    :rating_ids,
    :category_ids,
    :archive_warning_ids,
    :character_names,
    :character_ids,
    :relationship_names,
    :relationship_ids,
    :freeform_names,
    :freeform_ids,
    :date_from,
    :date_to,
    :words_from,
    :words_to,
    :sort_column,
    :sort_direction,
    :page
  ].freeze

  attr_accessor :options

  ATTRIBUTES.each do |filterable|
    define_method(filterable) { options[filterable] }
  end

  def initialize(opts={})
    @options = opts
    process_options
    @searcher = WorkQuery.new(@options)
  end

  def process_options
    @options.delete_if { |k, v| v == "0" || v.blank? }
    standardize_creator_queries
    standardize_language_ids
    set_sorting
    clean_up_angle_brackets
    rename_warning_field
  end

  # Make the creator/creators change backwards compatible
  def standardize_creator_queries
    return unless @options[:query].present?
    @options[:query] = @options[:query].gsub('creator:', 'creators:')
  end

  def standardize_language_ids
    # Maintain backward compatibility for old work searches/filters:

    # - Using language IDs in the "Language" dropdown
    if @options[:language_id].present? && @options[:language_id].to_i != 0
      language = Language.find_by(id: options[:language_id])
      options[:language_id] = language.short if language.present?
    end

    # - Using language IDs in "Any field" (search) or "Search within results" (filters)
    if @options[:query].present?
      @options[:query] = @options[:query].gsub(/\blanguage_id\s*:\s*(\d+)/) do
        lang = Language.find_by(id: Regexp.last_match[1])
        lang = Language.default if lang.blank?
        "language_id: " + lang.short
      end
    end
  end

  def set_sorting
    @options[:sort_column] ||= default_sort_column
    @options[:sort_direction] ||= default_sort_direction
  end

  def clean_up_angle_brackets
    [:word_count, :hits, :kudos_count, :comments_count, :bookmarks_count, :revised_at, :query].each do |countable|
      next unless @options[countable].present?
      str = @options[countable]
      @options[countable] = str.gsub("&gt;", ">").gsub("&lt;", "<")
    end
  end

  def rename_warning_field
    if @options[:warning_ids].present?
      @options[:archive_warning_ids] = @options.delete(:warning_ids)
    end
  end

  def persisted?
    false
  end

  def summary
    summary = []
    if @options[:query].present?
      summary << @options[:query].gsub('creators:', 'creator:')
    end
    if @options[:title].present?
      summary << "Title: #{@options[:title]}"
    end
    if @options[:creators].present?
      summary << "Creator: #{@options[:creators]}"
    end
    tags = @searcher.included_tag_names
    all_tag_ids = @searcher.filter_ids
    unless all_tag_ids.empty?
      tags << Tag.where(id: all_tag_ids).pluck(:name).join(", ")
    end
    unless tags.empty?
      summary << "Tags: #{tags.uniq.join(", ")}"
    end
    if complete.to_s == "T"
      summary << "Complete"
    elsif complete.to_s == "F"
      summary << "Incomplete"
    end
    if crossover.to_s == "T"
      summary << "Only Crossovers"
    elsif crossover.to_s == "F"
      summary << "No Crossovers"
    end
    if %w(1 true).include?(self.single_chapter.to_s)
      summary << "Single Chapter"
    end
    if @options[:language_id].present?
      language = Language.find_by(short: @options[:language_id])
      if language.present?
        summary << "Language: <span lang=#{language.short}>#{language.name}</span>"
      end
    end
    [:word_count, :hits, :kudos_count, :comments_count, :bookmarks_count, :revised_at].each do |countable|
      if @options[countable].present?
        summary << "#{countable.to_s.humanize.downcase}: #{@options[countable]}"
      end
    end
    if @options[:sort_column].present?
      # Use pretty name if available, otherwise fall back to plain column name
      pretty_sort_name = name_for_sort_column(@options[:sort_column])
      direction = if @options[:sort_direction].present?
                    @options[:sort_direction] == "asc" ? " ascending" : " descending"
                  else
                    ""
                  end
      summary << ("sort by: #{pretty_sort_name&.downcase || @options[:sort_column]}" + direction)
    end
    summary.join(" ")
  end

  def search_results
    @searcher.search_results
  end

  ###############
  # SORTING
  ###############

  SORT_OPTIONS = [
    ["Best Match", "_score"],
    %w[Creator authors_to_sort_on],
    %w[Title title_to_sort_on],
    ["Date Posted", "created_at"],
    ["Date Updated", "revised_at"],
    ["Word Count", "word_count"],
    %w[Hits hits],
    %w[Kudos kudos_count],
    %w[Comments comments_count],
    %w[Bookmarks bookmarks_count]
  ].freeze

  def sort_columns
    options[:sort_column] || default_sort_column
  end

  def sort_direction
    options[:sort_direction] || default_sort_direction
  end

  def sort_options
    options[:faceted] || options[:collected] ? SORT_OPTIONS[1..-1] : SORT_OPTIONS
  end

  def sort_values
    sort_options.map{ |option| option.last }
  end

  # extract the pretty name
  def name_for_sort_column(sort_column)
    Hash[SORT_OPTIONS.map { |v| [v[1], v[0]] }][sort_column]
  end

  def default_sort_column
    options[:faceted] || options[:collected] ? 'revised_at' : '_score'
  end

  def default_sort_direction
    if %w[authors_to_sort_on title_to_sort_on].include?(sort_column)
      'asc'
    else
      'desc'
    end
  end
end
