module TestProf::FactoryDefault

def config

def config
  @config ||= Configuration.new
end

def configure

def configure
  yield config
end

def disable!

def disable!
  was_enabled = @enabled
  @enabled = false
  return unless block_given?
  yield
ensure
  @enabled = was_enabled
end

def enable!

def enable!
  was_enabled = @enabled
  @enabled = true
  return unless block_given?
  yield
ensure
  @enabled = was_enabled
end

def enabled?

def enabled?
  @enabled
end

def get(name, traits = nil, overrides = nil, skip_stats: false)

def get(name, traits = nil, overrides = nil, skip_stats: false)
  return unless enabled?
  record = store[name]
  return unless record
  if traits && (trait_key = record[:traits][traits])
    name = trait_key
    record = store[name]
    traits = nil
  end
  stats[name][:miss] += 1 unless skip_stats
  if traits && !traits.empty? && record[:preserve_traits]
    return
  end
  object = record[:object]
  if overrides && !overrides.empty? && record[:preserve_attributes]
    overrides.each do |name, value|
      return unless object.respond_to?(name) # rubocop:disable Lint/NonLocalExitFromIterator
      return if object.public_send(name) != value # rubocop:disable Lint/NonLocalExitFromIterator
    end
  end
  unless skip_stats
    stats[name][:miss] -= 1
    stats[name][:hit] += 1
  end
  if record[:context] && (record[:context] != :example)
    object.refind
  else
    object
  end
end

def init

def init
  FactoryBotPatch.patch
  FabricationPatch.patch
  @profiler = config.profiling_enabled? ? Profiler.new : NoopProfiler.new
  @enabled = ENV["FACTORY_DEFAULT_DISABLED"] != "1"
  @stats = {}
end

def preserve_attributes=(val)

def preserve_attributes=(val)
  config.preserve_attributes = val
end

def preserve_traits=(val)

TODO(v2): drop
def preserve_traits=(val)
  config.preserve_traits = val
end

def print_report

def print_report
  profiler.print_report
  return unless config.report_stats || config.report_summary
  if stats.empty?
    log :info, "FactoryDefault has not been used"
    return
  end
  msgs = []
  if config.report_stats
    msgs <<
      <<~MSG
        FactoryDefault usage stats:
      MSG
    first_column = stats.keys.map(&:size).max + 2
    msgs << format(
      "%#{first_column}s  %9s  %9s",
      "factory", "hit", "miss"
    )
    msgs << ""
  end
  total_hit = 0
  total_miss = 0
  stats.to_a.sort_by { |(_, v)| -v[:hit] }.each do |(key, record_stats)|
    total_hit += record_stats[:hit]
    total_miss += record_stats[:miss]
    if config.report_stats
      msgs << format(
        "%#{first_column}s  %9d  %9d",
        key, record_stats[:hit], record_stats[:miss]
      )
    end
  end
  msgs << "" if config.report_stats
  msgs <<
    <<~MSG
      FactoryDefault summary: hit=#{total_hit} miss=#{total_miss}
    MSG
  log :info, msgs.join("\n")
end

def refind

def refind
  self
end

def register(name, obj, **options)

def register(name, obj, **options)
  # Name with traits
  if name.is_a?(Array)
    register_traited_record(*name, obj, **options)
  else
    register_default_record(name, obj, **options)
  end
  obj
end

def register_default_record(name, obj, **options)

def register_default_record(name, obj, **options)
  store[name] = {object: obj, traits: {}, context: current_context, **options}
  stats[name] ||= {hit: 0, miss: 0}
end

def register_traited_record(name, *traits, obj, **options)

def register_traited_record(name, *traits, obj, **options)
  name_with_traits = "#{name}[#{traits.join(",")}]"
  register_default_record(name_with_traits, obj, **options)
  register_default_record(name, obj, **options) unless store[name]
  # Add reference to the traited default to the original default record
  store[name][:traits][traits] = name_with_traits
end

def remove(name)

def remove(name)
  store.delete(name)
end

def reset(context: nil)

def reset(context: nil)
  return store.clear unless context
  store.delete_if do |_name, metadata|
    metadata[:context] == context
  end
end

def store

def store
  Thread.current[:testprof_factory_default_store] ||= {}
end

def to_override_key

def to_override_key
  "<#{self.class.name}::$id$#{object_id}$di$>"
end

def to_override_key

def to_override_key
  "<#{self.class.name}\#$id$#{public_send(self.class.primary_key)}$di$>"
end

def to_override_key

def to_override_key
  inspect
end