# frozen_string_literal: true

require "active_model"

module StoreModel
  module Types
    # Implements ActiveModel::Type::Value type for handling an instance of StoreModel::Model
    class OnePolymorphic < OneBase
      include PolymorphicHelper

      # Initializes type for model class
      #
      # @param model_wrapper [Proc] class to handle
      #
      # @return [StoreModel::Types::OnePolymorphic ]
      def initialize(model_wrapper)
        @model_wrapper = model_wrapper
      end

      # Returns type
      #
      # @return [Symbol]
      def type
        :polymorphic
      end

      # Casts +value+ from DB or user to StoreModel::Model instance
      #
      # @param value [Object] a value to cast
      #
      # @return StoreModel::Model
      def cast_value(value)
        case value
        when String then decode_and_initialize(value)
        when Hash then extract_model_klass(value).new(value)
        when nil then value
        else
          raise_cast_error(value) unless value.class.ancestors.include?(StoreModel::Model)

          value
        end
      rescue ActiveModel::UnknownAttributeError => e
        handle_unknown_attribute(value, e)
      end

      # Casts a value from the ruby type to a type that the database knows how
      # to understand.
      #
      # @param value [Object] value to serialize
      #
      # @return [String] serialized value
      def serialize(value)
        case value
        when Hash
          ActiveSupport::JSON.encode(value)
        else
          return ActiveSupport::JSON.encode(value) if implements_model?(value.class)

          super
        end
      end

      protected

      # Check if block returns an appropriate class and raise cast error if not
      #
      # @param value [Object] raw data
      #
      # @return [Class] which implements StoreModel::Model
      def extract_model_klass(value)
        model_klass = @model_wrapper.call(value)

        raise_extract_wrapper_error(model_klass) unless implements_model?(model_klass)

        model_klass
      end

      def raise_cast_error(value)
        raise StoreModel::Types::CastError,
              "failed casting #{value.inspect}, only String, " \
              "Hash or instances which implement StoreModel::Model are allowed"
      end

      def model_instance(value)
        extract_model_klass(value).new(value)
      end
    end
  end
end
