# frozen_string_literal: true

#
# Copyright (C) 2012 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

module StreamItemsHelper
  include TextHelper

  class StreamItemPresenter
    attr_accessor :stream_item_id, :updated_at, :unread, :path, :context, :state, :summary
  end

  class ContextPresenter
    attr_accessor :id, :type, :name, :linked_to, :time_zone
  end

  def categorize_stream_items(stream_items, user = @current_user)
    categorized_items = {}
    return categorized_items unless stream_items.present? # if we have no items (possibly because we have no user), don't try to activate the user's shard

    supported_categories = %w(Announcement Conversation Assignment DiscussionTopic AssessmentRequest)
    supported_categories.each { |category| categorized_items[category] = [] }

    topic_types = %w{DiscussionTopic Announcement}
    ActiveRecord::Associations::Preloader.new.preload(
      stream_items.select{|i| topic_types.include?(i.asset_type)}.map{|item| item.data }, :context)

    stream_items.each do |item|
      category = item.data.class.name
      category = category_for_message(item.data.notification_name) if category == "Message"

      next unless supported_categories.include?(category)

      if category == "Conversation"
        participant = user.conversation_participant(item.asset_id)

        next if participant.nil? || participant.last_message.nil? || participant.last_author?
        item.participant = participant

        # because we're cheating and just checking unread here instead of using
        # the workflow_state on the stream_item_instance, that workflow_state
        # may be out of sync with the underlying conversation.
        item.unread = participant.unread?
      elsif category == "AssessmentRequest"
        next unless item.data.asset.grants_right?(user, :read)
      end

      if topic_types.include? category
        next if item.data.try(:visible_for?, user) == false
      end

      categorized_items[category] << generate_presenter(category, item, user)
    end
    categorized_items
  end

  def generate_presenter(category, item, user = @current_user)
    presenter = StreamItemPresenter.new
    # need to store stream item id relative to the user's shard, since we'll
    # use it later to look up the user's StreamItemInstances for deletion
    presenter.stream_item_id = user.shard.activate{ item.id }
    presenter.updated_at = extract_updated_at(category, item, user)
    presenter.updated_at ||= item.updated_at
    presenter.unread = item.unread
    presenter.path = extract_path(category, item, user)
    presenter.context = extract_context(category, item)
    presenter.summary = extract_summary(category, item, user)
    presenter
  end

  def extract_updated_at(category, item, user)
    case category
    when "Conversation"
      item.data.conversation_participants.find_by(user: user)&.last_message_at
    else
      item.data.respond_to?(:updated_at) ? item.data.updated_at : nil
    end
  end

  def extract_path(category, item, user)
    case category
    when "Announcement", "DiscussionTopic"
      polymorphic_path([item.context_type.underscore, category.underscore], :"#{item.context_type.underscore}_id"\
                       => Shard.short_id_for(item.context_id), :id => Shard.short_id_for(item.asset_id))
    when "Conversation"
      conversation_path(Shard.short_id_for(item.asset_id))
    when "Assignment"
      polymorphic_path([item.context_type.underscore, category.underscore], :"#{item.context_type.underscore}_id"\
                       => Shard.short_id_for(item.context_id), :id => Shard.short_id_for(item.data.context_id))
    when "AssessmentRequest"
      submission = item.data.asset
      Submission::ShowPresenter.new(
        submission: submission,
        current_user: user,
        assessment_request: item.data
      ).submission_data_url
    end
  end

  def extract_context(category, item)
    context = ContextPresenter.new
    asset = item.data
    case category
    when "Announcement", "DiscussionTopic", "Assignment"
      context.type = item.context_type
      context.id = item.context_id
      context.name = asset.context_short_name
      context.linked_to = polymorphic_path([context.type.underscore, category.underscore.pluralize], :"#{context.type.underscore}_id" => Shard.short_id_for(context.id))
    when "Conversation"
      context.type = "User"
      last_author = item.participant.last_message.author
      context.id = last_author.id
      context.name = last_author.short_name
      context.linked_to = user_path(last_author)
    when "AssessmentRequest"
      context.type = item.context_type
      context.id = item.context_id
      context.name = asset.context_short_name
    end
    context.time_zone = item.context.try(:time_zone)
    context
  end

  def extract_summary(category, item, user = @current_user)
    asset = item.data
    case category
    when "Announcement", "DiscussionTopic"
      asset.title
    when "Conversation"
      CanvasTextHelper.truncate_text(item.participant.last_message.body, :max_length => 250)
    when "Assignment"
      asset.subject
    when "AssessmentRequest"
      # TODO I18N should use placeholders, not concatenation
      asset.asset.assignment.title + " " + I18n.t('for', "for") + " " + assessment_author_name(asset, user)
    else
      nil
    end
  end

  def category_for_message(name)
    case name
    when "Assignment Created",
         "Assignment Changed",
         "Assignment Due Date Changed"
      "Assignment"

    when "Assignment Graded",
         "Assignment Submitted Late",
         "Grade Weight Changed",
         "Group Assignment Submitted Late"
      "Submission"

    else
      "Ignore"
    end
  end

  def assessment_author_name(asset, user = @current_user)
    if can_do(asset, user, :read_assessment_user)
      asset.asset.user.name
    else
      I18n.t(:anonymous_user, "Anonymous User")
    end
  end

  private :category_for_message, :assessment_author_name
end
