# frozen_string_literal: true

require "concurrent/array"
require "concurrent/map"

require_relative "hook_event"

module FiniteMachine
  # A class reponsible for registering callbacks
  class Hooks
    attr_reader :hooks_map

    # Initialize a hooks_map of hooks
    #
    # @example
    #   Hooks.new
    #
    # @api public
    def initialize
      @hooks_map = Concurrent::Map.new do |events_hash, hook_event|
        events_hash.compute_if_absent(hook_event) do
          Concurrent::Map.new do |state_hash, name|
            state_hash.compute_if_absent(name) do
              Concurrent::Array.new
            end
          end
        end
      end
    end

    # Finds all hooks for the event type
    #
    # @param [Symbol] name
    #
    # @example
    #   hooks[HookEvent::Enter][:go] # => [-> { }]
    #
    # @return [Array[Transition]]
    #   the transitions matching event name
    #
    # @api public
    def find(name)
      @hooks_map[name]
    end
    alias [] find

    # Register callback
    #
    # @param [String] hook_event
    # @param [String] name
    # @param [Proc]   callback
    #
    # @example
    #   hooks.register HookEvent::Enter, :green do ... end
    #
    # @example
    #   hooks.register HookEvent::Before, any_state do ... end
    #
    # @return [Hash]
    #
    # @api public
    def register(hook_event, name, callback)
      @hooks_map[hook_event][name] << callback
    end

    # Unregister callback
    #
    # @param [String] hook_event
    # @param [String] name
    # @param [Proc]   callback
    #
    # @example
    #   hooks.unregister HookEvent::Enter, :green do ... end
    #
    # @return [Hash]
    #
    # @api public
    def unregister(hook_event, name, callback)
      @hooks_map[hook_event][name].delete(callback)
    end

    # Check if hooks_map has any elements
    #
    # @return [Boolean]
    #
    # @api public
    def empty?
      @hooks_map.empty?
    end

    # Remove all callbacks
    #
    # @api public
    def clear
      @hooks_map.clear
    end

    # String representation
    #
    # @return [String]
    #
    # @api public
    def to_s
      hash = {}
      @hooks_map.each_pair do |hook_event, nested_hash|
        hash[hook_event] = {}
        nested_hash.each_pair do |name, callbacks|
          hash[hook_event][name] = callbacks
        end
      end
      hash.to_s
    end

    # String representation
    #
    # @return [String]
    #
    # @api public
    def inspect
      "<##{self.class}:0x#{object_id.to_s(16)} @hooks_map=#{self}>"
    end
  end # Hooks
end # FiniteMachine
