module Searchkick
  class RecordData
    TYPE_KEYS = ["type", :type]

    attr_reader :index, :record

    def initialize(index, record)
      @index = index
      @record = record
    end

    def index_data
      data = record_data
      data[:data] = search_data
      {index: data}
    end

    def update_data(method_name)
      data = record_data
      data[:data] = {doc: search_data(method_name)}
      {update: data}
    end

    def delete_data
      {delete: record_data}
    end

    def search_id
      id = record.respond_to?(:search_document_id) ? record.search_document_id : record.id
      id.is_a?(Numeric) ? id : id.to_s
    end

    def document_type(ignore_type = false)
      index.klass_document_type(record.class, ignore_type)
    end

    def record_data
      data = {
        _index: index.name,
        _id: search_id
      }
      data[:_type] = document_type if Searchkick.server_below7?
      data[:routing] = record.search_routing if record.respond_to?(:search_routing)
      data
    end

    private

    def search_data(method_name = nil)
      partial_reindex = !method_name.nil?

      source = record.send(method_name || :search_data)

      # conversions
      index.conversions_fields.each do |conversions_field|
        if source[conversions_field]
          source[conversions_field] = source[conversions_field].map { |k, v| {query: k, count: v} }
        end
      end

      # hack to prevent generator field doesn't exist error
      if !partial_reindex
        index.suggest_fields.each do |field|
          if !source.key?(field) && !source.key?(field.to_sym)
            source[field] = nil
          end
        end
      end

      # locations
      index.locations_fields.each do |field|
        if source[field]
          if !source[field].is_a?(Hash) && (source[field].first.is_a?(Array) || source[field].first.is_a?(Hash))
            # multiple locations
            source[field] = source[field].map { |a| location_value(a) }
          else
            source[field] = location_value(source[field])
          end
        end
      end

      if index.options[:inheritance]
        if !TYPE_KEYS.any? { |tk| source.key?(tk) }
          source[:type] = document_type(true)
        end
      end

      cast_big_decimal(source)

      source
    end

    def location_value(value)
      if value.is_a?(Array)
        value.map(&:to_f).reverse
      elsif value.is_a?(Hash)
        {lat: value[:lat].to_f, lon: value[:lon].to_f}
      else
        value
      end
    end

    # change all BigDecimal values to floats due to
    # https://github.com/rails/rails/issues/6033
    # possible loss of precision :/
    def cast_big_decimal(obj)
      case obj
      when BigDecimal
        obj.to_f
      when Hash
        obj.each do |k, v|
          # performance
          if v.is_a?(BigDecimal)
            obj[k] = v.to_f
          elsif v.is_a?(Enumerable)
            obj[k] = cast_big_decimal(v)
          end
        end
      when Enumerable
        obj.map do |v|
          cast_big_decimal(v)
        end
      else
        obj
      end
    end
  end
end