# typed: strict
# frozen_string_literal: true

module HasAuxTable
  class ModelClassHelper < T::Struct
    extend T::Sig

    const :klass, T.class_of(ActiveRecord::Base)
    const :rejected_column_names, T::Set[String]

    sig { params(name: T.any(String, Symbol)).returns(T::Boolean) }
    def is_column?(name)
      column_names.include?(name.to_s)
    end

    sig { returns(T::Array[Symbol]) }
    def primary_keys
      @primary_keys ||=
        T.let(
          [klass.primary_key].flatten.map(&:to_sym),
          T.nilable(T::Array[Symbol])
        )
    end

    sig { returns(T::Array[String]) }
    def column_names
      @column_names ||=
        T.let(
          begin
            klass
              .column_names
              .reject { |col| rejected_column_names.include?(col.to_s) }
              .map(&:to_s)
          end,
          T.nilable(T::Array[String])
        )
    end

    sig { returns(T::Hash[String, ActiveRecord::ConnectionAdapters::Column]) }
    def columns_hash
      @columns_hash ||=
        T.let(
          slice_by_columns(klass.columns_hash),
          T.nilable(T::Hash[String, ActiveRecord::ConnectionAdapters::Column])
        )
    end

    sig { returns(T::Hash[String, ActiveModel::Type]) }
    def attribute_types
      @attribute_types ||=
        T.let(
          slice_by_columns(klass.attribute_types),
          T.nilable(T::Hash[String, ActiveModel::Type])
        )
    end

    sig { returns(T::Hash[String, ActiveModel::Attribute]) }
    def default_attributes
      @default_attributes ||=
        T.let(
          begin
            da = klass._default_attributes
            da.keys.map { |k, v| [k, da[k]] }.to_h.slice(*self.column_names)
          end,
          T.nilable(T::Hash[String, ActiveModel::Attribute])
        )
    end

    sig do
      params(instance: ActiveRecord::Base).returns(T::Hash[String, T.untyped])
    end
    def attributes_on(instance)
      Util.ensure_is_instance_of!(instance, self.klass)
      unless instance.class <= self.klass
        raise("#{instance.class.name} not a #{self.klass.name}")
      end
      slice_by_columns(instance.attributes)
    end

    private

    sig do
      type_parameters(:T)
        .params(hash: T::Hash[String, T.type_parameter(:T)])
        .returns(T::Hash[String, T.type_parameter(:T)])
    end
    def slice_by_columns(hash)
      T.unsafe(hash).slice(*self.column_names)
    end
  end
end
