module Appfuel
  module Repository
    # A mapping dsl that allows the collection of database columns to domain
    # attributes. The reason this dsl is separated from the DbEntityMapper
    # is due to the fact that we use method_missing for collecting column names
    # and don't want any incorrect method_missing calls to be confused when
    # collecting mapped values vs when defining them.
    class MappingDsl
      attr_reader :domain_name, :entries, :map_class, :container_name,
                  :storage_key, :storage_type

      # 1) mapping 'feature.domain', to: :db, model: 'foo.db.bar', contextual_key: false do
      #
      #    end
      #
      #    mapping 'feature.domain' web_api: 'web_api.http.model' do
      #     map 'id'
      #     map 'foo', 'bar'
      #     map 'biz', 'baz'
      #    end
      #
      def initialize(domain_name, to:, model:, **opts)
        opts          ||= {}
        @entries        = []
        @domain_name    = domain_name.to_s
        @map_class      = opts[:map_class] || StorageMap
        @container_name = opts[:container] || Appfuel.default_app_name

        @contextual_key = true
        if opts.key?(:contextual_key) && opts[:contextual_key] == false
          @contextual_key = false
        end
        @storage_type = to
        @storage_key  = translate_storage_key(to, model)

        fail "entity name can not be empty" if @domain_name.empty?
      end

      def create_storage_map
        map_class.new(
          domain_name:  domain_name,
          container:    container_name,
          storage_type: storage_type,
          storage_key:  storage_key,
          entries:      entries
        )
      end

      def map(name, domain_attr = nil, opts = {})
        if domain_attr.is_a?(Hash)
          opts = domain_attr
          domain_attr = nil
        end

        domain_attr = name if domain_attr.nil?

        data = opts.merge({
          domain_attr: domain_attr,
          storage_attr: name,
        })

        entries << data
      end

      def contextual_key?
        @contextual_key
      end

      # global.user
      # global.storage.db.user
      # membership.user
      # features.membership.{type}.user
      def translate_storage_key(type, partial_key)
        return partial_key unless contextual_key?
        fail "#{type} model key can not be empty" if partial_key.empty?

        # take the feature or domain root unless its global
        domain_top, _domain_base = domain_name.split('.')
        top, *parts = partial_key.split('.')

        if top == 'global'
          root = top
          base = parts.join('.')
        else
          root = "features.#{domain_top}"
          base = top
        end

        "#{root}.#{type}.#{base}"
      end
    end
  end
end