module Nanoc::Int
  # @api private
  class ConfigLoader
    class NoConfigFileFoundError < ::Nanoc::Error
      def initialize
        super('No configuration file found')
      end
    end

    class NoParentConfigFileFoundError < ::Nanoc::Error
      def initialize(filename)
        super("There is no parent configuration file at #{filename}")
      end
    end

    class CyclicalConfigFileError < ::Nanoc::Error
      def initialize(filename)
        super("The parent configuration file at #{filename} includes one of its descendants")
      end
    end

    # @return [Boolean]
    def self.cwd_is_nanoc_site?
      !config_filename_for_cwd.nil?
    end

    # @return [String]
    def self.config_filename_for_cwd
      filenames = %w(nanoc.yaml config.yaml)
      candidate = filenames.find { |f| File.file?(f) }
      candidate && File.expand_path(candidate)
    end

    def new_from_cwd
      # Determine path
      filename = self.class.config_filename_for_cwd
      raise NoConfigFileFoundError if filename.nil?

      # Read
      apply_parent_config(
        Nanoc::Int::Configuration.new(YAML.load_file(filename)),
        [filename],
      ).with_defaults
    end

    # @api private
    def apply_parent_config(config, processed_paths = [])
      parent_path = config[:parent_config_file]
      return config if parent_path.nil?

      # Get absolute path
      parent_path = File.absolute_path(parent_path, File.dirname(processed_paths.last))
      unless File.file?(parent_path)
        raise NoParentConfigFileFoundError.new(parent_path)
      end

      # Check recursion
      if processed_paths.include?(parent_path)
        raise CyclicalConfigFileError.new(parent_path)
      end

      # Load
      parent_config = Nanoc::Int::Configuration.new(YAML.load_file(parent_path))
      full_parent_config = apply_parent_config(parent_config, processed_paths + [parent_path])
      full_parent_config.merge(config.without(:parent_config_file))
    end
  end
end
