require 'pdk'
require 'pdk/template/renderer'

module PDK
  module Template
    module Renderer
      module V1
        class Renderer < PDK::Template::Renderer::AbstractRenderer
          # @see PDK::Template::Renderer::AbstractRenderer.render
          def render(template_type, _name, options = {})
            render_module(options) { |*args| yield(*args) } if template_type == PDK::Template::MODULE_TEMPLATE_TYPE
          end

          # @see PDK::Template::Renderer::AbstractRenderer.has_single_item?
          def has_single_item?(item_path) # rubocop:disable Naming/PredicateName
            PDK::Util::Filesystem.exist?(single_item_path(item_path))
          end

          # @see PDK::Template::Renderer::AbstractRenderer.render_single_item
          def render_single_item(relative_file_path, template_data_hash)
            template_file = single_item_path(relative_file_path)
            return nil unless PDK::Util::Filesystem.file?(template_file) && PDK::Util::Filesystem.readable?(template_file)

            PDK.logger.debug(format("Rendering '%{template}'...", template: template_file))
            new_template_file(template_file, template_data_hash).render
          end

          # Returns the full path for a single item
          #
          # @param item_path [String] The path of the single item to render
          # @return [String]
          # @api private
          # :nocov:
          def single_item_path(item_path)
            File.join(template_root, 'object_templates', item_path)
          end
          # :nocov:

          # Helper method used during testing
          # :nocov:
          # @api private
          def new_template_file(template_file, template_data_hash)
            TemplateFile.new(template_file, template_data_hash)
          end
          # :nocov:

          # Helper method used during testing
          # :nocov:
          # @api private
          def new_legacy_template_dir(context, uri, path, module_metadata = {})
            LegacyTemplateDir.new(context, uri, path, module_metadata)
          end
          # :nocov:

          # Renders a new module
          #
          # @param options [Hash{Object => Object}] A list of options to pass through to the renderer. See PDK::Template::TemplateDir helper methods for other options
          # @see #render
          # @api private
          # :nocov: This is tested in acceptance and packaging tests
          def render_module(options = {})
            require 'pdk/template/renderer/v1/template_file'

            moduleroot_dir = File.join(template_root, 'moduleroot')
            moduleroot_init = File.join(template_root, 'moduleroot_init')

            dirs = [moduleroot_dir]
            dirs << moduleroot_init if options[:include_first_time]

            legacy_template_dir = new_legacy_template_dir(context, template_uri, template_root, options[:module_metadata] || {})

            files_in_template(dirs).each do |template_file, template_loc|
              template_file = template_file.to_s
              PDK.logger.debug(format("Rendering '%{template}'...", template: template_file))
              dest_path = template_file.sub(/\.erb\Z/, '')
              config = legacy_template_dir.config_for(dest_path)

              dest_status = if template_loc.start_with?(moduleroot_init)
                              :init
                            else
                              :manage
                            end

              if config['unmanaged']
                dest_status = :unmanage
              elsif config['delete']
                dest_status = :delete
              else
                begin
                  dest_content = new_template_file(File.join(template_loc, template_file), configs: config, template_dir: legacy_template_dir).render
                rescue StandardError => e
                  error_msg = format("Failed to render template '%{template}'\n" \
                                     '%{exception}: %{message}', template: template_file, exception: e.class, message: e.message)
                  raise PDK::CLI::FatalError, error_msg
                end
              end

              dest_executable = config['manage_execute_permissions'] && PDK::Util::Filesystem.stat(File.join(template_loc, template_file)).executable?

              yield dest_path, dest_content, dest_status, dest_executable
            end
          end
          # :nocov:

          # Returns all files in the given template directories
          #
          # @param dirs [Array[String]] Directories to search in
          # @param glob_suffix [Array[String]] File glob to use when searching for files. Defaults to ['**', '*']
          #
          # @return [Hash{String => String}] Key is the template file relative path and the value is the absolute path to the template directory
          # @api private
          def files_in_template(dirs, glob_suffix = ['**', '*'])
            temp_paths = []
            dirlocs = []
            dirs.each do |dir|
              raise ArgumentError, format("The directory '%{dir}' doesn't exist", dir: dir) unless PDK::Util::Filesystem.directory?(dir)

              temp_paths += PDK::Util::Filesystem.glob(File.join(dir, *glob_suffix), File::FNM_DOTMATCH).select do |template_path|
                dirlocs << dir if PDK::Util::Filesystem.file?(template_path) && !PDK::Util::Filesystem.symlink?(template_path)
              end
              temp_paths.map do |template_path|
                template_path.sub!(/\A#{Regexp.escape(dir)}#{Regexp.escape(File::SEPARATOR)}/, '')
              end
            end
            (temp_paths.zip dirlocs).to_h
          end
        end
      end
    end
  end
end
