module Hyperstack
  module State
    module Observable
      def self.bulk_update(&block)
        Internal::State::Mapper.bulk_update(&block)
      end

      def self.included(base)
        base.include Internal::AutoUnmount
        %i[singleton_method method].each do |kind|
          base.send(:"define_#{kind}", :receives) do |*args, &block|
            Internal::Receiver.mount(self, *args, &block)
          end
          base.send(:"define_#{kind}", :observe) do |&block|
            result = block.call if block
            Internal::State::Mapper.observed! self
            result
          end
          base.send(:"define_#{kind}", :mutate) do |*_args, &block|
            # any args will be ignored thus allowing us to say `mutate @foo = 123, @bar[:x] = 7` etc
            result = block.call if block
            Internal::State::Mapper.mutated! self
            result
          end
          if RUBY_ENGINE == 'opal'
            base.send(:"define_#{kind}", :set) do |var|
              if Hyperstack.naming_convention == :prefix_state
                var = "_#{var}" if var !~ /^_/
              elsif Hyperstack.naming_convention != :none && var !~ /^[a-z]/
                dont_mutate = true
              end
              lambda do |val|
                `self[#{var}] = #{val}`
                mutate unless dont_mutate
              end
            end
          else
            base.send(:"define_#{kind}", :set) do |var|
              if Hyperstack.naming_convention == :prefix_state
                var = "_#{var}" if var !~ /^_/
              elsif Hyperstack.naming_convention != :none && var !~ /^[a-z]/
                dont_mutate = true
              end
              lambda do |val|
                instance_variable_set(:"@#{var}", val)
                mutate unless dont_mutate
              end
            end
          end
          base.singleton_class.send(:"define_#{kind}", :observer) do |name, &block|
            define_method(name) do |*args|
              instance_exec(*args, &block).tap do
                Internal::State::Mapper.observed! self
              end
            end
          end
          base.singleton_class.send(:"define_#{kind}", :mutator) do |name, &block|
            define_method(name) do |*args|
              instance_exec(*args, &block).tap do
                Internal::State::Mapper.mutated! self
              end
            end
          end
          base.singleton_class.send(:"define_#{kind}", :state_reader) do |*names|
            names.each do |name|
              var_name = if Hyperstack.naming_convention == :prefix_state && name !~ /^_/
                           "_#{name}"
                         else
                           name
                         end
              define_method(name) do
                instance_variable_get(:"@#{var_name}").tap { Internal::State::Mapper.observed!(self) }
              end
            end
          end
          base.singleton_class.send(:"define_#{kind}", :state_writer) do |*names|
            names.each do |name|
              var_name = if Hyperstack.naming_convention == :prefix_state && name !~ /^_/
                           "_#{name}"
                         else
                           name
                         end
              define_method(:"#{name}=") do |x|
                instance_variable_set(:"@#{var_name}", x).tap { Internal::State::Mapper.mutated!(self) }
              end
            end
          end
          base.singleton_class.send(:"define_#{kind}", :state_accessor) do |*names|
            state_reader(*names)
            state_writer(*names)
          end
        end
      end
    end
  end
end