require 'table_builder_helper/column'
require 'table_builder_helper/custom_links'
require 'table_builder_helper/url'
# table_builder_2
# A Rails helper that constructs an HTML table from a collection of objects. It
# receives the collection and an array of columns that get transformed into
# `
`s. A column of checkboxes can be added to the left side of the table
# for multiple selection. Columns are sortable by default, but sorting can be
# disabled either at the table level or at the column level. An optional
# `links` argument takes a set of symbols corresponding to controller actions
# that should be inserted in a gear menu next to each row in the table. That
# menu will also be populated with links defined in `collection#action_links`,
# a list of `Link` objects defined in a decorator for the given object.
#
# Depends on `params` and `current_referential`.
#
# Example:
#   table_builder_2(
#     @companies,
#     [
#       TableBuilderHelper::Column.new(
#         name: 'ID Codif',
#         attribute: Proc.new { |n| n.try(:objectid).try(:local_id) },
#         sortable: false
#       ),
#       TableBuilderHelper::Column.new(
#         key: :name,
#         attribute: 'name',
#         link_to: lambda do |company|
#           referential_company_path(@referential, company)
#         end
#       ),
#       TableBuilderHelper::Column.new(
#         key: :phone,
#         attribute: 'phone'
#       ),
#       TableBuilderHelper::Column.new(
#         key: :email,
#         attribute: 'email'
#       ),
#       TableBuilderHelper::Column.new(
#         key: :url,
#         attribute: 'url'
#       ),
#     ],
#     links: [:show, :edit],
#     cls: 'table has-search',
#     overhead: [
#       {
#         title: 'one',
#         width: 1,
#         cls: 'toto'
#       },
#       {
#         title: 'two Info',
#         width: 2,
#         cls: 'default'
#       }
#     ]
#   )
module TableBuilderHelper
  # TODO: rename this after migration from `table_builder`
  def table_builder_2(
    # An `ActiveRecord::Relation`, wrapped in a decorator to provide a list of
    # `Link` objects via an `#action_links` method
    collection,
    # An array of `TableBuilderHelper::Column`s
    columns,
    # When false, no columns will be sortable
    sortable: true,
    # When true, adds a column of checkboxes to the left side of the table
    selectable: false,
    # A set of controller actions that will be added as links to the top of the
    # gear menu
    links: [],
    # A CSS class to apply to the 
    cls: '',
    # A set of content, over the th line...
    overhead: [],
    # Possibility to override the result of collection.model
    model: nil,
    #overrides the params[:action] value
    action: nil
  )
    content_tag :table,
      thead(collection, columns, sortable, selectable, links.any?, overhead, model || collection.model, action || params[:action]) +
        tbody(collection, columns, selectable, links, overhead, model, action || params[:action]),
      class: cls
  end
  def self.item_row_class_name collection, model=nil
    model_name = model&.name
    model_name ||=
      if collection.respond_to?(:model)
        collection.model.name
      elsif collection.respond_to?(:first)
        collection.first.class.name
      else
        "item"
      end
    model_name.split("::").last.parameterize
  end
  private
  def thead(collection, columns, sortable, selectable, has_links, overhead, model, action)
    content_tag :thead do
      # Inserts overhead content if any specified
      over_head = ''
      unless overhead.empty?
        over_head = content_tag :tr, class: 'overhead' do
          oh_cont = []
          overhead.each do |h|
            oh_cont << content_tag(:th, raw(h[:title]), colspan: h[:width], class: h[:cls])
          end
          oh_cont.join.html_safe
        end
      end
      main_head = content_tag :tr do
        hcont = []
        if selectable
          hcont << content_tag(:th, checkbox(id_name: '0', value: 'all'))
        end
        columns.each do |column|
          if overhead.empty?
            hcont << content_tag(:th, build_column_header(
              column,
              sortable,
              model,
              params,
              params[:sort],
              params[:direction]
            ))
          else
            i = columns.index(column)
            if overhead[i].blank?
              prev = nil
              if i > 0
                (i-1..0).each do |j|
                  o = overhead[j]
                  if (j + o[:width].to_i) >= i
                    prev = o
                    break
                  end
                end
              end
              if prev
                clsArrayH = overhead[i - 1][:cls].split
                hcont << content_tag(:th, build_column_header(
                  column,
                  sortable,
                  model,
                  params,
                  params[:sort],
                  params[:direction]
                ), class: td_cls(clsArrayH))
              else
                hcont << content_tag(:th, build_column_header(
                  column,
                  sortable,
                  model,
                  params,
                  params[:sort],
                  params[:direction]
                ))
              end
            else
              clsArrayH = overhead[i][:cls].split
              hcont << content_tag(:th, build_column_header(
                column,
                sortable,
                model,
                params,
                params[:sort],
                params[:direction]
              ), class: td_cls(clsArrayH))
            end
          end
        end
        # Inserts a blank column for the gear menu
        last_item = collection.last
        action_links = last_item && last_item.respond_to?(:action_links) && (last_item&.action_links&.is_a?(AF83::Decorator::ActionLinks) ? last_item.action_links(action) : last_item.action_links)
        if has_links || action_links.try(:any?)
          hcont << content_tag(:th, '')
        end
        hcont.join.html_safe
      end
      (over_head + main_head).html_safe
    end
  end
  def tr item, columns, selectable, links, overhead, model_name, action
    klass = "#{model_name} #{model_name}-#{item.id}"
    content_tag :tr, class: klass do
      bcont = []
      if selectable
        disabled = selectable.respond_to?(:call) && !selectable.call(item)
        bcont << content_tag(
          :td,
          checkbox(id_name: item.try(:id), value: item.try(:id), disabled: disabled)
        )
      end
      columns.each do |column|
        value = column.value(item)
        extra_class = column.td_class(item)
        if column.linkable?
          path = column.link_to(item)
          link = value.present? && path.present? ? link_to(value, path) : value
          if overhead.empty?
            bcont << content_tag(:td, link, title: 'Voir', class: extra_class)
          else
            i = columns.index(column)
            if overhead[i].blank?
              prev = nil
              if i > 0
                (i-1..0).each do |j|
                  o = overhead[j]
                  if (j + o[:width].to_i) >= i
                    prev = o
                    break
                  end
                end
              end
              if prev
                clsArrayAlt = overhead[i - 1][:cls].split
                bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArrayAlt, extra_class))
              else
                bcont << content_tag(:td, link, title: 'Voir', class: extra_class)
              end
            else
              clsArray = overhead[columns.index(column)][:cls].split
              bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArray, extra_class))
            end
          end
        else
          if overhead.empty?
            bcont << content_tag(:td, value, class: extra_class)
          else
            i = columns.index(column)
            if overhead[i].blank?
              if (i > 0) && (overhead[i - 1][:width] > 1)
                clsArrayAlt = overhead[i - 1][:cls].split
                bcont << content_tag(:td, value, class: td_cls(clsArrayAlt, extra_class))
              else
                bcont << content_tag(:td, value, class: extra_class)
              end
            else
              clsArray = overhead[i][:cls].split
              bcont << content_tag(:td, value, class: td_cls(clsArray))
            end
          end
        end
      end
      action_links = item && item.respond_to?(:action_links) && (item.action_links.is_a?(AF83::Decorator::ActionLinks) ? item.action_links(action) : item.action_links)
      if links.any? || action_links.try(:any?)
        bcont << content_tag(
          :td,
          build_links(item, links, action),
          class: 'actions'
        )
      end
      bcont.join.html_safe
    end
  end
  def tbody(collection, columns, selectable, links, overhead, model = nil, action)
    model_name = TableBuilderHelper.item_row_class_name collection, model
    content_tag :tbody do
      collection.map do |item|
        tr item, columns, selectable, links, overhead, model_name, action
      end.join.html_safe
    end
  end
  def td_cls(a, extra_class="")
    out = [extra_class]
    if a.include? 'full-border'
      a.slice!(a.index('full-border'))
      out += a
    end
    out = out.select(&:present?).join(' ')
    out.present? ? out : nil
  end
  def build_links(item, links, action)
    trigger = content_tag(
      :div,
      class: 'btn dropdown-toggle',
      data: { toggle: 'dropdown' }
    ) do
      content_tag :span, '', class: 'fa fa-cog'
    end
    action_links = item.action_links
    if action_links.is_a?(AF83::Decorator::ActionLinks)
      menu = content_tag :div, class: 'dropdown-menu' do
        item.action_links(action).grouped_by(:primary, :secondary, :footer).map do |group, _links|
          if _links.any?
            content_tag :ul, class: group do
              _links.map{|link| gear_menu_link(link)}.join.html_safe
            end
          end
        end.join.html_safe
      end
    else
      menu = content_tag :ul, class: 'dropdown-menu' do
        (
          CustomLinks.new(item, pundit_user, links, referential, workgroup).links +
          action_links.select { |link| link.is_a?(Link) }
        ).map do |link|
          gear_menu_link(link)
        end.join.html_safe
      end
    end
    content_tag :div, trigger + menu, class: 'btn-group'
  end
  def build_column_header(
    column,
    table_is_sortable,
    model,
    params,
    sort_on,
    sort_direction
  )
    if !table_is_sortable || !column.sortable
      return column.header_label(model)
    end
    direction =
      if column.key.to_s == sort_on && sort_direction == 'desc'
        'asc'
      else
        'desc'
      end
    link_to(params.merge({direction: direction, sort: column.key})) do
      arrow_up = content_tag(
        :span,
        '',
        class: "fa fa-sort-asc #{direction == 'desc' ? 'active' : ''}"
      )
      arrow_down = content_tag(
        :span,
        '',
        class: "fa fa-sort-desc #{direction == 'asc' ? 'active' : ''}"
      )
      arrow_icons = content_tag :span, arrow_up + arrow_down, class: 'orderers'
      (
        column.header_label(model) +
        arrow_icons
      ).html_safe
    end
  end
  def checkbox(id_name:, value:, disabled: false)
    content_tag :div, '', class: 'checkbox' do
      check_box_tag(id_name, value, nil, disabled: disabled).concat(
        content_tag(:label, '', for: id_name)
      )
    end
  end
  def gear_menu_link(link)
    klass = []
    klass << link.extra_class if link.extra_class
    klass << 'delete-action' if link.method == :delete
    klass << 'disabled' if link.disabled
    content_tag(
      :li,
      link_to(
        link.disabled ? '#' : link.href,
        method: link.disabled ? nil : link.method,
        data: link.data,
        disabled: link.disabled
      ) do
        link.content
      end,
      class: (klass.join(' ') if klass.present?)
    )
  end
  def referential
    # Certain controllers don't define a `#current_referential`. In these
    # cases, avoid a `NoMethodError`.
    @__referential__ ||= try(:current_referential)
  end
  def workgroup
    # Certain controllers don't define a `#current_referential`. In these
    # cases, avoid a `NoMethodError`.
    @__workgroup__ ||= try(:current_workgroup)
  end
end
 |