# typed: strict
class Domain::Post < ReduxApplicationRecord
  extend T::Helpers
  include HasCompositeToParam
  include HasViewPrefix
  include AttrJsonRecordAliases
  include HasDescriptionHtmlForView
  include HasDomainType

  self.table_name = "domain_posts"
  abstract!

  class_attribute :class_has_creators,
                  :class_belongs_to_groups,
                  :class_has_faving_users

  sig { returns(T::Boolean) }
  def self.has_creators?
    !!class_has_creators
  end

  sig { returns(T::Boolean) }
  def self.belongs_to_groups?
    !!class_belongs_to_groups
  end

  sig { returns(T::Boolean) }
  def self.has_faving_users?
    !!class_has_faving_users
  end

  # so sorbet knows this is a string
  sig { returns(String) }
  def type
    super
  end

  sig { params(value: String).returns(String) }
  def type=(value)
    super
  end

  attr_json :last_submission_log_entry_id, :integer
  belongs_to :last_submission_log_entry,
             class_name: "::HttpLogEntry",
             optional: true

  has_many :files,
           class_name: "::Domain::PostFile",
           dependent: :destroy,
           inverse_of: :post,
           foreign_key: :post_id

  sig { params(klass: T.class_of(Domain::PostFile)).void }
  def self.has_single_file!(klass = Domain::PostFile)
    has_one :file,
            -> { order(created_at: :desc) },
            class_name: klass.name,
            foreign_key: :post_id
  end

  sig { params(klass: T.class_of(Domain::PostFile)).void }
  def self.has_multiple_files!(klass = Domain::PostFile)
    has_many :files,
             class_name: klass.name,
             foreign_key: :post_id,
             inverse_of: :post
  end

  sig { params(klass: T.class_of(Domain::User)).void }
  def self.has_single_creator!(klass)
    self.class_has_creators = klass
    has_one :primary_user_post_creation,
            class_name: "::Domain::UserPostCreation",
            inverse_of: :post,
            dependent: :destroy
    has_one :creator,
            class_name: klass.name,
            through: :primary_user_post_creation,
            source: :user,
            inverse_of: :posts
  end

  has_many :user_post_creations,
           class_name: "::Domain::UserPostCreation",
           inverse_of: :post,
           dependent: :destroy

  sig { params(klass: T.class_of(Domain::User)).void }
  def self.has_multiple_creators!(klass)
    # multiple creators association
    self.class_has_creators = klass
    has_many :creators,
             class_name: klass.name,
             through: :user_post_creations,
             source: :user
  end

  # special purpose association for preloading user_post_favs
  belongs_to :user_post_fav,
             class_name: "::Domain::UserPostFav",
             foreign_key: %i[user_id id],
             primary_key: %i[user_id post_id],
             optional: true

  scope :with_user_post_fav,
        -> do
          self
            .reorder("")
            .includes(:user_post_fav)
            .select("#{Domain::Post.table_name}.*")
            .select("#{Domain::UserPostFav.table_name}.user_id as user_id")
            .select(
              "(#{Domain::UserPostFav.table_name}.json_attributes->>'fav_id')::integer as fav_id",
            )
            .order(
              # if there is no fav_id, use post_order_attribute to put the post after
              # the post with the next highest post_order_attribute
              # otherwise sort by fav_id, then post_order_attribute
              Arel.sql(
                "fav_id DESC, (#{Domain::Post.table_name}.json_attributes->>'#{self.post_order_attribute}')::integer DESC",
              ),
            )
        end

  has_many :user_post_favs,
           class_name: "::Domain::UserPostFav",
           inverse_of: :post,
           dependent: :destroy

  sig { params(klass: T.class_of(Domain::User)).void }
  def self.has_faving_users!(klass)
    self.class_has_faving_users = klass
    has_many :faving_users,
             class_name: klass.name,
             through: :user_post_favs,
             source: :user
  end

  sig do
    params(
      group_assoc_name: Symbol,
      group_klass: T.class_of(Domain::PostGroup),
      join_klass: T.class_of(Domain::PostGroupJoin),
    ).void
  end
  def self.belongs_to_groups!(group_assoc_name, group_klass, join_klass)
    has_many :post_group_joins,
             class_name: join_klass.name,
             inverse_of: :post,
             dependent: :destroy

    has_many group_assoc_name,
             through: :post_group_joins,
             source: :group,
             class_name: group_klass.name
  end

  sig { overridable.returns(Symbol) }
  def self.post_order_attribute
    :id
  end

  sig { abstract.returns(T.nilable(String)) }
  def title
  end

  sig { overridable.returns(String) }
  def title_for_view
    title || "(unknown)"
  end

  sig { abstract.returns(T.nilable(T.any(String, Integer))) }
  def domain_id_for_view
  end

  sig { abstract.returns(T.nilable(Addressable::URI)) }
  def external_url_for_view
  end

  sig { abstract.returns(T.nilable(Domain::PostFile)) }
  def primary_file_for_view
  end

  sig { overridable.returns(T.nilable(Domain::User)) }
  def primary_creator_for_view
    nil
  end

  sig { overridable.returns(T.nilable(HttpLogEntry)) }
  def scanned_post_log_entry_for_view
    nil
  end

  sig { overridable.returns(T.nilable(ActiveSupport::TimeWithZone)) }
  def file_downloaded_at
    nil
  end

  sig { overridable.returns(T.nilable(String)) }
  def description_for_view
  end

  sig { overridable.returns(T.nilable(HttpLogEntry)) }
  def guess_last_submission_log_entry
    nil
  end

  sig { overridable.returns(T.nilable(Integer)) }
  def num_favorites_for_view
    nil
  end

  sig { overridable.returns(T.nilable(String)) }
  def primary_creator_name_fallback_for_view
    nil
  end

  class TagForView < T::Struct
    const :category, Symbol
    const :value, String
  end

  sig { overridable.returns(T.nilable(T::Array[TagForView])) }
  def tags_for_view
    nil
  end
end

# eager load all subclasses
Dir[Rails.root.join("app/models/domain/post/**/*.rb")].each do |file|
  require_dependency file
end
