# frozen_string_literal: true

module Lite
  module Command

    STATUSES = [
      SUCCESS = "success",
      NOOP    = "noop",
      INVALID = "invalid",
      FAILURE = "failure",
      ERROR   = "error"
    ].freeze
    FAULTS = (STATUSES - [SUCCESS]).freeze

    module Internals
      module Call

        def self.included(base)
          base.extend ClassMethods
          base.class_eval do
            attr_reader :reason, :metadata
          end
        end

        module ClassMethods

          def call(context = {})
            instance = send(:new, context)
            instance.send(:execute)
            instance
          end

          def call!(context = {})
            instance = send(:new, context)
            instance.send(:execute!)
            instance
          end

        end

        def call
          raise NotImplementedError, "call method not defined in #{self.class}"
        end

        def status
          @status || SUCCESS
        end

        def success?
          status == SUCCESS
        end

        def ok?(reason = nil)
          success? || noop?(reason)
        end

        def fault?(reason = nil)
          !success? && reason?(reason)
        end

        FAULTS.each do |f|
          # eg: noop? or failure?("idk")
          define_method(:"#{f}?") do |reason = nil|
            status == f && reason?(reason)
          end
        end

        private

        def reason?(str)
          str.nil? || str == reason
        end

        def fault(object, status, metadata)
          @status   = status
          @metadata = metadata

          down_stream = Lite::Command::FaultStreamer.new(self, object)
          @reason    ||= down_stream.reason
          @metadata  ||= down_stream.metadata
          @caused_by ||= down_stream.caused_by
          @thrown_by ||= down_stream.thrown_by
        end

        FAULTS.each do |f|
          # eg: invalid!("idk") or failure!(fault) or error!("idk", { error_key: "some.error" })
          define_method(:"#{f}!") do |object, metadata = nil|
            fault(object, f, metadata)
            raise Lite::Command::Fault.build(f.capitalize, self, object, dynamic: raise_dynamic_faults?)
          end
        end

        alias fail! failure!

      end
    end

  end
end