require 'pdk'

module PDK
  module Template
    module Renderer
      autoload :V1, 'pdk/template/renderer/v1'

      # Returns the most appropriate renderer for the given Template Directory and Context
      #
      # @param template_root [String] The path to where the template exists on disk
      # @param template_uri [PDK::Util::TemplateUri] A URI which points to the source location of the Template
      # @param context [PDK::Context] The context in which the redering will occur in
      #
      # @return [AbstractRenderer, nil] An instance of an AbstractRenderer subclass. Returns nil if no renderer could be found
      def self.instance(template_uri, template_root, context)
        return V1.instance(template_root, template_uri, context) if V1.compatible?(template_root, context)

        nil
      end

      # An abstract class which all Template Renderers should subclass and implement. This class is responsible for
      # rendering a template or a single item within a template directory
      #
      # To implement a new renderer:
      # 1. Create a new class which subclasses AbstractRenderer and implements the public methods (has_single_item?, render and render_single_item)
      # 2. Add class methods .compatible? and .instance which are used to detect if a template is compatible with the new renderer
      #    and create an instance of the new renderer respectively
      # 3. Update the PDK::Template::Renderer.instance method to detect and create an instance of the new renderer (using the .compatible? and .instance methods
      #    created in step 2).
      #
      # See the PDK::Template::Renderer::V1 module and classes for an example on how to to this.
      #
      # @api private
      # @abstract
      class AbstractRenderer
        # @return [String] The path to where the template exists on disk
        attr_reader :template_root

        # @return [PDK::Util::TemplateURI] The URI which points to the source location of the Template
        attr_reader :template_uri

        # @return context [PDK::Context] The context in which the redering will occur in
        attr_reader :context

        # @param template_root [String] The path to where the template exists on disk
        # @param template_uri [PDK::Util::TemplateUri] A URI which points to the source location of the Template
        # @param context [PDK::Context] The context in which the redering will occur in
        def initialize(template_root, template_uri, context)
          @template_root = template_root
          @template_uri = template_uri
          @context = context
        end

        # Whether the renderer supports rendering the a single item called 'item_path'. This is used when rendering things like a new Task or a new Puppet Classes.
        # Rendering a single item is different than redering an entire project, like a entire Puppet Module or Control Repo.
        #
        # @param item_path [String] The path to the item to check
        #
        # @return [Boolean] Whether the renderer can render the item
        # @abstract
        def has_single_item?(_item_path) # rubocop:disable Naming/PredicateName Changing the method name to `single_item?` will convey the wrong intent
          false
        end

        # Loop through the files in the template type, yielding each rendered file to the supplied block.
        #
        # @param template_type [PDK::Template::*_TEMPLATE_TYPE] The type of template to render
        # @param name [String] The name to use in the rendering process
        # @param options [Hash{Object => Object}] A list of options to pass through to the renderer. See PDK::Template::TemplateDir helper methods for other options
        # @option options [Boolean] :include_first_time Whether to include "first time" items when rendering the project. While it is up to the renderer to implement this
        #                                               the expected behavior is that if the item already exists, it will not be rendererd again.  Unlike normal items which
        #                                               are always rendered to keep them in-sync
        #
        # @yieldparam dest_path [String] The path of the destination file, relative to the root of the context.
        # @yieldparam dest_content [String] The rendered content of the destination file.
        # @yieldparam dest_status [Symbol] :unmanage, :delete, :init, :manage
        #
        # @see PDK::Template::TemplateDir.render
        #
        # @return [void]
        # @abstract
        def render(template_type, name, options = {}); end

        # Render a single item and return the resulting string. This is used when rendering things like a new Task or a new Puppet Classes.
        # Rendering a single item is different than redering an entire project, like a entire Puppet Module or Control Repo. This method is
        # used in conjunction with .has_single_item?
        #
        # @param item_path [String] The path of the single item to render
        # @param template_data_hash [Hash{Object => Object}] A hash of information which will be used in the rendering process
        #
        # @return [String, Nil] The rendered content, or nil of the file could not be rendered
        # @abstract
        def render_single_item(item_path, template_data_hash = {}); end
      end
    end
  end
end
