text: module.exports = function(Chart) {
  	/* eslint-disable global-require */
  	var chartHelpers = Chart.helpers;
  
  	var helpers = require('./helpers.js')(Chart);
  	var events = require('./events.js')(Chart);
  	/* eslint-enable global-require */
  
  	var annotationTypes = Chart.Annotation.types;
  
  	function setAfterDataLimitsHook(axisOptions) {
  		helpers.decorate(axisOptions, 'afterDataLimits', function(previous, scale) {
  			if (previous) {
  				previous(scale);
  			}
  			helpers.adjustScaleRange(scale);
  		});
  	}
  
  	function draw(drawTime) {
  		return function(chartInstance, easingDecimal) {
  			var defaultDrawTime = chartInstance.annotation.options.drawTime;
  
  			var elements = helpers.elements(chartInstance)
  				.filter(function(element) {
  					return drawTime === (element.options.drawTime || defaultDrawTime);
  				});
  
  			// Draw our box/line first, then the labels, so the labels will be on top.
  			elements.forEach(function(element) {
  				element.configure();
  				element.transition(easingDecimal).draw();
  			});
  			elements.forEach(function(element) {
  				element.transition(easingDecimal).drawLabel();
  			});
  		};
  	}
  
  	function getAnnotationConfig(chartOptions) {
  		var plugins = chartOptions.plugins;
  		var pluginAnnotation = plugins && plugins.annotation ? plugins.annotation : null;
  		return pluginAnnotation || chartOptions.annotation || {};
  	}
  
  	return {
  		beforeInit: function(chartInstance) {
  			var chartOptions = chartInstance.options;
  
  			// Initialize chart instance plugin namespace
  			var ns = chartInstance.annotation = {
  				elements: {},
  				options: helpers.initConfig(getAnnotationConfig(chartOptions)),
  				onDestroy: [],
  				firstRun: true,
  				supported: false
  			};
  
  			// Add the annotation scale adjuster to each scale's afterDataLimits hook
  			chartInstance.ensureScalesHaveIDs();
  			if (chartOptions.scales) {
  				ns.supported = true;
  				chartHelpers.each(chartOptions.scales.xAxes, setAfterDataLimitsHook);
  				chartHelpers.each(chartOptions.scales.yAxes, setAfterDataLimitsHook);
  			}
  		},
  		beforeUpdate: function(chartInstance) {
  			var ns = chartInstance.annotation;
  
  			if (!ns.supported) {
  				return;
  			}
  
  			if (!ns.firstRun) {
  				ns.options = helpers.initConfig(getAnnotationConfig(chartInstance.options));
  			} else {
  				ns.firstRun = false;
  			}
  
  			var elementIds = [];
  
  			// Add new elements, or update existing ones
  			ns.options.annotations.forEach(function(annotation) {
  				var id = annotation.id || helpers.objectId();
  
  				// No element with that ID exists, and it's a valid annotation type
  				if (!ns.elements[id] && annotationTypes[annotation.type]) {
  					var cls = annotationTypes[annotation.type];
  					var element = new cls({
  						id: id,
  						options: annotation,
  						chartInstance: chartInstance,
  					});
  					element.initialize();
  					ns.elements[id] = element;
  					annotation.id = id;
  					elementIds.push(id);
  				} else if (ns.elements[id]) {
  					// Nothing to do for update, since the element config references
  					// the same object that exists in the chart annotation config
  					elementIds.push(id);
  				}
  			});
  
  			// Delete removed elements
  			Object.keys(ns.elements).forEach(function(id) {
  				if (elementIds.indexOf(id) === -1) {
  					ns.elements[id].destroy();
  					delete ns.elements[id];
  				}
  			});
  		},
  		beforeDatasetsDraw: draw('beforeDatasetsDraw'),
  		afterDatasetsDraw: draw('afterDatasetsDraw'),
  		afterDraw: draw('afterDraw'),
  		afterInit: function(chartInstance) {
  			// Detect and intercept events that happen on an annotation element
  			var watchFor = chartInstance.annotation.options.events;
  			if (chartHelpers.isArray(watchFor) && watchFor.length > 0) {
  				var canvas = chartInstance.chart.canvas;
  				var eventHandler = events.dispatcher.bind(chartInstance);
  				events.collapseHoverEvents(watchFor).forEach(function(eventName) {
  					chartHelpers.addEvent(canvas, eventName, eventHandler);
  					chartInstance.annotation.onDestroy.push(function() {
  						chartHelpers.removeEvent(canvas, eventName, eventHandler);
  					});
  				});
  			}
  		},
  		destroy: function(chartInstance) {
  			var deregisterers = chartInstance.annotation.onDestroy;
  			while (deregisterers.length > 0) {
  				deregisterers.pop()();
  			}
  		}
  	};
  };
  