# frozen_string_literal: true

module Effective
  module EffectiveDatatable
    module Format
      BLANK = ''.freeze
      NONVISIBLE = '...'.freeze
      SPACER = 'EFFECTIVEDATATABLESSPACER'.freeze
      SPACER_TEMPLATE = '/effective/datatables/spacer_template'.freeze

      private

      def format(collection, csv: false)
        # We want to use the render :collection for each column that renders partials
        rendered = {}

        columns.each do |name, opts|
          if state[:visible][name] == false && !csv
            # Nothing to do
          elsif csv && !opts[:csv]
            # Nothing to do
          elsif opts[:partial]
            locals = { datatable: self, column: opts }.merge!(resource_col_locals(opts))

            rendered[name] = (view.render(
              partial: opts[:partial],
              as: (opts[:partial_as] || :resource),
              collection: collection.map { |row| row[opts[:index]] },
              formats: :html,
              locals: locals,
              spacer_template: SPACER_TEMPLATE
            ) || '').split(SPACER)

          elsif opts[:as] == :actions # This is actions_col and actions_col do .. end, but not actions_col partial: 'something'
            locals = { datatable: self, column: opts, spacer_template: SPACER_TEMPLATE }

            atts = {
              actions: actions_col_actions(opts),
              btn_class: opts[:btn_class],
              effective_resource: effective_resource,
              locals: locals,
              partial: opts[:actions_partial],
            }.merge!(opts[:actions]).tap(&:compact!)

            rendered[name] = if effective_resource.blank?
              collection.map { |row| row[opts[:index]] }.map do |resource|
                polymorphic_resource = Effective::Resource.new(resource, namespace: controller_namespace)
                (view.render_resource_actions(resource, atts.merge(effective_resource: polymorphic_resource), &opts[:format]) || '')
              end
            else
              (view.render_resource_actions(collection.map { |row| row[opts[:index]] }, atts, &opts[:format]) || '').split(SPACER)
            end
          end
        end

        collection.each_with_index do |row, row_index|
          columns.each do |name, opts|
            index = opts[:index]
            value = row[index]

            formatted = (
              if state[:visible][name] == false && !csv
                NONVISIBLE
              elsif csv && !opts[:csv]
                BLANK
              elsif opts[:as] == :actions
                rendered[name][row_index]
              elsif opts[:format] && rendered.key?(name)
                dsl_tool.instance_exec(value, row, rendered[name][row_index], &opts[:format])
              elsif opts[:format]
                dsl_tool.instance_exec(value, row, &opts[:format]).to_s
              elsif opts[:partial]
                rendered[name][row_index]
              else
                format_column(value, opts, csv: csv)
              end
            )

            if csv && (opts[:format] || opts[:partial]) && !formatted.frozen?
              html_content = formatted.include?('<') && formatted.include?('>')

              formatted.gsub!("&nbsp;", ' ')
              formatted.gsub!("<br>\n", ' ')
              formatted.gsub!("<br> ", ' ')
              formatted.gsub!("<br>", ' ')
              formatted.gsub!("<br/>", ' ')
              formatted.gsub!("<br />", ' ')
              formatted.gsub!("\n", ' ')

              if html_content
                # formatted.gsub!(/<span[^>]*\bclass=["']?badge\b[^>]*>.*?<\/span>/, '')
                formatted.gsub!('</div>', "</div>\n") if formatted.include?('col-resource_item')
                formatted = view.strip_tags(formatted).strip
              end

              formatted.gsub!('  ', ' ')
            end

            row[index] = formatted
          end
        end
      end

      # Must return a string
      def format_column(value, column, as: nil, csv: false)
        return if value.nil? || (column[:resource] && value.blank?)

        if value.kind_of?(Array) && [:string, :text].include?(column[:as])
          if csv
            return value.map { |v| format_column(v, column, csv: csv) }.join("\n")
          else
            return value.map { |v| view.content_tag(:div, format_column(v, column, as: :string, csv: csv).html_safe, class: 'col-resource_item') }.join.html_safe
          end
        end

        case (as || column[:as])
        when :actions
          raise("please use actions_col instead of col(#{name}, as: :actions)")
        when :boolean
          label = view.t("effective_datatables.boolean_#{value}")

          if csv
            label.to_s
          else
            color = value ? EffectiveDatatables.format_true : EffectiveDatatables.format_false
            color.present? ? view.badge(label, color) : label
          end
        when :currency
          view.number_to_currency(value)
        when :date
          value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_date) : BLANK
        when :datetime
          if csv
            value.to_s
          else
            value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_datetime) : BLANK
          end
        when :decimal
          value.to_s
        when :duration
          view.number_to_duration(value)
        when :effective_addresses
          csv ? value.to_html.gsub('<br>', '').gsub("&nbsp;", ' ') : value.to_html
        when :effective_obfuscation
          value.to_s
        when :effective_roles
          csv ? value.join(', ') : view.roles_badges(value)
        when :email
          csv ? value : view.mail_to(value)
        when :integer
          value.to_s
        when :percent, :percentage
          case value
          when Integer    ; view.number_to_percentage(value / 1000.0, precision: 3).gsub('.000%', '%')
          when Numeric    ; view.number_to_percentage(value, precision: 3).gsub('.000%', '%')
          end
        when :price
          case value
          when Integer    ; view.number_to_currency(value / 100.0) # an Integer representing the number of cents
          when Numeric    ; view.number_to_currency(value)
          end
        when :time
          value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_time) : BLANK
        when :text
          value_to_s = value.to_s

          if csv
            value_to_s
          elsif value_to_s.include?('<') && value_to_s.include?('>')
            view.sanitize(value_to_s)
          else
            view.simple_format(value_to_s)
          end
        when :string
          value_to_s = value.to_s

          if csv
            value_to_s
          elsif value_to_s.include?('<') && value_to_s.include?('>')
            view.sanitize(value_to_s)
          else
            view.escape_once(value_to_s)
          end
        else
          value.to_s
        end
      end

      # Takes all default resource actions
      # Applies data-remote to anything that's data-method post or delete
      # Merges in any extra attributes when passed as a Hash
      def actions_col_actions(column)
        resource_actions = (effective_resource&.resource_actions || fallback_effective_resource.fallback_resource_actions)

        actions = if inline?
          resource_actions.transform_values { |opts| opts['data-remote'] = true; opts }
        else
          resource_actions.transform_values { |opts| opts['data-remote'] = true if opts['data-method']; opts }
        end

        # Merge local options. Special behaviour for remote: false
        if column[:actions].present? && column[:actions].kind_of?(Hash)
          column[:actions].each do |action, opts|
            next unless opts.kind_of?(Hash)

            existing = actions.find { |_, v| v[:action] == action }&.first
            next unless existing.present?

            actions[existing]['data-remote'] = opts[:remote] if opts.key?(:remote)
            actions[existing]['data-remote'] = opts['remote'] if opts.key?('remote')

            actions[existing].merge!(opts.except(:remote, 'remote'))
          end

          actions = actions.sort do |(_, a), (_, b)|
            (column[:actions].keys.index(a[:action]) || 99) <=> (column[:actions].keys.index(b[:action]) || 99)
          end.to_h

        end

        actions
      end

      def resource_col_locals(opts)
        return {} unless (associated_resource = opts[:resource]).present?

        associated = associated_resource.macros.include?(opts[:as])
        polymorphic = (opts[:as] == :belongs_to_polymorphic)

        resource_name = opts[:name] if associated
        resource_to_s = opts[:name] unless associated || array_collection?

        locals = {
          resource_name: resource_name,
          resource_to_s: resource_to_s,
          effective_resource: associated_resource,
          format_each: opts[:format_each],
          show_action: false,
          edit_action: false
        }

        case opts[:action]
        when :edit
          locals[:edit_action] = (polymorphic || associated_resource.routes[:edit].present?)
        when :show
          locals[:show_action] = (polymorphic || associated_resource.routes[:show].present?)
        when false
          # Nothing. Already false.
        else
          locals[:edit_action] = (polymorphic || associated_resource.routes[:edit].present?)
          locals[:show_action] = (polymorphic || associated_resource.routes[:show].present?)
        end

        locals
      end

    end # / Rendering
  end
end
