# frozen_string_literal: true

require 'intranet/abstract_responder'
require 'intranet/core/haml_wrapper'
require 'intranet/core/locales'
require 'json'
require_relative 'version'

module Intranet
  module Pictures
    # The responder for the Pictures monitor module of the Intranet.
    class Responder < AbstractResponder # rubocop:disable Metrics/ClassLength
      include Core::HamlWrapper # 'inherits' from methods of HamlWrapper

      # Returns the name of the module.
      # @return [String] The name of the module.
      def self.module_name
        NAME
      end

      # Returns the version of the module, according to semantic versionning.
      # @return [String] The version of the module.
      def self.module_version
        VERSION
      end

      # Returns the homepage URL of the module.
      # @return [String] The homepage URL of the module.
      def self.module_homepage
        HOMEPAGE_URL
      end

      # Initializes a new Pictures responder instance.
      # @param provider [#title,#list_pictures,#group_thumbnail,#group_brief,#picture]
      #  The pictures provider.
      # @see Intranet::Pictures::JsonDbProvider The specification of the provider, and in particular
      #  the minimal mandatory elements that must be returned by the operations.
      # @param recents [Array<Hash{group_by:String, sort_by:String, sort_order:String,
      #  limit:Integer}>]
      #  The description of the recent pictures to be displayed on the module home page. Pictures
      #  will first be sorted according to +sort_by+ and +sort_order+, then grouped by +group_by+,
      #  keeping only the first +limit+ elements, or all if +limit+ is zero. All keys except
      #  +group_by+ may be omitted.
      # @param home_groups [Array<Hash{group_by:String, sort_by:String, sort_order:String,
      #  browse_group_by:String, browse_sort_by:String, browse_sort_order:String}>]
      #  The description of the pictures groups to be displayed on the module home page, after the
      #  +recents+. Pictures will first be sorted according to +sort_by+ and +sort_order+, then
      #  grouped by +group_by+. The obtained groups will be displayed with their thumbnail,
      #  (optional) brief text and a link to display images of that group sorted according to
      #  +browse_sort_by+ and +browse_sort_order+ and grouped by +browse_group_by+. All keys except
      #  +group_by+ and +browse_group_by+ may be omitted.
      # @param in_menu [Boolean] Whether the module instance should be displayed in the main
      #                          navigation menu or not.
      def initialize(provider, recents = [], home_groups = [], in_menu = true)
        @provider = provider
        @recents = recents
        @in_menu = in_menu
        @home_groups = home_groups
      end

      # Specifies if the responder instance should be displayed in the main navigation menu or not.
      # @return [Boolean] True if the responder instance should be added to the main navigation
      #                   menu, False otherwise.
      def in_menu?
        @in_menu
      end

      # Specifies the absolute path to the resources directory for that module.
      # @return [String] The absolute path to the resources directory for the module.
      def resources_dir
        File.absolute_path(File.join('..', 'resources'), __dir__)
      end

      # Generates the HTML content associated to the given +path+ and +query+.
      # @param path [String] The requested URI, relative to that module root URI.
      # @param query [Hash<String,String>] The URI variable/value pairs, if any.
      # @return [Integer, String, String] The HTTP return code, the MIME type and the answer body.
      #
      # Relevant queries are as follows:
      #
      # * If +path+ is +/browse.html+, key/value pairs may be used to restrict the displayed images
      #   to those satisfying all the pairs. Moreover, +sort_by+ (followed by a key), +sort_order+
      #   (followed by either +asc+ or +desc+) and +group_by+ may be used to alter the way images
      #   are displayed.
      # * If +path+ is +/api/group_thumbnail+ or +/api/group_brief+, query must contain only a
      #   single key/value pair designating the pictures group.
      # * If +path+ is +/api/pictures+, key/value pairs may be used to restrict the displayed images
      #   to those satisfying all the pairs. Moreover, +sort_by+ (followed by a key), and
      #   +sort_order+ (followed by either +asc+ or +desc+) may be used to alter the order in which
      #   images are returned.
      # * If +path+ is +api/picture+, key/value pairs must be used to select a single image from the
      #   gallery.
      #
      # When +path+ is +/api/pictures+, the selected pictures are returned in JSON format (REST API)
      # with the following structure:
      #     [
      #       { "id": "...", "height": 480, "width": 640, "title": "...", "datetime": "...", ... },
      #       { ... }
      #     ]
      def generate_page(path, query)
        case path
        when %r{^/index\.html$}  then serve_home
        when %r{^/browse\.html$} then serve_browse(query)
        when %r{^/api/} then serve_api(path.gsub(%r{^/api}, ''), query)
        when %r{^/i18n\.js$} then serve_i18n_js
        else super(path, query)
        end
      end

      # Returns the title of the Pictures module, as displayed on the web page.
      # @return [String] The title of the Pictures module web page.
      def title
        @provider.title
      end

      private

      # Extract a selector from the given +query+.
      # @return [Hash<String,String>] The picture selector.
      def selector(query)
        query.except('group_by', 'sort_by', 'sort_order')
      end

      # Extract the grouping criteria from the given +query+, ie. the key that will be used to group
      # the selected pictures.
      # @return [String] The key to use to group pictures.
      # @raise KeyError If no grouping criteria is specified.
      def group_by(query)
        query.fetch('group_by')
      end

      # Extract the sorting criteria from the given +query+, ie. the key that will be used to sort
      # the selected pictures.
      # @return [String] The key to use to sort pictures, or nil if no sorting is specified.
      def sort_by(query)
        query.fetch('sort_by')
      rescue KeyError
        nil
      end

      # Extract the sorting order from the given +query+.
      # @return [Boolean] True if the pictures should be sorted in ascending order, False otherwise.
      # @raise KeyError If the query requests an invalid sort order.
      def sort_order(query)
        return false if query['sort_order'] == 'desc'
        return true if query['sort_order'].nil? || query['sort_order'] == 'asc'

        raise KeyError # incorrect value for 'sort_order'
      end

      # Provides the list of required stylesheets.
      # @return [Array<String>] The list of required stylesheets.
      def stylesheets
        ['design/style.css', 'design/photoswipe/photoswipe.css',
         'design/photoswipe/photoswipe-dynamic-caption-plugin.css']
      end

      # Provides the list of required script files.
      # @return [Array<Hash<Symbol,String>>] The list of required scripts.
      def scripts
        [{ src: 'design/jpictures.js', type: 'module' }]
      end

      ##########################################################################
      ###            Servicing of the HTML "display-able" content            ###
      ##########################################################################

      def collect_groups(selector, group_by, sort_by, sort_order)
        # Select all pictures, sort them & eventually keep only group names (first occurrence only)
        @provider.list_pictures(selector, sort_by, sort_order).map do |picture|
          picture.fetch(group_by)
        end.uniq
      end

      def hash_to_query(hash)
        hash.map { |k, v| [k, v].join('=') }.join('&')
      end

      def recent_groups
        @recents.map do |recent|
          groups = collect_groups({}, group_by(recent), sort_by(recent), sort_order(recent))
          see_more_url = ''
          if recent['limit'].to_i.positive?
            groups = groups.first(recent['limit'].to_i)
            see_more_url = "browse.html?#{hash_to_query(recent.except('limit'))}"
          end
          { group_key: group_by(recent), groups: groups, see_more_url: see_more_url }
        end
      end

      def all_groups
        @home_groups.map do |sec|
          groups = collect_groups({}, group_by(sec), sort_by(sec), sort_order(sec))
          url_prefix = "browse.html?group_by=#{sec['browse_group_by']}"
          url_prefix += "&sort_by=#{sec['browse_sort_by']}" if sec['browse_sort_by']
          url_prefix += "&sort_order=#{sec['browse_sort_order']}" if sec['browse_sort_order']
          { group_key: group_by(sec), groups: groups, url_prefix: url_prefix }
        end
      end

      def make_nav(query = {})
        h = { I18n.t('nav.home') => '/index.html', I18n.t('pictures.menu') => nil, title => nil }
        unless query['group_by'].nil?
          h[title] = 'index.html'
          extra_key = I18n.t("pictures.nav.#{group_by(query)}")
          filters = selector(query).values
          extra_key += " (#{filters.join(', ')})" unless filters.empty?
          h.store(extra_key, nil)
        end
        h
      end

      def gallery_url(key, value, filters = {})
        filters.store(key, value)
        filters.store('sort_by', 'datetime')
        hash_to_query(filters)
      end

      def serve_home
        content = to_markup('pictures_home', nav: make_nav)
        [206, 'text/html',
         { content: content, title: title, stylesheets: stylesheets, scripts: scripts }]
      rescue KeyError
        [404, '', '']
      end

      def serve_browse(query)
        groups = collect_groups(selector(query), group_by(query), sort_by(query), sort_order(query))
        content = to_markup('pictures_browse', nav: make_nav(query), group_key: group_by(query),
                                               filters: selector(query), groups: groups)
        [206, 'text/html',
         { content: content, title: title, stylesheets: stylesheets, scripts: scripts }]
      rescue KeyError
        [404, '', '']
      end

      def serve_i18n_js
        [200, 'text/javascript',
         "export default {\n" \
         "  viewer_close: '#{I18n.t('pictures.viewer.close')}',\n" \
         "  viewer_zoom: '#{I18n.t('pictures.viewer.zoom')}',\n" \
         "  viewer_previous: '#{I18n.t('pictures.viewer.previous')}',\n" \
         "  viewer_next: '#{I18n.t('pictures.viewer.next')}',\n" \
         "  viewer_toggle_caption: '#{I18n.t('pictures.viewer.toggle_caption')}' };"]
      end

      ##########################################################################
      ###        Servicing of the REST API (raw JSON data & pictures)        ###
      ##########################################################################

      def api_group_thumbnail(query)
        raise KeyError unless query.size == 1

        key, value = query.first
        pic = @provider.group_thumbnail(key, value)
        if pic.nil?
          pic = ['image/svg+xml', File.read(File.join(resources_dir, 'www', 'group_thumbnail.svg'))]
        end
        pic
      end

      def api_group_brief(query)
        raise KeyError unless query.size == 1

        key, value = query.first
        @provider.group_brief(key, value)
      end

      def api_list_pictures(query)
        @provider.list_pictures(selector(query), sort_by(query), sort_order(query))
      end

      def serve_api(path, query)
        case path
        when %r{^/group_thumbnail$} then [200, api_group_thumbnail(query)].flatten
        when %r{^/group_brief$} then [200, 'application/json', api_group_brief(query).to_json]
        when %r{^/pictures$} then [200, 'application/json', api_list_pictures(query).to_json]
        when %r{^/picture$} then [200, @provider.picture(query)].flatten
        else [404, '', '']
        end
      rescue KeyError
        [404, '', '']
      end
    end
  end
end