# frozen_string_literal: true

require 'plumb/composable'

module Plumb
  class Pipeline
    include Composable

    class AroundStep
      include Composable

      def initialize(step, block)
        @step = step
        @block = block
      end

      def call(result)
        @block.call(@step, result)
      end
    end

    class << self
      def around_blocks
        @around_blocks ||= []
      end

      def around(callable = nil, &block)
        around_blocks << (callable || block)
        self
      end

      def inherited(subclass)
        around_blocks.each { |block| subclass.around(block) }
        super
      end
    end

    attr_reader :children

    def initialize(type: Types::Any, freeze_after: true, &setup)
      @type = type
      @children = [type].freeze
      @around_blocks = self.class.around_blocks.dup

      configure(&setup) if block_given?
      freeze if freeze_after
    end

    def call(result)
      @type.call(result)
    end

    def step(callable = nil, &block)
      callable ||= block
      unless is_a_step?(callable)
        raise ArgumentError,
              "#step expects an interface #call(Result) Result, but got #{callable.inspect}"
      end

      callable = prepare_step(callable)
      callable = @around_blocks.reverse.reduce(callable) { |cl, bl| AroundStep.new(cl, bl) } if @around_blocks.any?
      @type >>= callable
      self
    end

    def around(callable = nil, &block)
      @around_blocks << (callable || block)
      self
    end

    private

    def configure(&setup)
      case setup.arity
      when 1
        setup.call(self)
      when 0
        instance_eval(&setup)
      else
        raise ArgumentError, 'setup block must have arity of 0 or 1'
      end
    end

    def is_a_step?(callable)
      callable.respond_to?(:call)
    end

    def prepare_step(callable) = callable
  end
end
