class PeakFlowUtils::ModelInspector
  attr_reader :clazz

  cattr_accessor :models_loaded

  # Yields a model-inspector for each model found in the application.
  def self.model_classes
    # Make sure all models are loaded.
    load_models

    @scanned = {}
    @yielded = {}
    @skip = ["ActiveRecord::SchemaMigration"]

    ArrayEnumerator.new do |yielder|
      find_subclasses(ActiveRecord::Base) do |model_inspector|
        next if !model_inspector.clazz.name || @skip.include?(model_inspector.clazz.name)

        yielder << model_inspector
      end
    end
  end

  def initialize(clazz)
    @clazz = clazz
  end

  def attributes
    ArrayEnumerator.new do |yielder|
      @clazz.attribute_names.each do |attribute_name|
        yielder << PeakFlowUtils::AttributeService.new(self, attribute_name)
      end
    end
  end

  def paperclip_attachments
    return unless ::Kernel.const_defined?("Paperclip")

    Paperclip::AttachmentRegistry.names_for(@clazz).each do |name|
      yield name
    end
  end

  def money_attributes
    return if !::Kernel.const_defined?("Money") || !@clazz.respond_to?(:monetized_attributes)

    @clazz.monetized_attributes.each do |attribute|
      yield attribute[0].to_s
    end
  end

  def globalize_attributes
    return if !::Kernel.const_defined?("Globalize") || !@clazz.respond_to?(:translated_attribute_names)

    @clazz.translated_attribute_names.each do |attribute|
      yield attribute.to_s
    end
  end

  def snake_name
    clazz.name.gsub("::", "/").split("/").map(&:underscore).join("/")
  end

  def class_key
    "activerecord.models.#{snake_name}"
  end

  def class_key_one
    "#{class_key}.one"
  end

  def class_key_other
    "#{class_key}.other"
  end

  # TODO: Maybe this should yield a ModelInspector::Relationship instead?
  def relationships
    @clazz.reflections.each do |key, reflection|
      yield key, reflection
    end
  end

  def attribute_key(attribute_name)
    "activerecord.attributes.#{snake_name}.#{attribute_name}"
  end

  def to_s
    "<PeakFlowUtils::ModelInspector class-name: \"#{@clazz.name}\">"
  end

  def inspect
    to_s
  end

  def self.find_subclasses(clazz, &blk)
    return if @scanned[clazz.name]

    @scanned[clazz.name] = true

    clazz.subclasses.each do |subclass|
      blk.call ::PeakFlowUtils::ModelInspector.new(subclass)
      find_subclasses(subclass, &blk)
    end
  end

  # Preloads all models for Rails app and all engines (if they aren't loaded, then they cant be inspected).
  def self.load_models
    return false if PeakFlowUtils::ModelInspector.models_loaded

    PeakFlowUtils::ModelInspector.models_loaded = true

    load_models_for(Rails.root)
    engines.each do |engine|
      load_models_for(engine.root)
    end

    true
  end

  def self.engines
    ::Rails::Engine.subclasses.map(&:instance)
  end

  # Loads models for the given app-directory (Rails-root or engine).
  def self.load_models_for(root)
    Dir.glob("#{root}/app/models/**/*.rb").sort.each do |model_path|
      require model_path
    rescue StandardError => e
      warn "Could not load model in #{model_path}"
      warn e.inspect
      warn e.backtrace
    end
  end
end