class ItemsController < ApplicationController
  before_action :set_query
  before_action :support_staff_only, except: [:index, :show, :sources]
  rescue_from Item::Search::Error, :with => :search_error

  def index
    if @query
      if params[:per_page]
        per_page = params[:per_page].to_i
        per_page = 50 if per_page && per_page > 50
      else
        per_page = 30
      end

      @items = @query.results.paginate(
        page: params[:page], per_page: per_page)
      assign_closeted!(@items)

      respond_to do |format|
        format.html {
          @campaign = Fundraising::Campaign.current rescue nil
          if @items.count == 1
            redirect_to @items.first
          else
            render
          end
        }
        format.json {
          render json: {
            items: @items.as_json(
              methods: [:nc?, :pb?, :owned?, :wanted?],
              include: {
                restricted_zones: {
                  only: [:id, :depth, :label],
                  methods: [:is_commonly_used_by_items],
                },
              },
            ),
            appearances: load_appearances.as_json(
              include: {
                swf_assets: {
                  only: [:id, :remote_id, :body_id],
                  include: {
                    zone: {
                      only: [:id, :depth, :label],
                      methods: [:is_commonly_used_by_items],
                    },
                    restricted_zones: {
                      only: [:id, :depth, :label],
                      methods: [:is_commonly_used_by_items],
                    },
                  },
                  methods: [:urls, :known_glitches],
                },
              }
            ),
            total_pages: @items.total_pages,
            query: @query.to_s,
          }
        }
      end
    elsif params.has_key?(:ids) && params[:ids].is_a?(Array)
      @items = Item.find(params[:ids])
      assign_closeted!(@items)
      respond_to do |format|
        format.json { render json: @items }
      end
    else
      respond_to do |format|
        format.html {
          @campaign = Fundraising::Campaign.current rescue nil
          @newest_items = Item.newest.limit(18)
        }
      end
    end
  end

  def show
    @item = Item.find params[:id]

    respond_to do |format|
      format.html do
        @trades = @item.closet_hangers.trading.user_is_active.to_trades

        @contributors_with_counts = @item.contributors_with_counts

        if user_signed_in?
          @current_user_lists = current_user.closet_lists.alphabetical.
            group_by_owned
          @current_user_quantities = current_user.item_quantities_for(@item)
        end

        @selected_preview_pet_type = load_selected_preview_pet_type
        @preview_outfit = Outfit.new(
          pet_state: load_preview_pet_type.canonical_pet_state,
          worn_items: [@item],
        )
        @preview_error = validate_preview

        @all_appearances = @item.appearances
        @appearances_by_occupied_zone = @item.appearances_by_occupied_zone.
          sort_by { |z, a| z.label }
        @selected_item_appearance = @preview_outfit.item_appearances.first

        @preview_pet_type_options = PetType.where(color: @preview_outfit.color).
          includes(:species).merge(Species.alphabetical)
      end

      format.gif do
        expires_in 1.month
        redirect_to @item.thumbnail_url, allow_other_host: true
      end
    end
  end

  def edit
    @item = Item.find params[:id]
    render layout: "application"
  end

  def update
    @item = Item.find params[:id]
    if @item.update(item_params)
      flash[:notice] = "\"#{@item.name}\" successfully saved!"
      redirect_to @item
    else
      render action: "edit", layout: "application", status: :bad_request
    end
  end

  def sources
    # Load all the items, then group them by source.
    item_ids = params[:ids].split(",")
    @all_items = Item.where(id: item_ids).includes(:nc_mall_record).
      includes(:dyeworks_base_item).order(:name).limit(50)
    @items = @all_items.group_by(&:source).tap { |i| i.default = [] }

    assign_closeted!(@all_items)

    if @all_items.empty?
      render file: "public/404.html", status: :not_found, layout: nil
      return
    end

    # For Dyeworks items whose base is currently in the NC Mall, preload their
    # trade values. We'll use this to determine which ones are fully buyable rn
    # (because Owls tracks this data and we don't).
    Item.preload_nc_trade_values(@items[:dyeworks])

    # Start loading the NC trade values for the non-Mall NC items.
    trade_values_task = Async { Item.preload_nc_trade_values(@items[:other_nc]) }

    # Also, PB items have some special handling: we group them by color, then
    # load example pet types for the colors that don't have paint brushes.
    @pb_items_by_color = @items[:pb].group_by(&:pb_color).
      sort_by { |color, items| color&.name }.to_h

    colors_without_thumbnails = @pb_items_by_color.keys.
      select(&:present?).reject(&:pb_item_thumbnail_url?)

    @pb_color_pet_types = colors_without_thumbnails.map do |color|
      # Infer the ideal species from the first item we can, then try to find a
      # matching pet type to use as the thumbnail, if needed.
      species = @pb_items_by_color[color].map(&:pb_species).select(&:present?)
        .first
      [color, color.example_pet_type(preferred_species: species)]
    end.to_h

    # Create a second value that only include the items the user *needs*: that
    # is, that they don't already own.
    @items_needed = @items.transform_values { |items| items.reject(&:owned?) }
    @pb_items_needed_by_color =
      @pb_items_by_color.transform_values { |items| items.reject(&:owned?) }

    # Finish loading the NC trade values.
    trade_values_task.wait

    render layout: "application"
  end

  protected

  def item_params
    params.require(:item).permit(
      :name, :thumbnail_url, :description, :modeling_status_hint,
      :is_manually_nc, :explicitly_body_specific,
    ).tap do |p|
      p[:modeling_status_hint] = nil if p[:modeling_status_hint] == ""
    end
  end

  def assign_closeted!(items)
    current_user.assign_closeted_to_items!(items) if user_signed_in?
  end

  def load_appearances
    appearance_params = params[:with_appearances_for]
    return {} if appearance_params.blank?
    
    if appearance_params[:alt_style_id].present?
      target = Item::Search::Query.load_alt_style_by_id(
        appearance_params[:alt_style_id])
    else
      target = Item::Search::Query.load_pet_type_by_color_and_species(
        appearance_params[:color_id], appearance_params[:species_id])
    end

    target.appearances_for(@items, swf_asset_includes: [:zone]).
      tap do |appearances|
        # Preload the manifests for these SWF assets concurrently, rather than
        # loading them in sequence when we generate the JSON.
        swf_assets = appearances.values.map(&:swf_assets).flatten
        SwfAsset.preload_manifests(swf_assets)
      end
  end

  def load_selected_preview_pet_type
    color_id = params.dig(:preview, :color_id)
    species_id = params.dig(:preview, :species_id)

    return load_default_preview_pet_type if color_id.nil? || species_id.nil?

    PetType.find_or_initialize_by(color_id:, species_id:).tap do |pet_type|
      if pet_type.persisted?
        cookies["preferred-preview-color-id"] = color_id
        cookies["preferred-preview-species-id"] = species_id
      end
    end
  end

  def load_preview_pet_type
    if @selected_preview_pet_type.persisted?
      @selected_preview_pet_type
    else
      load_default_preview_pet_type
    end
  end

  def load_default_preview_pet_type
    @item.compatible_pet_types.
      preferring_species(cookies["preferred-preview-species-id"] || "<ignore>").
      preferring_color(cookies["preferred-preview-color-id"] || "<ignore>").
      preferring_simple.first ||
      PetType.matching_name("Blue", "Acara").first!
  end

  def validate_preview
    if @selected_preview_pet_type.new_record?
      :pet_type_does_not_exist
    elsif @preview_outfit.item_appearances.any?(&:empty?)
      :no_item_data
    end
  end
  
  def search_error(e)
    @items = []
    @query = params[:q]
    respond_to do |format|
      format.html { flash.now[:alert] = e.message; render }
      format.json { render :json => {error: e.message} }
    end
  end

  def set_query
    q = params[:q]
    if q.is_a?(String)
      @query = Item::Search::Query.from_text(q, current_user)
    elsif q.is_a?(ActionController::Parameters)
      @query = Item::Search::Query.from_params(q, current_user)
    end
  end
end