require_relative '../commands/context'

module ContentfulMiddleman
  module Mapper
    class Base
      attr_reader :entries

      def initialize(entries, options)
        @entries = entries
        @options = options
        @children = {}
        @created_at_key = using_camel_case? ? :createdAt : :created_at
        @updated_at_key = using_camel_case? ? :updatedAt : :updated_at
        @content_type_key = using_camel_case? ? :contentType : :content_type
      end

      def map(context, entry)
        @children = {
          :queue => [{ :context => context, :entry => entry }],
          :discovered => [entry.id] }
        while !@children[:queue].first.nil? do
          nxt = @children[:queue].pop
          context = nxt[:context]
          entry = nxt[:entry]
          map_entry_full(entry, context)
        end
      end

      private

      def using_camel_case?
        @options.client_options.fetch(:use_camel_case, false)
      end

      def has_multiple_locales?
        @options.cda_query.fetch(:locale, nil) == '*'
      end

      def map_field(context, field_name, field_value)
        if has_multiple_locales?
          processed_locales = {}
          field_value.each do |locale, value|
            processed_locales[locale] = map_value(value, locale.to_s)
          end
          context.set(field_name, processed_locales)
        else
          value_mapping = map_value(field_value)
          context.set(field_name, value_mapping)
        end
      end

      def map_value(value, locale = nil)
        case value
        when Contentful::Asset
          map_asset(value, locale)
        when Contentful::Location
          map_location(value)
        when Contentful::Link
          map_link(value)
        when Contentful::Entry
          map_entry(value)
        when Array
          map_array(value, locale)
        when Hash
          map_hash(value, locale)
        else
          value
        end
      end

      def map_asset_metadata(asset)
        context = Context.new
        context.updated_at = asset.sys[@updated_at_key].iso8601 unless asset.sys[@updated_at_key].nil?
        context.created_at = asset.sys[@created_at_key].iso8601 unless asset.sys[@created_at_key].nil?
        context.id = asset.sys[:id]

        context
      end

      def map_asset(asset, locale = nil)
        context = Context.new
        if locale
          context.title = asset.fields(locale)[:title]
          context.description = asset.fields(locale)[:description]
          context.url = asset.fields(locale)[:file].url unless asset.fields(locale)[:file].nil?
        end

        context.title = asset.title unless context.has?(:title) && !context.title.nil?
        context.description = asset.description unless context.has?(:description) && !context.description.nil?
        context.url = asset.url unless asset.file.nil? || (context.has?(:url) && !context.url.nil?)

        context._meta = map_asset_metadata(asset)

        context
      end

      def map_entry_metadata(entry)
        context = Context.new
        context.content_type_id = entry.sys[@content_type_key].id unless entry.sys[@content_type_key].nil?
        context.updated_at = entry.sys[@updated_at_key].iso8601 unless entry.sys[@updated_at_key].nil?
        context.created_at = entry.sys[@created_at_key].iso8601 unless entry.sys[@created_at_key].nil?
        context.id = entry.sys[:id]

        context
      end

      def map_entry_full(entry, context)
        context.id = entry.id
        context._meta = map_entry_metadata(entry)

        fields = has_multiple_locales? ? entry.fields_with_locales : entry.fields

        # Prevent entries with no values from breaking the import
        fields ||= {}

        fields.each {|k, v| map_field context, k, v}
      end

      def map_entry(entry)
        context = Context.new
        context.id = entry.id
        @children[:discovered].push(entry.id) unless @children[:discovered].include?(entry.id)
        @children[:queue].push({ :context => context, :entry => entry})
        context
      end

      def map_location(location)
        context = Context.new
        context.lat = location.lat
        context.lon = location.lon

        context
      end

      def map_link(link)
        context = Context.new
        context.id = link.id

        context
      end

      def map_array(array, locale = nil)
        array.map {|element| map_value(element, locale)}
      end

      def map_hash(hash, locale = nil)
        return hash.transform_values {|element| map_value(element, locale)} if hash.respond_to?(:transform_values)

        # Support for Ruby versions previous to 2.4
        result = {}
        hash.each do |k, v|
          result[k] = map_value(v, locale)
        end
        result
      end
    end
  end
end
