module Neo4j::ActiveNode
  # A reflection contains information about an association.
  # They are often used in connection with form builders to determine associated classes.
  # This module contains methods related to the creation and retrieval of reflections.
  module Reflection
    extend ActiveSupport::Concern

    included do
      class_attribute :reflections
      self.reflections = {}
    end

    # Adds methods to the class related to creating and retrieving reflections.
    module ClassMethods
      # @param macro [Symbol] the association type, :has_many or :has_one
      # @param name [Symbol] the association name
      # @param association_object [Neo4j::ActiveNode::HasN::Association] the association object created in the course of creating this reflection
      def create_reflection(macro, name, association_object)
        self.reflections = self.reflections.merge(name => AssociationReflection.new(macro, name, association_object))
      end

      private :create_reflection
      # @param [Symbol] an association declared on the model
      # @return [Neo4j::ActiveNode::Reflection::AssociationReflection] of the given association
      def reflect_on_association(association)
        reflections[association.to_sym]
      end

      # Returns an array containing one reflection for each association declared in the model.
      def reflect_on_all_associations(macro = nil)
        association_reflections = reflections.values
        macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
      end
    end

    # The actual reflection object that contains information about the given association.
    # These should never need to be created manually, they will always be created by declaring a :has_many or :has_one association on a model.
    class AssociationReflection
      # The name of the association
      attr_reader :name

      # The type of association
      attr_reader :macro

      # The association object referenced by this reflection
      attr_reader :association

      def initialize(macro, name, association)
        @macro        = macro
        @name         = name
        @association  = association
      end

      # Returns the target model
      def klass
        @klass ||= class_name.constantize
      end

      # Returns the name of the target model
      def class_name
        @class_name ||= association.target_class.name
      end

      def rel_klass
        @rel_klass ||= rel_class_name.constantize
      end

      def rel_class_name
        @rel_class_name ||= association.relationship_class.name.to_s
      end

      def type
        @type ||= association.relationship_type
      end

      def collection?
        macro == :has_many
      end

      def validate?
        true
      end
    end
  end
end