class CrossModelSlugValidator < ActiveModel::EachValidator
  FORMAT_REGEX = /\A[0-9a-z\-_]+\z/
  ORGANIZATION_FORMAT_REGEX = /\A(?![0-9]+\z)[0-9a-z\-_]+\z/
  ## allow / in page slugs
  PAGE_FORMAT_REGEX = %r{\A[0-9a-z\-_/+]+\z}
  PAGE_DIRECTORY_LIMIT = 6

  def validate_each(record, attribute, value)
    return if value.blank?

    correct_format?(record, attribute, value)
    allowed_subdirectory_count?(record, attribute, value)
    not_on_reserved_list?(record, attribute, value)
    unique_across_models?(record, attribute, value)
  end

  private

  def not_on_reserved_list?(record, attribute, value)
    return false if record.instance_of?(::Page) || ReservedWords.all.exclude?(value)

    record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.is_reserved"))
  end

  def correct_format?(record, attribute, value)
    format_regex = case record.class.name
                   when "Organization"
                     ORGANIZATION_FORMAT_REGEX
                   when "Page"
                     PAGE_FORMAT_REGEX
                   else
                     FORMAT_REGEX
                   end
    return false if value.match?(format_regex)

    record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.is_invalid"))
  end

  def unique_across_models?(record, attribute, value)
    # attribute_changed? is likely redundant, but is much cheaper than the cross-model exists check
    return false unless record.public_send("#{attribute}_changed?")
    return false unless already_exists?(value)

    record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.is_taken"))
  end

  def allowed_subdirectory_count?(record, attribute, value)
    return false unless record.instance_of?(::Page)
    return false if value.split("/").count <= PAGE_DIRECTORY_LIMIT

    record.errors.add(attribute, I18n.t("validators.cross_model_slug_validator.too_many_subdirectories"))
  end

  def already_exists?(value)
    CrossModelSlug.exists?(value.downcase)
  end
end
