class Eco::API::Session::Config::Workflow

def after(key = nil, &block)

def after(key = nil, &block)
  raise "A block should be given." unless block
  if !key
    @after.push(block)
  else
    stage(key).after(&block)
  end
  self
end

def before(key = nil, &block)

def before(key = nil, &block)
  raise "A block should be given." unless block
  if !key
    @before.push(block)
  else
    stage(key).before(&block)
  end
  self
end

def existing_stages

def existing_stages
  # sort defined stages by stage order

  sorted_keys = self.class.stages & @stages.keys
  sorted_keys.map {|k| stage(k)}
end

def for(key = nil)

def for(key = nil)
  raise "A block should be given." unless block_given?
  if !key
    yield(self)
  else
    stage(key).for(&Proc.new)
  end
  self
end

def get_model(key)

def get_model(key)
  raise "Expected Symbol. Given: #{key.class}" unless key.is_a?(Symbol)
  workflow = @workflow[key]
  raise "Expected Array. Given #{model.class}" unless !workflow || workflow.is_a?(Hash)
  class_name      = titleize(key.to_s)
  full_class_name = "#{self}::#{class_name}"
  if target_class = resolve_class(full_class_name, exception: false)
  else
    target_class  = Class.new(Eco::API::Session::Config::Workflow) do
      @workflow = workflow
    end
    self.const_set class_name, target_class
  end
  target_class
end

def initialize(name = nil, _parent: self, config:)

def initialize(name = nil, _parent: self, config:)
  @config   = config
  @name     = name
  @stages   = {}
  @_parent  = _parent
  @pending  = true
  # moments

  @on   = nil
  @before   = []
  @after    = []
end

def on(key = nil, &block)

def on(key = nil, &block)
  raise "A block should be given." unless block
  if !key
    @on = block
  else
    stage(key).on(&block)
  end
  self
end

def path

def path
  return name if root?
  "#{@_parent.path}:#{name}"
end

def pending?

def pending?
  @pending
end

def ready?

def ready?
  !!@on
end

def ready_stages

def ready_stages
  exiting_stages.select {|s| s.ready?}
end

def root?

def root?
  @_parent == self
end

def run(key = nil, io:, &block)

def run(key = nil, io:, &block)
  if key
    io = stage(key).run(io: io, &block)
  elsif pending?
    @before.each {|c| io = c.call(self, io)}
    unless skip?
      io.session.logger.debug("(Workflow: #{path}) running now")
      if block
        io = block.call(self, io)
      else
        existing_stages.each {|stg| io = stg.run(io: io)}
        unless ready?
          msg = "(Workflow: #{path}) 'on' callback is not defined, nor block given"
          io.session.logger.debug(msg)
        end
        io = @on.call(self, io) if ready?
      end
      @pending = false
    end
    @after.each  {|c| io = c.call(self, io)}
  end
  io
end

def skip!

def skip!
  @skip    = true
  @pending = false
end

def skip?

def skip?
  return @skip if instance_variable_defined?(:@skip)
  return false if root?
  @_parent.skip?
end

def stage(key)

def stage(key)
  self.class.validate_stage(key)
  @stages[key] ||= self.class.get_model(key).new(key, _parent: self, config: config)
end

def stages

def stages
  @stages ||= (@workflow || {}).keys
end

def stages_ready?

def stages_ready?
  existing_stages.all? {|s| s.ready?}
end

def titleize(key)

def titleize(key)
  str_name = key.to_s.strip.split(/[\-\_ ]/i).compact.map do |str|
    str.slice(0).upcase + str.slice(1..-1).downcase
  end.join("")
end

def validate_stage(stage)

def validate_stage(stage)
  "Unknown Workflow stage '#{stage}'. Should be any of #{stages}" unless stages.include?(stage)
end