# frozen_string_literal: true

class ProductsReflex < ApplicationReflex
  include Pagy::Backend

  before_reflex :init_filters_params, :init_pagination_params

  def change_per_page
    @per_page = element.value.to_i
    @page = 1

    fetch_and_render_products_with_flash
  end

  def clear_search
    @search_term = nil
    @producer_id = nil
    @category_id = nil
    @page = 1

    fetch_and_render_products_with_flash
  end

  def delete_product
    id = current_id_from_element(element)
    product = product_finder(id).find_product
    authorize! :delete, product

    if product.destroy
      flash[:success] = I18n.t('admin.products_v3.delete_product.success')
    else
      flash[:error] = I18n.t('admin.products_v3.delete_product.error')
    end

    fetch_and_render_products_with_flash
  end

  def delete_variant
    id = current_id_from_element(element)
    variant = Spree::Variant.active.find(id)
    authorize! :delete, variant

    if VariantDeleter.new.delete(variant)
      flash[:success] = I18n.t('admin.products_v3.delete_variant.success')
    else
      flash[:error] = I18n.t('admin.products_v3.delete_variant.error')
    end

    fetch_and_render_products_with_flash
  end

  def edit_image
    id = current_id_from_element(element)
    product = product_finder(id).find_product
    image = product.image

    image = Spree::Image.new(viewable: product) if product.image.blank?

    morph "#modal-component",
          render(partial: "admin/products_v3/edit_image",
                 locals: { product:, image:, return_url: url })
  end

  private

  def init_filters_params
    # params comes from the form
    # _params comes from the url
    # priority is given to params from the form (if present) over url params
    @search_term = params[:search_term] || params[:_search_term]
    @producer_id = params[:producer_id] || params[:_producer_id]
    @category_id = params[:category_id] || params[:_category_id]
  end

  def init_pagination_params
    # prority is given to element dataset (if present) over url params
    @page = element.dataset.page || params[:_page] || 1
    @per_page = element.dataset.perpage || params[:_per_page] || 15
  end

  def fetch_and_render_products_with_flash
    fetch_products
    render_products
  end

  def render_products
    cable_ready.replace(
      selector: "#products-content",
      html: render(partial: "admin/products_v3/content",
                   locals: { products: @products, pagy: @pagy, search_term: @search_term,
                             producer_options: producers, producer_id: @producer_id,
                             category_options: categories, category_id: @category_id,
                             flashes: flash })
    )

    cable_ready.replace_state(
      url: current_url,
    )

    morph :nothing
  end

  def render_products_form_with_flash
    locals = { products: @products }
    locals[:error_counts] = @error_counts if @error_counts.present?
    locals[:flashes] = flash if flash.any?

    cable_ready.replace(
      selector: "#products-form",
      html: render(partial: "admin/products_v3/table", locals:)
    )
    morph :nothing

    # dunno why this doesn't work. The HTML stops after the first `<col>` element, wtf?!
    # morph "#products-form", render(partial: "admin/products_v3/table", locals:)
  end

  def producers
    producers = OpenFoodNetwork::Permissions.new(current_user)
      .managed_product_enterprises.is_primary_producer.by_name
    producers.map { |p| [p.name, p.id] }
  end

  def categories
    Spree::Taxon.order(:name).map { |c| [c.name, c.id] }
  end

  def fetch_products
    product_query = OpenFoodNetwork::Permissions.new(current_user)
      .editable_products.merge(product_scope).ransack(ransack_query).result(distinct: true)
    @pagy, @products = pagy(product_query.order(:name), items: @per_page, page: @page,
                                                        size: [1, 2, 2, 1])
  end

  def product_scope
    scope = if current_user.has_spree_role?("admin") || current_user.enterprises.present?
              Spree::Product
            else
              Spree::Product.active
            end

    scope.includes(product_query_includes)
  end

  def ransack_query
    query = {}
    query.merge!(supplier_id_in: @producer_id) if @producer_id.present?
    if @search_term.present?
      query.merge!(Spree::Variant::SEARCH_KEY => @search_term)
    end
    query.merge!(variants_primary_taxon_id_in: @category_id) if @category_id.present?
    query
  end

  # Optimise by pre-loading required columns
  def product_query_includes
    # TODO: add other fields used in columns? (eg supplier: [:name])
    [
      # variants: [
      #   :default_price,
      #   :stock_locations,
      #   :stock_items,
      #   :variant_overrides
      # ]
    ]
  end

  def current_url
    url = URI(request.original_url)
    url.query = url.query.present? ? "#{url.query}&" : ""
    # add params with _ to avoid conflicts with params from the form
    url.query += "_page=#{@page}"
    url.query += "&_per_page=#{@per_page}"
    url.query += "&_search_term=#{@search_term}" if @search_term.present?
    url.query += "&_producer_id=#{@producer_id}" if @producer_id.present?
    url.query += "&_category_id=#{@category_id}" if @category_id.present?
    url.to_s
  end

  # Similar to spree/admin/products_controller
  def product_set_from_params
    # Form field names:
    #   '[products][0][id]' (hidden field)
    #   '[products][0][name]'
    #   '[products][0][variants_attributes][0][id]' (hidden field)
    #   '[products][0][variants_attributes][0][display_name]'
    #
    # Resulting in params:
    #     "products" => {
    #       "0" =>  {
    #         "id" => "123"
    #         "name" => "Pommes",
    #         "variants_attributes" => {
    #           "0" => {
    #           "id" => "1234",
    #           "display_name" => "Large box",
    #         }
    #       }
    #     }
    collection_hash = products_bulk_params[:products]
      .transform_values { |product|
        # Convert variants_attributes form hash to an array if present
        product[:variants_attributes] &&= product[:variants_attributes].values
        product
      }.with_indifferent_access
    Sets::ProductSet.new(collection_attributes: collection_hash)
  end

  def products_bulk_params
    params.permit(products: ::PermittedAttributes::Product.attributes)
      .to_h.with_indifferent_access
  end

  def product_finder(id)
    ProductScopeQuery.new(current_user, { id: })
  end

  def current_id_from_element(element)
    element.dataset.current_id
  end
end
