# frozen_string_literal: true

module Micro
  module Observers

    class Set
      def self.for(subject)
        new(subject)
      end

      def initialize(subject, subscribers: nil)
        @subject = subject
        @subject_changed = false
        @subscribers = Subscribers.new(subscribers)
      end

      def count; @subscribers.count; end
      def none?; @subscribers.none?; end
      def some?; !none?; end

      def subject_changed?
        @subject_changed
      end

      INVALID_BOOLEAN_MSG = 'expected a boolean (true, false)'.freeze

      def subject_changed(state)
        return @subject_changed = state if state == true || state == false

        raise ArgumentError, INVALID_BOOLEAN_MSG
      end

      def subject_changed!
        subject_changed(true)
      end

      def include?(observer)
        @subscribers.include?(observer)
      end
      alias included? include?

      def attach(*args); @subscribers.attach(args) and self; end
      def detach(*args); @subscribers.detach(args) and self; end

      def on(options = Utils::EMPTY_HASH); @subscribers.on(options) and self; end
      def once(options = Utils::EMPTY_HASH); @subscribers.once(options) and self; end

      def off(*args)
        @subscribers.off(args) and self
      end

      def notify(*events, data: nil)
        broadcast_if_subject_changed(Event::Names.fetch(events), data)
      end

      def notify!(*events, data: nil)
        broadcast(Event::Names.fetch(events), data)
      end

      CALL_EVENT = [:call].freeze

      def call(*events, data: nil)
        broadcast_if_subject_changed(Event::Names[events, default: CALL_EVENT], data)
      end

      def call!(*events, data: nil)
        broadcast(Event::Names[events, default: CALL_EVENT], data)
      end

      def inspect
        subs = @subscribers.to_inspect

        '#<%s @subject=%s @subject_changed=%p @subscribers=%p>' % [self.class, @subject, @subject_changed, subs]
      end

      private

        def broadcast_if_subject_changed(event_names, data = nil)
          return self if none? || !subject_changed?

          broadcast(event_names, data)

          subject_changed(false)

          self
        end

        def broadcast(event_names, data)
          return self if none?

          Broadcast.call(@subscribers, @subject, data, event_names)

          self
        end

      private_constant :INVALID_BOOLEAN_MSG, :CALL_EVENT
    end

  end
end