require "./item"

module Spec
  # Each example (`it`) in a spec suite.
  class Example
    include Item

    # :nodoc:
    getter block : (->) | Nil

    # :nodoc:
    def initialize(@parent : Context, @description : String,
                   @file : String, @line : Int32, @end_line : Int32,
                   @focus : Bool, tags,
                   @block : (->) | Nil)
      initialize_tags(tags)
    end

    # :nodoc:
    def run
      @parent.cli.root_context.check_nesting_spec(file, line) do
        @parent.cli.formatters.each(&.before_example(description))

        if @parent.cli.dry_run?
          @parent.report(:success, description, file, line)
          return
        end

        unless block = @block
          @parent.report(:pending, description, file, line)
          return
        end

        non_nil_block = block
        start = Time.instant

        ran = @parent.run_around_each_hooks(Example::Procsy.new(self) { internal_run(start, non_nil_block) })
        ran || internal_run(start, non_nil_block)

        # We do this to give a chance for signals (like CTRL+C) to be handled,
        # which currently are only handled when there's a fiber switch
        # (IO stuff, sleep, etc.). Without it the user might wait more than needed
        # after pressing CTRL+C to quit the tests.
        Fiber.yield
      end
    end

    private def internal_run(start, block)
      @parent.run_before_each_hooks
      block.call
      @parent.report(:success, description, file, line, start.elapsed)
    rescue ex : Spec::AssertionFailed
      @parent.report(:fail, description, file, line, start.elapsed, ex)
      @parent.cli.abort! if @parent.cli.fail_fast?
    rescue ex : Spec::ExamplePending
      @parent.report(:pending, description, file, line, start.elapsed)
    rescue ex
      @parent.report(:error, description, file, line, start.elapsed, ex)
      @parent.cli.abort! if @parent.cli.fail_fast?
    ensure
      @parent.run_after_each_hooks
    end
  end
end

require "./example/procsy"
