class AutocompleteController < ApplicationController
  respond_to :json

  skip_before_action :store_location
  skip_around_action :set_current_user, except: [:collection_parent_name, :owned_tag_sets, :site_skins]
  skip_before_action :sanitize_ac_params # can we dare!

  #### DO WE NEED THIS AT ALL? IF IT FIRES WITHOUT A TERM AND 500s BECAUSE USER DID SOMETHING WACKY SO WHAT
  # # If you have an autocomplete that should fire without a term add it here
  # before_action :require_term, except: [:tag_in_fandom, :relationship_in_fandom, :character_in_fandom, :nominated_parents]
  #
  # def require_term
  #   if params[:term].blank?
  #     flash[:error] = ts("What were you trying to autocomplete?")
  #     redirect_to(request.env["HTTP_REFERER"] || root_path) and return
  #   end
  # end
  #
  #########################################
  ############# LOOKUP ACTIONS GO HERE

  # PSEUDS
  def pseud
    return if params[:term].blank?

    render_output(Pseud.autocomplete_lookup(search_param: params[:term], autocomplete_prefix: "autocomplete_pseud").map { |res| Pseud.fullname_from_autocomplete(res) })
  end

  ## TAGS
  private
    def tag_output(search_param, tag_type)
      tags = Tag.autocomplete_lookup(search_param: search_param, autocomplete_prefix: "autocomplete_tag_#{tag_type}")
      render_output tags.map {|r| Tag.name_from_autocomplete(r)}
    end
  public
  # these are all basically duplicates but make our calls to autocomplete more readable
  def tag; tag_output(params[:term], params[:type] || "all"); end
  def fandom; tag_output(params[:term], "fandom"); end
  def character; tag_output(params[:term], "character"); end
  def relationship; tag_output(params[:term], "relationship"); end
  def freeform; tag_output(params[:term], "freeform"); end


  ## TAGS IN FANDOMS
  private
    def tag_in_fandom_output(params)
      render_output(Tag.autocomplete_fandom_lookup(params).map {|r| Tag.name_from_autocomplete(r)})
    end
  public
  def character_in_fandom; tag_in_fandom_output(params.merge({tag_type: "character"})); end
  def relationship_in_fandom; tag_in_fandom_output(params.merge({tag_type: "relationship"})); end


  ## TAGS IN SETS
  #
  # Note that only tagsets in OwnedTagSets are in autocomplete
  #

  # expects the following params:
  # :tag_set - tag set ids comma-separated
  # :tag_type - tag type as a string unless "all" desired
  # :in_any - set to false if only want tags in ALL specified sets
  # :term - the search term
  def tags_in_sets
    results = TagSet.autocomplete_lookup(params)
    render_output(results.map {|r| Tag.name_from_autocomplete(r)})
  end

  # expects the following params:
  # :fandom - fandom name(s) as an array or a comma-separated string
  # :tag_set - tag set id(s) as an array or a comma-separated string
  # :tag_type - tag type as a string unless "all" desired
  # :include_wrangled - set to false if you only want tags from the set associations and NOT tags wrangled into the fandom
  # :fallback - set to false to NOT do
  # :term - the search term
  def associated_tags
    if params[:fandom].blank?
      render_output([ts("Please select a fandom first!")])
    else
      results = TagSetAssociation.autocomplete_lookup(params)
      render_output(results.map {|r| Tag.name_from_autocomplete(r)})
    end
  end

  ## NONCANONICAL TAGS
  def noncanonical_tag
    search_param = Query.new.escape_reserved_characters(params[:term])
    raise "Redshirt: Attempted to constantize invalid class initialize noncanonical_tag #{params[:type].classify}" unless Tag::TYPES.include?(params[:type].classify)

    tag_class = params[:type].classify.constantize
    one_tag = tag_class.find_by(canonical: false, name: params[:term]) if params[:term].present?
    # If there is an exact match in the database, ensure it is the first thing suggested.
    match = if one_tag
              [one_tag.name]
            else
              []
            end

    # As explained in https://stackoverflow.com/a/54080114, the Elasticsearch suggestion suggester does not support
    # matches in the middle of a series of words. Therefore, we break the autocomplete query into its individual
    # words – based on whitespace – except for the last word, which could be incomplete, so a prefix match is
    # appropriate. This emulates the behavior of SQL `LIKE '%text%'.

    word_list = search_param.split
    last_word = word_list.pop
    search_list = word_list.map { |w| { term: { name: { value: w, case_insensitive: true } } } } + [{ prefix: { name: { value: last_word, case_insensitive: true } } }]
    begin
      # Size is chosen so we get enough search results from each shard.
      search_results = $elasticsearch.search(
        index: TagIndexer.index_name,
        body: { size: "100", query: { bool: { filter: [{ match: { tag_type: params[:type].capitalize } }, { match: { canonical: false } }], must: search_list } } }
      )
      render_output((match + search_results["hits"]["hits"].first(10).map { |t| t["_source"]["name"] }).uniq)
    rescue Elastic::Transport::Transport::Errors::BadRequest
      render_output(match)
    end
  end

  # more-specific autocompletes should be added below here when they can't be avoided

  # look up collections ranked by number of items they contain

  def collection_fullname
    results = Collection.autocomplete_lookup(search_param: params[:term], autocomplete_prefix: "autocomplete_collection_all").map {|res| Collection.fullname_from_autocomplete(res)}
    render_output(results)
  end

  # return collection names

  def open_collection_names
    # in this case we want different ids from names so we can display the title but only put in the name
    results = Collection.autocomplete_lookup(search_param: params[:term], autocomplete_prefix: "autocomplete_collection_open").map do |str|
      {id: (whole_name = Collection.name_from_autocomplete(str)), name: Collection.title_from_autocomplete(str) + " (#{whole_name})" }
    end
    respond_with(results)
  end

  # For creating collections, autocomplete the name of a parent collection owned by the user only
  def collection_parent_name
    render_output(current_user.maintained_collections.top_level.with_name_like(params[:term]).pluck(:name).sort)
  end

  # for looking up existing urls for external works to avoid duplication
  def external_work
    render_output(ExternalWork.where(["url LIKE ?", '%' + params[:term] + '%']).limit(10).order(:url).pluck(:url))
  end

  # the pseuds of the potential matches who could fulfill the requests in the given signup
  def potential_offers
    potential_matches(false)
  end

  # the pseuds of the potential matches who want the offers in the given signup
  def potential_requests
    potential_matches(true)
  end

  # Return matching potential requests or offers
  def potential_matches(return_requests=true)
    search_param = params[:term]
    signup_id = params[:signup_id]
    signup = ChallengeSignup.find(signup_id)
    pmatches = return_requests ?
      signup.offer_potential_matches.sort.reverse.map {|pm| pm.request_signup.pseud.byline} :
      signup.request_potential_matches.sort.reverse.map {|pm| pm.offer_signup.pseud.byline}
    pmatches.select! { |pm| pm.match(/#{Regexp.escape(search_param)}/) } if search_param.present?
    render_output(pmatches)
  end

  # owned tag sets that are usable by all
  def owned_tag_sets
    if params[:term].length > 0
      search_param = '%' + params[:term] + '%'
      render_output(OwnedTagSet.limit(10).order(:title).usable.where("owned_tag_sets.title LIKE ?", search_param).collect(&:title))
    end
  end

  # skins for parenting
  def site_skins
    if params[:term].present?
      search_param = '%' + params[:term] + '%'
      query = Skin.site_skins.where("title LIKE ?", search_param).limit(15).sort_by_recent
      if logged_in?
        query = query.approved_or_owned_by(current_user)
      else
        query = query.approved_skins
      end
      render_output(query.pluck(:title))
    end
  end

  # admin posts for translations, formatted as Admin Post Title (Post #id)
  def admin_posts
    if params[:term].present?
      search_param = '%' + params[:term] + '%'
      results = AdminPost.non_translated.where("title LIKE ?", search_param).limit(ArchiveConfig.MAX_RECENT).map do |result|
        {id: (post_id = result.id),
        name: result.title + " (Post ##{post_id})" }
      end
      respond_with(results)
    end
  end

  def admin_post_tags
    if params[:term].present?
      search_param = '%' + params[:term].strip + '%'
      query = AdminPostTag.where("name LIKE ?", search_param).limit(ArchiveConfig.MAX_RECENT)
      render_output(query.pluck(:name))
    end
  end

private

  # Because of the respond_to :json at the top of the controller, this will return a JSON-encoded
  # response which the autocomplete javascript on the other end should be able to handle :)
  def render_output(result_strings)
    if result_strings.first.is_a?(String)
      respond_with(result_strings.map {|str| {id: str, name: str}})
    else
      respond_with(result_strings)
    end
  end

end
