# frozen_string_literal: true

module ActiveScaffold::Config
  class List < Base
    self.crud_type = :read

    def initialize(core_config)
      super
      # inherit from global scope
      # full configuration path is: defaults => global table => local table
      @per_page = self.class.per_page
      @filters = ActiveScaffold::DataStructures::Filters.new
      @page_links_inner_window = self.class.page_links_inner_window
      @page_links_outer_window = self.class.page_links_outer_window

      # originates here
      @sorting = ActiveScaffold::DataStructures::Sorting.new(@core.columns, @core.model)
      @sorting.set_default_sorting

      # inherit from global scope
      @empty_field_text = self.class.empty_field_text
      @association_join_text = self.class.association_join_text
      @pagination = self.class.pagination
      @auto_pagination = self.class.auto_pagination
      @show_search_reset = self.class.show_search_reset
      @show_filter_reset = self.class.show_filter_reset
      @filter_human_message = self.class.filter_human_message
      @reset_link = self.class.reset_link.clone
      @reset_filter_link = self.class.reset_filter_link.clone
      @wrap_tag = self.class.wrap_tag
      @always_show_search = self.class.always_show_search
      @always_show_create = self.class.always_show_create
      @messages_above_header = self.class.messages_above_header
      @auto_select_columns = self.class.auto_select_columns
      @refresh_with_header = self.class.refresh_with_header
      @calculate_etag = self.class.calculate_etag
    end

    # global level configuration
    # --------------------------
    # include list header on refresh
    cattr_accessor :refresh_with_header, instance_accessor: false
    @@refresh_with_header = false

    # how many records to show per page
    cattr_accessor :per_page, instance_accessor: false
    @@per_page = 15

    # how many page links around current page to show
    cattr_accessor :page_links_inner_window, instance_accessor: false
    @@page_links_inner_window = 2

    # how many page links around first and last page to show
    cattr_accessor :page_links_outer_window, instance_accessor: false
    @@page_links_outer_window = 0

    # what string to use when a field is empty
    cattr_accessor :empty_field_text, instance_accessor: false
    @@empty_field_text = '-'

    # display messages above table header
    cattr_accessor :messages_above_header, instance_accessor: false
    @@messages_above_header = false

    # what string to use to join records from plural associations
    cattr_accessor :association_join_text, instance_accessor: false
    @@association_join_text = ', '

    # What kind of pagination to use:
    # * true: The usual pagination
    # * :infinite: Treat the source as having an infinite number of pages (i.e. don't count the records; useful for large tables where counting is slow and we don't really care anyway)
    # * false: Disable pagination
    cattr_accessor :pagination, instance_accessor: false
    @@pagination = true

    # Auto paginate, only can be used with pagination enabled
    # * true: First page will be loaded on first request, next pages will be requested by AJAX until all items are loaded
    # * false: Disable auto pagination
    cattr_accessor :auto_pagination, instance_accessor: false
    @@auto_pagination = false

    # show a link to reset the search next to filtered message
    cattr_accessor :show_search_reset, instance_accessor: false
    @@show_search_reset = true

    # show a link to reset the filter next to filter human message
    cattr_accessor :show_filter_reset, instance_accessor: false
    @@show_filter_reset = true

    # filter human message
    # you may show the user a humanized applied filters, not the default ones
    cattr_accessor :filter_human_message, instance_accessor: false
    @@filter_human_message = false

    # the ActionLink to reset search
    cattr_reader :reset_link, instance_reader: false
    @@reset_link = ActiveScaffold::DataStructures::ActionLink.new('index', label: :click_to_reset, type: :collection, position: false, parameters: {search: ''})

    # the ActionLink to reset the filters
    cattr_reader :reset_filter_link, instance_reader: false
    @@reset_filter_link = ActiveScaffold::DataStructures::ActionLink.new('index', label: :click_to_reset, type: :collection, position: false, dynamic_parameters: -> { clear_filters_params })

    # wrap normal cells (not inplace editable columns or with link) with a tag
    # it allows for more css styling
    cattr_accessor :wrap_tag, instance_accessor: false
    @@wrap_tag = nil

    # Show search form in the list header instead of display the link
    cattr_accessor :always_show_search, instance_accessor: false
    @@always_show_search = false

    # Show create form in the list header instead of display the link
    cattr_accessor :always_show_create, instance_accessor: false
    @@always_show_create = false

    # Enable auto select columns on list, so only columns needed for list columns are selected
    cattr_accessor :auto_select_columns, instance_accessor: false
    @@auto_select_columns = false

    # Enable ETag calculation (when conditional_get_support is enabled), it requires to load records for page, when is disabled query can be avoided when page is cached in browser
    # order clause will be used for ETag when calculate_etag is disabled, so query for records can be avoided
    cattr_accessor :calculate_etag, instance_accessor: false
    @@calculate_etag = false

    def self.filters
      ActiveScaffold::DataStructures::Filters
    end

    # instance-level configuration
    # ----------------------------

    columns_accessor :columns

    # include list header on refresh
    attr_accessor :refresh_with_header

    # how many rows to show at once
    attr_accessor :per_page

    # how many page links around current page to show
    attr_accessor :page_links_inner_window

    # how many page links around current page to show
    attr_accessor :page_links_outer_window

    # What kind of pagination to use:
    # * true: The usual pagination
    # * :infinite: Treat the source as having an infinite number of pages (i.e. don't count the records; useful for large tables where counting is slow and we don't really care anyway)
    # * false: Disable pagination
    attr_accessor :pagination

    # Auto paginate, only can be used with pagination enabled
    # * true: First page will be loaded on first request, next pages will be requested by AJAX until all items are loaded
    # * false: Disable auto pagination
    attr_accessor :auto_pagination

    # what string to use when a field is empty
    attr_accessor :empty_field_text

    # display messages above table header
    attr_accessor :messages_above_header

    # what string to use to join records from plural associations
    attr_accessor :association_join_text

    # show a link to reset the search next to filtered message
    attr_accessor :show_search_reset

    # show a link to reset the filter next to filter human message
    attr_accessor :show_filter_reset

    # filter human message
    # you may show the user a humanized applied filters, not the default ones
    attr_accessor :filter_human_message

    # the ActionLink to reset search
    attr_reader :reset_link

    # the ActionLink to reset the filters
    attr_reader :reset_filter_link

    # the filters for this controller
    attr_reader :filters

    # the default sorting.
    # should be a hash of {column_name => direction}, e.g. {a: 'desc', b: 'asc'}.
    # for backwards compatibility, it may be an array of hashes of {column_name => direction}, e.g. [{a: 'desc'}, {b: 'asc'}].
    # to just sort on one column, you can simply provide a hash, e.g. {a: 'desc'}.
    def sorting=(val)
      val = [val] if val.is_a? Hash
      sorting.set(*val)
    end

    def sorting
      @sorting ||= ActiveScaffold::DataStructures::Sorting.new(@core.columns, @core.model)
    end

    # overwrite the includes used for the count sql query
    attr_accessor :count_includes

    # the label for this List action. used for the header.
    attr_writer :label

    def label
      @label ? as_(@label, count: 2) : @core.label(count: 2)
    end

    attr_writer :no_entries_message, :filtered_message, :always_show_search

    def no_entries_message
      @no_entries_message || :no_entries
    end

    def filtered_message
      @filtered_message || :filtered
    end

    def always_show_search
      @always_show_search && search_partial.present?
    end

    def search_partial
      if @always_show_search == true
        auto_search_partial
      elsif @core.actions.include? @always_show_search
        @always_show_search.to_s
      end
    end

    def auto_search_partial
      if @core.actions.include?(:search)
        'search'
      elsif @core.actions.include?(:field_search)
        'field_search'
      end
    end

    # always show create
    attr_writer :always_show_create

    def always_show_create
      @always_show_create && @core.actions.include?(:create)
    end

    # if list view is nested hide nested_column
    attr_writer :hide_nested_column

    def hide_nested_column
      @hide_nested_column.nil? ? true : @hide_nested_column
    end

    # wrap normal cells (not inplace editable columns or with link) with a tag
    # it allows for more css styling
    attr_accessor :wrap_tag

    # Enable auto select columns on list, so only columns needed for list columns are selected
    attr_accessor :auto_select_columns

    # Enable ETag calculation (when conditional_get_support is enabled), it requires to load records for page, when is disabled query can be avoided when page is cached in browser
    # order clause will be used for ETag when calculate_etag is disabled, so query for records can be avoided
    attr_accessor :calculate_etag

    # don't inherit, defined with columns_accessor, class_eval would complain about block too long
    class UserSettings
      user_attr :page_links_inner_window, :page_links_outer_window, :refresh_with_header, :empty_field_text,
                :association_join_text, :messages_above_header, :wrap_tag, :auto_select_columns, :calculate_etag,
                :no_entries_message, :filtered_message, :show_search_reset, :always_show_create, :always_show_search,
                :hide_nested_column, :pagination, :auto_pagination, :filter_human_message, :show_filter_reset

      def initialize(conf, storage, params)
        super(conf, storage, params, :list)
        @_sorting = nil
      end

      attr_writer :label

      # This label has already been localized.
      def label
        self['label'] || embedded_label || @label || @conf.label
      end

      def embedded_label
        @params.dig :embedded, :label
      end

      def per_page
        self['per_page'] = @params['limit'].to_i if @params.key? 'limit'
        self['per_page'] || @conf.per_page
      end

      def per_page=(value)
        self['per_page'] = value
      end

      def page
        self['page'] = @params['page'] || 1 if @params.key?('page') || @conf.auto_pagination
        self['page'] || 1
      end

      def page=(value = nil)
        self['page'] = value
      end

      attr_reader :nested_default_sorting

      def nested_default_sorting=(options)
        @nested_default_sorting ||= sorting_dup
        @nested_default_sorting.set_nested_sorting(options[:table_name], options[:default_sorting])
      end

      def default_sorting
        nested_default_sorting.nil? || @sorting.present? ? sorting_dup : nested_default_sorting
      end

      # TODO: programatically set sorting, for per-request configuration, priority @params, then @sort

      def user_sorting?
        @params['sort'] && @params['sort_direction'] != 'reset'
      end

      # change list sorting for this request, unless sorting is defined
      # {column => direction, column => direction}
      attr_writer :sorting

      def sorting
        if @_sorting.nil?
          # we want to store as little as possible in the session, but we want to return a Sorting data structure. so we recreate it each page load based on session data.
          self['sort'] = [@params['sort'], @params['sort_direction']] if @params['sort'] && @params['sort_direction']
          self['sort'] = nil if @params['sort_direction'] == 'reset'

          if self['sort'] && @conf.core.columns[self['sort'][0]]
            sorting = sorting_dup
            sorting.set(*self['sort'])
            @_sorting = sorting
          else
            @_sorting = default_sorting
            @_sorting.set(*@sorting) if @sorting
            @_sorting.constraint_columns = @conf.columns.constraint_columns if @conf.columns.constraint_columns.present?
          end
        end
        @_sorting
      end

      def count_includes
        @conf.count_includes
      end

      protected

      def sorting_dup
        sorting = @conf.sorting.dup
        # access to config instance columns instead of config class columns, in case columns are changed in this request
        sorting.instance_variable_set :@columns, core.columns
        sorting
      end
    end
  end
end
