# frozen_string_literal: true

require 'dry/plugins/config'
require 'delegate'
require 'forwardable'

# noinspection YARDTagsInspection

module Dry
  module Plugins
    # @abstract A delegator providing Plugin DSL
    class Plugin < SimpleDelegator
      extend Forwardable

      # @overload initialize(registry, name, plugin)
      #   @param registry [Registry]
      #   @param name [#to_s]
      # @overload initialize(registry, plugin)
      #   @param registry [Registry]
      #   @param plugin [Module]
      def initialize(registry, name, plugin = nil)
        @__registry__ = registry

        plugin = name if name.is_a?(Module)
        self.name = name
        self.plugin = plugin if plugin
      end

      # @!method config
      #   @return [Config]
      def_delegators :@__registry__, :config

      # Module with actual plugin data
      # @return [Module]
      def __getobj__
        super do
          __setobj__ load
        end
      end

      alias plugin __getobj__

      # @param plugin [Module]
      def __setobj__(plugin)
        self.name = plugin if plugin
        super(plugin)
      end

      alias plugin= __setobj__

      # @return [Symbol]
      attr_reader :name

      # @param name [#to_s]
      # @return [Symbol]
      def name=(name)
        name = name.to_s
        @name = Inflecto.underscore(Inflecto.demodulize(name)).to_sym unless name.empty?
      end

      # @param host [Module]
      # @param configuration [Proc]
      #
      # @return [<Symbol>]
      def call(host, &configuration)
        load_dependencies(host)
        host.send(:include, plugin)
        if plugin.const_defined?(Plugins.config.class_interface_name)
          host.extend(plugin.const_get(Plugins.config.class_interface_name))
        end
        configure(host, &configuration) if configuration
        host.used_plugins << name
        host
      end

      alias plug call

      # Resolves the plug-in `Module` from the {Registry}
      # @return [Module]
      def load
        @__registry__.resolve(name)
      end

      # Load plugin and plugin dependencies (if declared)
      #
      # @param host [Module]
      def load_dependencies(host)
        return unless plugin.respond_to?(Plugins.config.load_dependencies_method)
        plugin.public_send(Plugins.config.load_dependencies_method, host)
      end

      # Configure the `host` using `configuration`
      # @param host [Module]
      # @param configuration [Proc]
      def configure(host, &configuration)
        if plugin.respond_to?(Plugins.config.configure_method)
          return plugin.public_send(Plugins.config.configure_method, host, &configuration)
        end
        host.module_eval(&configuration)
      end
    end
  end
end