# frozen_string_literal: true

require_relative "configuration"
require_relative "converter"
require_relative "converters/array"
require_relative "converters/boolean"
require_relative "converters/date_time"
require_relative "converters/hash"
require_relative "converters/numeric"
require_relative "converters/range"

module Necromancer
  # Represents the context used to configure various converters
  # and facilitate type conversion
  #
  # @api public
  class Conversions
    DELIMITER = "->"

    # Creates a new conversions map
    #
    # @example
    #   conversion = Necromancer::Conversions.new
    #
    # @api public
    def initialize(configuration = Configuration.new, map = {})
      @configuration = configuration
      @converter_map = map.dup
    end

    # Load converters
    #
    # @api private
    def load
      ArrayConverters.load(self)
      BooleanConverters.load(self)
      DateTimeConverters.load(self)
      HashConverters.load(self)
      NumericConverters.load(self)
      RangeConverters.load(self)
    end

    # Retrieve converter for source and target
    #
    # @param [Object] source
    #   the source of conversion
    #
    # @param [Object] target
    #   the target of conversion
    #
    # @return [Converter]
    #  the converter for the source and target
    #
    # @api public
    def [](source, target)
      key = "#{source}#{DELIMITER}#{target}"
      converter_map[key] ||
        converter_map["object#{DELIMITER}#{target}"] ||
        raise_no_type_conversion_available(key)
    end
    alias fetch []

    # Register a converter
    #
    # @example with simple object
    #   conversions.register NullConverter.new(:array, :array)
    #
    # @example with block
    #   conversions.register do |c|
    #     c.source = :array
    #     c.target = :array
    #     c.convert = -> { |val, options| val }
    #   end
    #
    # @api public
    def register(converter = nil, &block)
      converter ||= Converter.create(&block)
      key = generate_key(converter)
      converter = add_config(converter, @configuration)
      return false if converter_map.key?(key)

      converter_map[key] = converter
      true
    end

    # Export all the conversions as hash
    #
    # @return [Hash[String, String]]
    #
    # @api public
    def to_hash
      converter_map.dup
    end

    protected

    # Fail with conversion error
    #
    # @api private
    def raise_no_type_conversion_available(key)
      raise NoTypeConversionAvailableError, "Conversion '#{key}' unavailable."
    end

    # @api private
    def generate_key(converter)
      parts = []
      parts << (converter.source ? converter.source.to_s : "none")
      parts << (converter.target ? converter.target.to_s : "none")
      parts.join(DELIMITER)
    end

    # Inject config into converter
    #
    # @api private
    def add_config(converter, config)
      converter.instance_exec(:"@config") do |var|
        instance_variable_set(var, config)
      end
      converter
    end

    # Map of type and conversion
    #
    # @return [Hash]
    #
    # @api private
    attr_reader :converter_map
  end # Conversions
end # Necromancer
