# frozen_string_literal: true

#########
#
# CollectionBuilder Page Generator, v1.3-csv
#
# Jekyll plugin to generate pages from _data/ files.
# Designed to create Item pages from metadata CSV for digital collection sites.
# (c) 2021 CollectionBuilder, evanwill, https://github.com/CollectionBuilder/
# Distributed under the conditions of the MIT license
# 
# Originally inspired by jekyll-datapage_gen, https://github.com/avillafiorita/jekyll-datapage_gen
#
#########

module CollectionBuilderPageGenerator
  class ItemPageGenerator < Jekyll::Generator
    safe true
    # include jekyll utils so can use slugify
    include Jekyll::Utils

    # main function to read config, data, and generate pages
    def generate(site)

      #########
      #
      # Default Settings
      # 
      # These values are used if not configured in the 'page_gen' object in _config.yml
      # Defaults follow CollectionBuilder specific conventions.
      #
      data_file_default = site.config['metadata'] || 'metadata' # _data to use
      template_location_default = "item/" # folder in _layouts used to organize templates, ends with slash, empty if using root
      template_default = 'item' # layout to use for all pages by default
      display_template_default = 'display_template' # metadata column to use to assign layout
      name_default = 'objectid' # value to use for filename
      dir_default = 'items' # where to output pages
      extension_default = 'html' # extension, usually html
      filter_default = 'objectid' # value to filter records on, filters on objectid by default
      filter_condition_default = '!record["parentid"]' # expression to filter records on, default filters rows with a parentid
      #
      ######

      # get optional configuration from _config.yml, or create a single default one from CB metadata setting
      configure_gen = site.config['page_gen'] || [{ 'data' => data_file_default }]

      # iterate over each instance in configuration
      # this allows to generate from multiple _data sources
      configure_gen.each do |data_config|
        data_file = data_config['data'] || data_file_default
        template_location = data_config['template_location'] || template_location_default
        template = template_location + (data_config['template'] || template_default)
        display_template = data_config['display_template'] || display_template_default
        name = data_config['name'] || name_default
        dir = data_config['dir'] || dir_default
        extension = data_config['extension'] || extension_default
        filter = data_config['filter'] || filter_default
        filter_condition = data_config['filter_condition'] || filter_condition_default

        # check if data value uses .csv extension, if so provide error message and skip. This avoids common CB error.
        if data_file.split('.')[1] == "csv"
          puts color_text("Error cb_page_gen: metadata value '#{data_file}' includes '.csv' extension. Please remove the extension from _config.yml 'metadata' or page_gen 'data' value. Pages are NOT being generated from '#{data_file}'!", :red)
          next
        end
        # check if data file exists, if not provide error message and skip. This supports nested key yml or json data sources
        if !site.data.key? data_file.split('.')[0]
          puts color_text("Error cb_page_gen: Data value '#{data_file}' does not match any site data. Please check _config.yml 'metadata' or page_gen 'data' value. Common issues are spelling errors. Pages are NOT being generated from '#{data_file}'!", :red)
          next
        end

        # Get the records to generate pages from
        # note: this splits on . to support a nested key in yml or json for page gen.
        # however, CB template and other plugins do not support nested yml or json for metadata items. 
        # items described in unnested yml or json will work for both page gen and CB template pages.
        records = nil
        data_file.split('.').each do |level|
          if records.nil?
            records = site.data[level]
          else
            records = records[level]
          end
        end

        # Filter records if filter is configured (default is on objectid)
        if filter
          filtered_records = records.select { |r| r[filter] }
          filtered_number = records.size - filtered_records.size
          # provide notice if filter is applied
          puts color_text("Notice cb_page_gen: filter on '#{filter}' is applied. #{filtered_number} records are filtered because they do not have a value in '#{filter}'.", :green) if filtered_number != 0 
          records = filtered_records
        end
        # Filter records if filter_condition is configured
        if filter_condition
          filtered_records = records.select { |record| eval(filter_condition) } 
          filtered_number = records.size - filtered_records.size
          records = filtered_records
          # provide notice if non-default filter is applied
          if filter_condition != filter_condition_default 
            puts color_text("Notice cb_page_gen: filter_condition '#{filter_condition}' is applied. #{filtered_number} records are filtered.", :green) if filtered_number != 0 
          end
        end

        # Check for unique names, if not provide error message
        names_test = records.map { |x| x[name] }
        if names_test.size != names_test.uniq.size 
          puts color_text("Error cb_page_gen: some values in '#{name}' are not unique! This means those pages will overwrite each other, so you will be missing some Item pages. Please check '#{name}' and make them all unique.", :red) 
        end

        # Check for missing layouts
        template_test = records.map { |x| x[display_template] ? template_location + x[display_template].strip : template }.uniq
        all_layouts = site.layouts.keys
        missing_layouts = (template_test - all_layouts)
        if !missing_layouts.empty? # if there is missing layouts
          if all_layouts.include? template 
            # if there is a valid default layout fallback, continue
            puts color_text("Notice cb_page_gen: could not find layout(s) #{missing_layouts.join(', ')} in '_layouts/'. Records with these display_template will fallback to the default layout '#{template}'. If this is unexpected, please add the missing layout(s) or check configuration of 'template' and 'display_template' field.", :yellow)
          else
            # if there is no valid fallback / template
            puts color_text("Notice cb_page_gen: could not find layout(s) #{missing_layouts.join(', ')} in '_layouts/'. This includes the default layout '#{template}'. Please add the layout(s) or check configuration of 'template' and 'display_template'. Item pages will not be generated for records using the missing layouts!", :yellow)
            #next
          end
        end

        # Generate pages for each record
        records.each_with_index do |record, index|
          # Check for valid name, skip page gen if none
          if record[name].nil? || record[name].strip.empty?
            puts color_text("Notice cb_page_gen: record '#{index}' in '#{data_file}' does not have a value in '#{name}'! This record will be skipped.", :yellow)
            next
          end

          # create clean filename with Jekyll Slugify pretty mode
          # this ensures safe filenames, but may cause unintended issues with links if objectid are not well formed
          record['base_filename'] = slugify(record[name], mode: "pretty").to_s
          puts color_text("Notice cb_page_gen: record '#{index}' in '#{data_file}', '#{record[name]}' is being sanitized to create a valid filename. This may cause issues with links generated on other pages. Please check the naming convention used in '#{name}' field.", :yellow) if record['base_filename'] != record[name]

          # Provide index number for page object
          record['index_number'] = index 
          
          # Find next item 
          if index == records.size - 1
            next_item = records[0][name]
          else
            next_item = records[index + 1][name]
          end
          record['next_item'] = "/" + dir + "/" + slugify(next_item, mode: "pretty").to_s + "." + extension.to_s
          # Find previous item
          if index == 0
            previous_item = records[records.size - 1][name]
          else
            previous_item = records[index -1][name]
          end
          record['previous_item'] = "/" + dir + "/" + slugify(previous_item, mode: "pretty").to_s + "." + extension.to_s
          
          # Add layout value from display_template or the default
          if record[display_template]
            record['layout'] = template_location + record[display_template].strip
            # if not valid layout, fall back to template default
            if !all_layouts.include? record['layout']
              record['layout'] = template
            end
          else
            record['layout'] = template
          end
          # Check if layout exists, if not provide error message and skip
          if !all_layouts.include? record['layout']
            puts color_text("Error cb_page_gen: Could not find layout '#{record['layout']}'. Please check configuration or add the layout. Item page NOT generated for record '#{index}' in '#{data_file}'!", :red)
            next
          end

          # Pass the page data to the ItemPage generator
          site.pages << ItemPage.new(site, record, dir, extension)
        end
      end
    end

    # Color helper, to add warning colors to message outputs
    # use like: puts color_text("example", :red)
    def text_colors
      @colors = {
        red: 31,
        yellow: 33,
        green: 32
      }
    end
    def color_text(str, color_code)
      "\e[#{text_colors[color_code]}m#{str}\e[0m"
    end

  end

  # Subclass of `Jekyll::Page` with custom method definitions.
  class ItemPage < Jekyll::Page

    # function to generate each individual page
    def initialize(site, record, dir, extension)
      @site = site             # the current site instance.
      @base = site.source      # path to the source directory.
      @dir  = dir         # the directory the page will output in
      
      @basename = record['base_filename']  # filename without the extension.
      @ext      = "." + extension.to_s  # the extension.
      @name     = record['base_filename'] + "." + extension.to_s # @basename + @ext.

      # add record data to the page
      # all record data will be available in page object
      @data = record

    end
  end

end
