module HasRichText
  extend ActiveSupport::Concern

  included do
    class_attribute :has_rich_text_attributes
    self.has_rich_text_attributes = [].freeze
    attr_accessor :has_rich_text_attachments_cache
    attr_accessor :form_id

    before_save :has_rich_text_parse
    after_save :has_rich_text_commit_cache
    after_save :has_rich_text_pickup_attachments
    after_save :has_rich_text_cleanup_unused_attachments
  end

=begin

Checks if file is used inline

@param store_object [Store] attachment to evaluate
@return [Bool] if attachment is inline

@example
  store_object = Store.first
  HasRichText.attachment_inline?(::CanAssets.reduce(list, {})

=end

  def self.attachment_inline?(store_object)
    store_object.preferences&.dig('Content-Disposition') == 'inline'
  end

  private

  def has_rich_text_parse # rubocop:disable Naming/PredicateName
    has_rich_text_attributes.each { |attr| has_rich_text_parse_attribute(attr) }
  end

  def has_rich_text_parse_attribute(attr) # rubocop:disable Naming/PredicateName
    image_prefix = "#{self.class.name}_#{attr}"
    raw = send(attr)

    scrubber = Loofah::Scrubber.new do |node|
      next if node.name != 'img'
      next if !(cid = node.delete 'cid')

      node['src'] = "cid:#{cid}"
    end

    parsed = Loofah.scrub_fragment(raw, scrubber).to_s
    parsed = HtmlSanitizer.strict(parsed)

    (parsed, attachments_inline) = HtmlSanitizer.replace_inline_images(parsed, image_prefix)

    send("#{attr}=", parsed)

    self.has_rich_text_attachments_cache ||= []
    self.has_rich_text_attachments_cache += attachments_inline
  end

  def has_rich_text_commit_cache # rubocop:disable Naming/PredicateName
    return if has_rich_text_attachments_cache.blank?

    has_rich_text_attachments_cache.each do |attachment_cache|
      Store.add(
        object:      self.class.name,
        o_id:        id,
        data:        attachment_cache[:data],
        filename:    attachment_cache[:filename],
        preferences: attachment_cache[:preferences],
      )
    end
  end

  def attributes_with_association_ids
    attrs = super

    self.class.has_rich_text_attributes.each do |attr|
      attrs[attr.to_s] = send("#{attr}_with_urls")
    end

    attrs
  end

  def has_rich_text_pickup_attachments # rubocop:disable Naming/PredicateName
    return if form_id.blank?

    self.attachments = Store.list(
      object: 'UploadCache',
      o_id:   form_id,
    )
  end

  def has_rich_text_cleanup_unused_attachments # rubocop:disable Naming/PredicateName
    active_cids = has_rich_text_attributes.each_with_object([]) do |elem, memo|
      memo.concat self.class.has_rich_text_inline_cids(self, elem)
    end

    attachments
      .select { |file| HasRichText.attachment_inline?(file) }
      .reject { |file| active_cids.include? file.preferences&.dig('Content-ID') }
      .each   { |file| Store.remove_item(file.id) }
  end

  class_methods do
    def has_rich_text(*attrs) # rubocop:disable Naming/PredicateName
      (self.has_rich_text_attributes += attrs.map(&:to_sym)).freeze

      attrs.each do |attr|
        define_method "#{attr}_with_urls" do
          self.class.has_rich_text_insert_urls(self, attr)
        end
      end
    end

    def has_rich_text_insert_urls(object, attr) # rubocop:disable Naming/PredicateName
      raw = object.send(attr)

      attachments = object.attachments

      scrubber = Loofah::Scrubber.new do |node|
        next if node.name != 'img'
        next if !node['src']&.start_with?('cid:')

        cid = node['src'].sub(/^cid:/, '')
        lookup_cids = [cid, "<#{cid}>"]

        attachment = attachments.find do |file|
          lookup_cids.include? file.preferences&.dig('Content-ID')
        end

        next if !attachment

        node['cid'] = cid
        node['src'] = Rails.application.routes.url_helpers.attachment_path(attachment.id)
      end

      parsed = Loofah.scrub_fragment(raw, scrubber).to_s

      parsed
    end

    def has_rich_text_inline_cids(object, attr) # rubocop:disable Naming/PredicateName
      raw = object.send(attr)

      inline_cids = []

      scrubber = Loofah::Scrubber.new do |node|
        next if node.name != 'img'
        next if !node['src']&.start_with? 'cid:'

        cid = node['src'].sub(/^cid:/, '')
        inline_cids << cid
      end

      Loofah.scrub_fragment(raw, scrubber)

      inline_cids
    end
  end
end
