module ActiveType

  module RecordExtension

    module Inheritance

      extend ActiveSupport::Concern

      included do
        class_attribute :extended_record_base_class
      end

      def self.add_foreign_key_option(extended_record_base_class, scope = nil, options = {})
        if scope.is_a?(Hash)
          options = scope
          scope = nil
        end
        unless options[:foreign_key] || options[:as]
          options = options.merge(foreign_key: extended_record_base_class.name.foreign_key)
        end
        if ActiveRecord::VERSION::MAJOR > 3
          [options, scope]
        else
          [options]
        end
      end

      module ClassMethods

        def model_name
          @_model_name ||= begin
            if name
              # Namespace detection copied from ActiveModel::Naming
              namespace = module_ancestors.detect do |n|
                n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
              end
              # We create a Name object, with the sti class name, but self as the @klass reference
              # This way lookup_ancestors is invoked on the right class instead of the extended_record_base_class
              dup_model_name = ActiveModel::Name.new(self, namespace, sti_name)
              key = name.underscore.to_sym
              # We set the `i18n_key` to lookup on the derived class key
              # We keep the others the same to preserve parameter and route names
              dup_model_name.instance_variable_set(:@i18n_key, key)
              dup_model_name
            else # name is nil for the anonymous intermediate class
              extended_record_base_class.model_name
            end
          end
        end

        def module_ancestors
          if extended_record_base_class.respond_to?(:module_parents)
            extended_record_base_class.module_parents
          else
            extended_record_base_class.parents
          end
        end

        def sti_name
          extended_record_base_class.sti_name
        end

        def descends_from_active_record?
          extended_record_base_class.descends_from_active_record?
        end

        def has_many(name, scope=nil, *args, &extension)
          new_args, new_scope = Inheritance.add_foreign_key_option(extended_record_base_class, scope, *args)
          if ActiveRecord::VERSION::MAJOR <= 3 || new_scope.nil?
            super(name, **new_args, &extension)
          else
            super(name, new_scope, **new_args, &extension)
          end
        end

        def has_one(name, scope=nil, *args, &extension)
          new_args, new_scope = Inheritance.add_foreign_key_option(extended_record_base_class, scope, *args)
          if ActiveRecord::VERSION::MAJOR <= 3 || new_scope.nil?
            super(name, **new_args, &extension)
          else
            super(name, new_scope, **new_args, &extension)
          end
        end

        private

        if ActiveRecord::VERSION::MAJOR < 5

          def find_sti_class(type_name)
            sti_class = super

            # Consider this class hierarchy
            # class Parent < ActiveRecord::Base; end
            # class Child < Parent; end
            # class ExtendedParent < ActiveType::Record[Parent]; end
            # class ExtendedChild < ActiveType::Record[Child]; end
            if self < sti_class
              # i.e. ExtendendChild.find(child.id)
              # => self = ExtendedChild; sti_class = Child
              # instantiate as ExtendedChild
              self
            elsif sti_class < extended_record_base_class
              # i.e. ExtendedParent.find(child.id)
              # => sti_class = Child; self = ExtendedParent; extended_record_base_class = Parent
              # There is no really good solution here, since we cannot instantiate as both ExtendedParent
              # and Child. We opt to instantiate as ExtendedParent, since the other option can be
              # achieved by using Parent.find(child.id)
              self
            else
              sti_class
            end
          end

        else

          # Rails 5 find_sti_class does a sanity check for proper inheritance that fails for
          # our usecase
          # copied from activerecord/lib/active_record/inheritance.rb
          def find_sti_class(type_name)
            type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
            subclass = begin
              if store_full_sti_class
                type_name.constantize
              else
                compute_type(type_name)
              end
            rescue NameError
              raise ActiveRecord::SubclassNotFound,
                "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
                "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
                "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
                "or overwrite #{name}.inheritance_column to use another column for that information."
            end
            #### our code starts here
            # Consider this class hierarchy
            # class Parent < ActiveRecord::Base; end
            # class Child < Parent; end
            # class ExtendedParent < ActiveType::Record[Parent]; end
            # class ExtendedChild < ActiveType::Record[Child]; end
            if self < subclass
              # i.e. ExtendendChild.find(child.id)
              # => self = ExtendedChild; subclass = Child
              # instantiate as ExtendedChild
              subclass = self
            elsif subclass < extended_record_base_class
              # i.e. ExtendedParent.find(child.id)
              # => subclass = Child; self = ExtendedParent; extended_record_base_class = Parent
              # There is no really good solution here, since we cannot instantiate as both ExtendedParent
              # and Child. We opt to instantiate as ExtendedParent, since the other option can be
              # achieved by using Parent.find(child.id)
              subclass = self
            end
            #### our code ends here
            unless subclass == self || descendants.include?(subclass)
              raise ActiveRecord::SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
            end
            subclass
          end

        end

      end

    end

  end

end
