# frozen_string_literal: true

require "signalize/struct"

class Bridgetown::Signals < Signalize::Struct
  alias_method :__prev_to_h, :to_h
  include Enumerable
  alias_method :to_h, :__prev_to_h

  def self.signal_accessor(...)
    super

    return unless members.include?(:members)

    # So…we need to support site data that could have a `members` key. This is how we do it:
    # by conditionally providing the value the gem class expects when it's gem code, otherwise we
    # provide the `members` signal value.
    define_method :members do
      if caller_locations(1..1).first.path.end_with?("/lib/signalize/struct.rb")
        self.class.members
      else
        members_signal.value
      end
    end
  end

  def each(...)
    to_h.each(...)
  end

  def key?(key)
    self.class.members.include?(key.to_sym)
  end

  def [](key)
    return unless key?(key)

    send(key)
  end

  def []=(key, value)
    unless key?(key)
      if instance_of?(Bridgetown::Signals)
        raise Bridgetown::Errors::FatalException,
              "You must use a unique subclass of `Bridgetown::Signals' before adding new members"
      end

      self.class.signal_accessor(key)
    end

    send(:"#{key}=", value)
  end

  def method_missing(key, *value, &block) # rubocop:disable Style/MissingRespondToMissing
    return nil if value.empty? && block.nil?

    key = key.to_s
    return self[key.chop] = value[0] if key.end_with?("=")

    super(key.to_sym)
  end

  # You can access signals and mutate objects (aka push to an array, change a hash value), and by
  # making those changes with a block, this method will track which signals were accessed and resave
  # them with duplicated objects thereby triggering new dependent effects or subscriptions.
  def batch_mutations
    ef = Signalize.effect { yield self }
    node = ef.receiver._sources
    deps = []
    while node
      deps << node._source
      node = node._next_source
    end

    ef.() # dispose

    Signalize.batch do
      self.class.members.each do |member_name|
        matching_dep = deps.find { _1 == send(:"#{member_name}_signal") }
        next unless matching_dep

        Signalize.batch do
          new_value = matching_dep.value.dup
          matching_dep.value = nil
          matching_dep.value = new_value
        end
      end
    end

    nil
  end
end
