module Workarea
  module Storefront
    class ProductViewModel < ApplicationViewModel
      def self.wrap(model, options = {})
        if model.is_a?(Enumerable)
          model.map { |m| wrap(m, options) }
        elsif Workarea.config.product_templates.include?(model.template.to_sym)
          view_model_class = "Workarea::Storefront::ProductTemplates::#{model.template.camelize}ViewModel"
          view_model_class.constantize.new(model, options)
        else
          new(model, options)
        end
      end

      def breadcrumbs
        @breadcrumbs ||=
          if options[:via].present?
            Navigation::Breadcrumbs.from_global_id(
              options[:via],
              last: model
            )
          else
            Navigation::Breadcrumbs.new(
              default_category,
              last: model
            )
          end
      end

      def primary_image
        images.primary
      end

      def images
        @images_collection ||=
          Storefront::ProductViewModel::ImageCollection.new(model, options)
      end

      def cache_key
        @cache_key ||= CacheKey.new(
          model,
          options.merge(current_sku: current_sku)
        ).to_s
      end

      #
      # Browsing
      #
      #

      def catalog_id
        options[:catalog_id]
      end

      def browse_link_options
        @browse_link_options ||= options.slice(:via).with_indifferent_access
      end

      # Returns the set of recommendations for the product.
      # The view model it returns behave like {Enumerable}.
      #
      # @return [Workarea::Storefront::DetailPageRecommendationsViewModel]
      #
      def recommendations
        @recommendations ||= DetailPageRecommendationsViewModel.new(model, options)
      end

      #
      # Pricing
      #
      #

      delegate :sell_min_price, :sell_max_price, :on_sale?, :has_prices?, to: :pricing

      def pricing
        @pricing ||= options[:pricing] || Pricing::Collection.new(
          options[:sku].presence || variants.map(&:sku)
        )
      end

      def purchasable?
        has_prices? && model.purchasable? && inventory_purchasable?
      end

      def one_price?
        return false if sell_min_price.nil?
        sell_min_price >= original_min_price
      end

      def show_sell_range?
        return false if sell_min_price.nil? || sell_max_price.nil?
        sell_min_price < sell_max_price
      end

      def show_original_range?
        return false if original_min_price.nil? || original_max_price.nil?
        original_min_price < original_max_price
      end

      def original_min_price
        return nil unless has_prices?

        if pricing.msrp_min_price.present? && pricing.msrp_min_price > sell_min_price
          pricing.msrp_min_price
        else
          pricing.regular_min_price
        end
      end

      def original_max_price
        return nil unless has_prices?

        if pricing.msrp_max_price.present? && pricing.msrp_max_price > sell_max_price
          pricing.msrp_max_price
        else
          pricing.regular_max_price
        end
      end

      #
      # Variants
      #
      #

      def variants
        @variants ||= model.variants.active.select do |variant|
          !!inventory.for_sku(variant.sku).try(:displayable?)
        end
      end

      def current_sku
        options[:sku]
      end

      def current_variant
        variants.detect { |variant| variant.sku == current_sku }
      end

      def sku_options
        @sku_options ||= SkuOptions.new(variants).to_a
      end

      def inventory
        @inventory ||= options[:inventory] ||
          Inventory::Collection.new(model.variants.map(&:sku))
      end

      def inventory_status
        InventoryStatusViewModel.new(inventory.for_sku(current_sku)).message
      end

      def inventory_purchasable?
        current_sku.blank? || inventory.for_sku(current_sku).purchasable?
      end

      def default_category
        @default_category ||= Categorization.new(model).default_model
      end

      #
      # Detail
      #
      #

      def browser_title
        if model.browser_title.present?
          model.browser_title
        else
          model.name
        end
      end

      def meta_description
        if model.meta_description.present?
          model.meta_description
        else
          model.description
        end
      end

      #
      # Fulfillment
      #
      #

      def fulfillment_skus
        @fulfillment_skus ||=
          Fulfillment::Sku.find_or_initialize_all(variants.map(&:sku))
      end

      def requires_shipping?
        if current_variant.present?
          fulfillment_skus
            .detect { |sku| sku.id == current_variant.sku }
            .shipping?
        else
          fulfillment_skus.any?(&:shipping?)
        end
      end
    end
  end
end