# frozen_string_literal: true

require 'dry/effects/provider'
require 'dry/effects/instructions/raise'

module Dry
  module Effects
    module Providers
      class Resolve < Provider[:resolve]
        def self.handle_method(*, as: Undefined, **)
          Undefined.default(as, :provide)
        end

        include Dry::Equalizer(:static, :parent, :dynamic)

        Locate = Effect.new(type: :resolve, name: :locate)

        param :static, default: -> { EMPTY_HASH }

        attr_reader :parent

        attr_reader :dynamic

        def initialize(*)
          super
          @dynamic = EMPTY_HASH
        end

        def resolve(key)
          if parent&.key?(key)
            parent.resolve(key)
          elsif dynamic.key?(key)
            dynamic[key]
          elsif static.key?(key)
            static[key]
          else
            Instructions.Raise(Errors::ResolutionError.new(key))
          end
        end

        def locate
          self
        end

        def call(stack, dynamic = EMPTY_HASH, options = EMPTY_HASH)
          @dynamic = dynamic

          if options.fetch(:overridable, false)
            @parent = ::Dry::Effects.yield(Locate) { nil }
          else
            @parent = nil
          end

          super(stack)
        ensure
          @dynamic = EMPTY_HASH
        end

        def provide?(effect)
          if super
            !effect.name.equal?(:resolve) || key?(effect.payload[0])
          else
            false
          end
        end

        def key?(key)
          static.key?(key) || dynamic.key?(key) || parent&.key?(key)
        end

        def represent
          containers = [represent_container(static), represent_container(dynamic)].compact.join('+')
          "resolve[#{containers.empty? ? 'empty' : containers}]"
        end

        def represent_container(container)
          if container.is_a?(::Hash)
            container.empty? ? nil : 'hash'
          elsif container.is_a?(::Class)
            container.name || container.to_s
          else
            container.to_s
          end
        end
      end
    end
  end
end