class Tapioca::Dsl::Helpers::ActiveRecordColumnTypeHelper

def as_non_nilable_if_persisted_and_not_nullable(base_type, column_nullability:)

: (String base_type, column_nullability: bool) -> String
def as_non_nilable_if_persisted_and_not_nullable(base_type, column_nullability:)
  # It's possible that when ActiveModel::Type::Value is used, the signature being reflected on in
  # ActiveModelTypeHelper.type_for(type_value) may say the type can be nilable. However, if the type is
  # persisted and the column is not nullable, we can assume it's not nilable.
  return as_non_nilable_type(base_type) if @column_type_option.persisted? && !column_nullability
  base_type
end

def column_type_for(column_name)

: (String? column_name) -> [String, String]
def column_type_for(column_name)
  return ["T.untyped", "T.untyped"] if @column_type_option.untyped?
  column = @constant.columns_hash[column_name]
  column_type = @constant.attribute_types[column_name]
  getter_type = type_for_activerecord_value(column_type, column_nullability: !!column&.null)
  setter_type =
    case column_type
    when ActiveRecord::Enum::EnumType
      enum_setter_type(column_type)
    else
      getter_type
    end
  if @column_type_option.persisted? && !column&.null
    [getter_type, setter_type]
  else
    getter_type = as_nilable_type(getter_type) unless not_nilable_serialized_column?(column_type)
    [getter_type, as_nilable_type(setter_type)]
  end
end

def enum_setter_type(column_type)

: (ActiveRecord::Enum::EnumType column_type) -> String
def enum_setter_type(column_type)
  # In Rails < 7 this method is private. When support for that is dropped we can call the method directly
  case column_type.send(:subtype)
  when ActiveRecord::Type::Integer
    "T.any(::String, ::Symbol, ::Integer)"
  else
    "T.any(::String, ::Symbol)"
  end
end

def id_type

: -> [String, String]
def id_type
  if @constant.respond_to?(:composite_primary_key?) && T.unsafe(@constant).composite_primary_key?
    primary_key_columns = @constant.primary_key
    getters = []
    setters = []
    primary_key_columns.each do |column|
      getter, setter = column_type_for(column)
      getters << getter
      setters << setter
    end
    ["[#{getters.join(", ")}]", "[#{setters.join(", ")}]"]
  else
    column_type_for(@constant.primary_key)
  end
end

def initialize(constant, column_type_option: ColumnTypeOption::Persisted)

: (singleton(ActiveRecord::Base) constant, ?column_type_option: ColumnTypeOption) -> void
def initialize(constant, column_type_option: ColumnTypeOption::Persisted)
  @constant = constant
  @column_type_option = column_type_option
end

def not_nilable_serialized_column?(column_type)

: (untyped column_type) -> bool
def not_nilable_serialized_column?(column_type)
  return false unless column_type.is_a?(ActiveRecord::Type::Serialized)
  return false unless column_type.coder.is_a?(ActiveRecord::Coders::YAMLColumn)
  [Array.singleton_class, Hash.singleton_class].include?(column_type.coder.object_class.singleton_class)
end

def serialized_column_type(column_type)

: (ActiveRecord::Type::Serialized column_type) -> String
def serialized_column_type(column_type)
  case column_type.coder
  when ActiveRecord::Coders::YAMLColumn
    case column_type.coder.object_class
    when Array.singleton_class
      "T::Array[T.untyped]"
    when Hash.singleton_class
      "T::Hash[T.untyped, T.untyped]"
    else
      "T.untyped"
    end
  else
    "T.untyped"
  end
end

def type_for(attribute_name, column_name = attribute_name)

: (String attribute_name, ?String column_name) -> [String, String]
def type_for(attribute_name, column_name = attribute_name)
  return id_type if attribute_name == "id"
  column_type_for(column_name)
end

def type_for_activerecord_value(column_type, column_nullability:)

: (untyped column_type, column_nullability: bool) -> String
def type_for_activerecord_value(column_type, column_nullability:)
  case column_type
  when ->(type) { defined?(MoneyColumn) && MoneyColumn::ActiveRecordType === type }
    "::Money"
  when ActiveRecord::Type::Integer
    "::Integer"
  when ->(type) {
         defined?(ActiveRecord::Encryption) && ActiveRecord::Encryption::EncryptedAttributeType === type
       }
    # Reflect to see if `ActiveModel::Type::Value` is being used first.
    getter_type = Tapioca::Dsl::Helpers::ActiveModelTypeHelper.type_for(column_type)
    # Fallback to String as `ActiveRecord::Encryption::EncryptedAttributeType` inherits from
    # `ActiveRecord::Type::Text` which inherits from `ActiveModel::Type::String`.
    return "::String" if getter_type == "T.untyped"
    as_non_nilable_if_persisted_and_not_nullable(getter_type, column_nullability:)
  when ActiveRecord::Type::String
    "::String"
  when ActiveRecord::Type::Date
    "::Date"
  when ActiveRecord::Type::Decimal
    "::BigDecimal"
  when ActiveRecord::Type::Float
    "::Float"
  when ActiveRecord::Type::Boolean
    "T::Boolean"
  when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
    "::Time"
  when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
    "::ActiveSupport::TimeWithZone"
  when ActiveRecord::Enum::EnumType
    "::String"
  when ActiveRecord::Type::Binary
    "::String"
  when ActiveRecord::Type::Serialized
    serialized_column_type(column_type)
  when ->(type) {
         defined?(ActiveRecord::Normalization::NormalizedValueType) &&
           ActiveRecord::Normalization::NormalizedValueType === type
       }
    type_for_activerecord_value(column_type.cast_type, column_nullability:)
  when ->(type) {
         defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid) &&
           ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid === type
       }
    "::String"
  when ->(type) {
         defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Cidr) &&
           ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Cidr === type
       }
    "::IPAddr"
  when ->(type) {
         defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore) &&
           ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore === type
       }
    "T::Hash[::String, ::String]"
  when ->(type) {
         defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Interval) &&
           ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Interval === type
       }
    "::ActiveSupport::Duration"
  when ->(type) {
         defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array) &&
           ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array === type
       }
    "T::Array[#{type_for_activerecord_value(column_type.subtype, column_nullability:)}]"
  when ->(type) {
         defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bit) &&
           ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bit === type
       }
    "::String"
  when ->(type) {
         defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::BitVarying) &&
           ActiveRecord::ConnectionAdapters::PostgreSQL::OID::BitVarying === type
       }
    "::String"
  when ->(type) {
         defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Range) &&
           ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Range === type
       }
    "T::Range[#{type_for_activerecord_value(column_type.subtype, column_nullability:)}]"
  when ->(type) {
         defined?(ActiveRecord::Locking::LockingType) &&
           ActiveRecord::Locking::LockingType === type
       }
    as_non_nilable_if_persisted_and_not_nullable("::Integer", column_nullability:)
  when ->(type) {
         defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Enum) &&
           ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Enum === type
       }
    "::String"
  else
    as_non_nilable_if_persisted_and_not_nullable(
      ActiveModelTypeHelper.type_for(column_type),
      column_nullability: column_nullability,
    )
  end
end