# frozen_string_literal: true

class ApplicationLayoutPreloader
  include ReadOnlyMixin

  def self.banner_json_cache
    @banner_json_cache ||= DistributedCache.new("banner_json")
  end

  def initialize(guardian:, theme_id:, theme_target:, login_method:)
    @guardian = guardian
    @theme_id = theme_id
    @theme_target = theme_target
    @login_method = login_method
    @preloaded = {}
  end

  def store_preloaded(key, json)
    # I dislike that there is a gsub as opposed to a gsub!
    #  but we can not be mucking with user input, I wonder if there is a way
    #  to inject this safety deeper in the library or even in AM serializer
    @preloaded[key] = json.gsub("</", "<\\/")
  end

  def preloaded_data
    preload_anonymous_data

    preload_upcoming_change_data(@guardian.user)

    if @guardian.authenticated?
      @guardian.user.sync_notification_channel_position
      preload_current_user_data
    end

    @preloaded
  end

  def banner_json
    return "{}" if !@guardian.authenticated? && SiteSetting.login_required?

    self
      .class
      .banner_json_cache
      .defer_get_set("json_#{I18n.locale}") do
        topic = Topic.where(archetype: Archetype.banner).first
        banner = topic.present? && !topic.category&.read_restricted? ? topic.banner(@guardian) : {}
        MultiJson.dump(banner)
      end
  end

  def custom_html_json
    data =
      if @theme_id.present?
        {
          top: Theme.lookup_field(@theme_id, @theme_target, "after_header"),
          footer: Theme.lookup_field(@theme_id, @theme_target, "footer"),
        }
      else
        {}
      end

    data.merge! DiscoursePluginRegistry.custom_html if DiscoursePluginRegistry.custom_html

    DiscoursePluginRegistry.html_builders.each do |name, _|
      if name.start_with?("client:")
        data[name.sub(/\Aclient:/, "")] = DiscoursePluginRegistry.build_html(name, self)
      end
    end

    MultiJson.dump(data)
  end

  private

  def preload_current_user_data
    @preloaded["currentUser"] = MultiJson.dump(
      CurrentUserSerializer.new(
        @guardian.user,
        scope: @guardian,
        root: false,
        login_method: @login_method,
      ),
    )

    report = TopicTrackingState.report(@guardian.user)
    serializer = TopicTrackingStateSerializer.new(report, scope: @guardian, root: false)
    hash = serializer.as_json

    @preloaded["topicTrackingStates"] = MultiJson.dump(hash[:data])
    @preloaded["topicTrackingStateMeta"] = MultiJson.dump(hash[:meta])

    if @guardian.is_admin?
      # This is used in the wizard so we can preload fonts using the FontMap JS API.
      @preloaded["fontMap"] = MultiJson.dump(load_font_map)

      # Used to show plugin-specific admin routes in the sidebar.
      @preloaded["visiblePlugins"] = MultiJson.dump(
        Discourse
          .plugins_sorted_by_name(enabled_only: false)
          .map do |plugin|
            {
              name: plugin.name.downcase,
              humanized_name: plugin.humanized_name,
              admin_route: plugin.full_admin_route,
              enabled: plugin.enabled?,
              description: plugin.metadata.about,
            }
          end,
      )
    end
  end

  def preload_anonymous_data
    check_readonly_mode if @readonly_mode.nil?
    @preloaded["site"] = Site.json_for(@guardian)
    @preloaded["siteSettings"] = SiteSetting.client_settings_json
    @preloaded["themeSiteSettingOverrides"] = SiteSetting.theme_site_settings_json(@theme_id)
    @preloaded["customHTML"] = custom_html_json
    @preloaded["banner"] = banner_json
    @preloaded["customEmoji"] = custom_emoji
    @preloaded["isReadOnly"] = @readonly_mode.to_json
    @preloaded["isStaffWritesOnly"] = @staff_writes_only_mode.to_json
    @preloaded["activatedThemes"] = activated_themes_json
  end

  def preload_upcoming_change_data(user)
    @preloaded["upcomingChanges"] = SiteSetting
      .upcoming_change_site_settings
      .each_with_object({}) do |upcoming_change, hash|
        hash[upcoming_change] = UpcomingChanges.enabled_for_user?(upcoming_change, user)
      end
      .to_json
  end

  def activated_themes_json
    id = @theme_id
    return "{}" if id.blank?
    ids = Theme.transform_ids(id)
    Theme.where(id: ids).pluck(:id, :name).to_h.to_json
  end

  def load_font_map
    DiscourseFonts
      .fonts
      .each_with_object({}) do |font, font_map|
        next if !font[:variants]
        font_map[font[:key]] = font[:variants].map do |v|
          {
            url: "#{Discourse.base_url}/fonts/#{v[:filename]}?v=#{DiscourseFonts::VERSION}",
            weight: v[:weight],
          }
        end
      end
  end

  def custom_emoji
    serializer = ActiveModel::ArraySerializer.new(Emoji.custom, each_serializer: EmojiSerializer)
    MultiJson.dump(serializer)
  end
end
