text: 'use strict';
  
  const Hoek = require('@hapi/hoek');
  const Validate = require('@hapi/validate');
  
  const Schemas = require('./schemas');
  const Manager = require('./manager');
  
  // Additional helper modules required in constructor
  
  
  const internals = {};
  
  
  exports.plugin = {
      multiple: true,
      pkg: require('../package.json'),
      requirements: {
          hapi: '>=18.4.0'
      },
  
      register: function (server, options) {
  
          if (Object.keys(options).length > 0) {
              internals.assignManager.call(server, options, server.realm.parent);
          }
  
          const rootRealm = internals.getRootRealm(server.realm);
          const rootState = internals.state(rootRealm);
  
          if (rootState.setup) {
              return;
          }
  
          server.decorate('server', 'views', internals.assignManager);
          server.decorate('server', 'render', internals.render);
          server.decorate('server', 'getViewsManager', internals.getViewsManager);
          server.decorate('request', 'render', internals.render);
          server.decorate('request', 'getViewsManager', internals.getViewsManager);
          server.decorate('handler', 'view', internals.handler);
          server.decorate('toolkit', 'view', internals.toolkitView);
          server.decorate('toolkit', 'getViewsManager', internals.getViewsManager);
  
          rootState.setup = true;
      }
  };
  
  
  internals.getViewsManager = function () {
  
      let realm;
  
      // The root server has a 'route' func
      if (this.route && typeof this.route !== 'function') {
          realm = this.route.realm;
      }
      else if (this.request) {
          realm = this.request.route.realm;
      }
      else {
          realm = this.realm;
      }
  
      return internals.nearestManager(realm);
  };
  
  
  internals.assignManager = function (options, realm) {
  
      realm = realm || this.realm;
      const realmState = internals.state(realm);
  
      Hoek.assert(!realmState.manager, 'Cannot set views manager more than once per realm');
  
      if (!options.relativeTo &&
          realm.settings.files.relativeTo) {
  
          options = Object.assign({}, options);                   // Shallow cloned
          options.relativeTo = realm.settings.files.relativeTo;
      }
  
      const manager = new Manager(this, options);
      realmState.manager = manager;
      return manager;
  };
  
  
  internals.render = async function (template, context, options = {}) {
  
      const isServer = (typeof this.route === 'function');
      const realm = (isServer ? this.realm : this.route.realm);
  
      const manager = internals.nearestManager(realm);
      Hoek.assert(manager, 'Missing views manager');
  
      if (!isServer) {
          // this is the request
          return await manager.render(template, context, options, this);
      }
  
      return await manager.render(template, context, options);
  };
  
  
  internals.toolkitView = function (template, context, options) {
  
      const manager = internals.nearestManager(this.realm);
  
      Hoek.assert(manager, 'Missing views manager');
      return this.response(manager._response(template, context, options, this.request));
  };
  
  
  internals.handler = function (route, options) {
  
      Validate.assert(options, Schemas.handler, 'Invalid view handler options (' + route.path + ')');
  
      if (typeof options === 'string') {
          options = { template: options };
      }
  
      const settings = {                                                // Shallow copy to allow making dynamic changes to context
          template: options.template,
          context: options.context,
          options: options.options
      };
  
      return function (request, h) {
  
          const context = {
              params: request.params,
              payload: request.payload,
              query: request.query,
              pre: request.pre
          };
  
          if (settings.context) {                                     // Shallow copy to avoid cloning unknown objects
              const keys = Object.keys(settings.context);
              for (let i = 0; i < keys.length; ++i) {
                  const key = keys[i];
                  context[key] = settings.context[key];
              }
          }
  
          return h.view(settings.template, context, settings.options);
      };
  };
  
  
  internals.state = (realm) => {
  
      const state = realm.plugins.vision = realm.plugins.vision || {};
      return state;
  };
  
  
  internals.nearestManager = function (realm) {
  
      const pluginState = internals.state(realm);
      if (pluginState.manager) {
          return pluginState.manager;
      }
  
      let parent = realm.parent;
      while (parent) {
  
          const parentState = internals.state(parent);
          if (parentState.manager) {
              return parentState.manager;
          }
  
          parent = parent.parent;
      }
  
      return null;
  };
  
  
  internals.getRootRealm = (realm) => {
  
      while (realm.parent) {
          realm = realm.parent;
      }
  
      return realm;
  };
  