module Graphiti
  module Util
    class SerializerAttribute
      def initialize(name, attr, resource, serializer, extra)
        @name = name
        @attr = attr
        @resource = resource
        @serializer = serializer
        @extra = extra
      end

      def apply
        return unless readable?

        if @name == :id
          @serializer.id(&@attr[:proc]) if @attr[:proc]
        elsif @attr[:proc]
          @serializer.send(_method, @name, serializer_options, &proc)
        else
          if @serializer.attribute_blocks[@name].nil?
            @serializer.send(_method, @name, serializer_options, &proc)
          else
            inner = @serializer.attribute_blocks.delete(@name)
            wrapped = wrap_proc(inner)
            @serializer.send(_method, @name, serializer_options, &wrapped)
          end
        end
      end

      private

      def _method
        extra? ? :extra_attribute : :attribute
      end

      def extra?
        !!@extra
      end

      def readable?
        !!@attr[:readable]
      end

      def guard
        _method = @attr[:readable]
        instance = @resource.new
        -> { instance.instance_eval(&_method) }
      end

      def guard?
        @attr[:readable] != true
      end

      def serializer_options
        {}.tap do |opts|
          opts[:if] = guard if guard?
        end
      end

      def default_proc
        _name = @name
        _resource = @resource.new
        ->(_) {
          _resource.typecast(_name, @object.send(_name), :readable)
        }
      end

      def wrap_proc(inner)
        _resource = @resource.new
        _name = @name
        ->(serializer_instance = nil) {
           value = serializer_instance.instance_eval(&inner)
          _resource.typecast(_name, value, :readable)
        }
      end

      def proc
        @attr[:proc] ? wrap_proc(@attr[:proc]) : default_proc
      end
    end

    class SerializerAttributes
      def initialize(resource, attributes, extra = false)
        @resource = resource
        @serializer = resource.serializer
        @attributes = attributes
        @extra = extra
      end

      def apply
        @attributes.each_pair do |name, attr|
          SerializerAttribute
            .new(name, attr, @resource, @serializer, @extra).apply
        end
      end
    end
  end
end