text: 'use strict';
  
  module.exports = function(Chart) {
  	var helpers = Chart.helpers;
  
  	/**
  	 * Helper function to get relative position for an event
  	 * @param {Event|IEvent} event - The event to get the position for
  	 * @param {Chart} chart - The chart
  	 * @returns {Point} the event position
  	 */
  	function getRelativePosition(e, chart) {
  		if (e.native) {
  			return {
  				x: e.x,
  				y: e.y
  			};
  		}
  
  		return helpers.getRelativePosition(e, chart);
  	}
  
  	/**
  	 * Helper function to traverse all of the visible elements in the chart
  	 * @param chart {chart} the chart
  	 * @param handler {Function} the callback to execute for each visible item
  	 */
  	function parseVisibleItems(chart, handler) {
  		var datasets = chart.data.datasets;
  		var meta, i, j, ilen, jlen;
  
  		for (i = 0, ilen = datasets.length; i < ilen; ++i) {
  			if (!chart.isDatasetVisible(i)) {
  				continue;
  			}
  
  			meta = chart.getDatasetMeta(i);
  			for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
  				var element = meta.data[j];
  				if (!element._view.skip) {
  					handler(element);
  				}
  			}
  		}
  	}
  
  	/**
  	 * Helper function to get the items that intersect the event position
  	 * @param items {ChartElement[]} elements to filter
  	 * @param position {Point} the point to be nearest to
  	 * @return {ChartElement[]} the nearest items
  	 */
  	function getIntersectItems(chart, position) {
  		var elements = [];
  
  		parseVisibleItems(chart, function(element) {
  			if (element.inRange(position.x, position.y)) {
  				elements.push(element);
  			}
  		});
  
  		return elements;
  	}
  
  	/**
  	 * Helper function to get the items nearest to the event position considering all visible items in teh chart
  	 * @param chart {Chart} the chart to look at elements from
  	 * @param position {Point} the point to be nearest to
  	 * @param intersect {Boolean} if true, only consider items that intersect the position
  	 * @param distanceMetric {Function} Optional function to provide the distance between
  	 * @return {ChartElement[]} the nearest items
  	 */
  	function getNearestItems(chart, position, intersect, distanceMetric) {
  		var minDistance = Number.POSITIVE_INFINITY;
  		var nearestItems = [];
  
  		if (!distanceMetric) {
  			distanceMetric = helpers.distanceBetweenPoints;
  		}
  
  		parseVisibleItems(chart, function(element) {
  			if (intersect && !element.inRange(position.x, position.y)) {
  				return;
  			}
  
  			var center = element.getCenterPoint();
  			var distance = distanceMetric(position, center);
  
  			if (distance < minDistance) {
  				nearestItems = [element];
  				minDistance = distance;
  			} else if (distance === minDistance) {
  				// Can have multiple items at the same distance in which case we sort by size
  				nearestItems.push(element);
  			}
  		});
  
  		return nearestItems;
  	}
  
  	function indexMode(chart, e, options) {
  		var position = getRelativePosition(e, chart.chart);
  		var distanceMetric = function(pt1, pt2) {
  			return Math.abs(pt1.x - pt2.x);
  		};
  		var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
  		var elements = [];
  
  		if (!items.length) {
  			return [];
  		}
  
  		chart.data.datasets.forEach(function(dataset, datasetIndex) {
  			if (chart.isDatasetVisible(datasetIndex)) {
  				var meta = chart.getDatasetMeta(datasetIndex),
  					element = meta.data[items[0]._index];
  
  				// don't count items that are skipped (null data)
  				if (element && !element._view.skip) {
  					elements.push(element);
  				}
  			}
  		});
  
  		return elements;
  	}
  
  	/**
  	 * @interface IInteractionOptions
  	 */
  	/**
  	 * If true, only consider items that intersect the point
  	 * @name IInterfaceOptions#boolean
  	 * @type Boolean
  	 */
  
  	/**
  	 * Contains interaction related functions
  	 * @namespace Chart.Interaction
  	 */
  	Chart.Interaction = {
  		// Helper function for different modes
  		modes: {
  			single: function(chart, e) {
  				var position = getRelativePosition(e, chart.chart);
  				var elements = [];
  
  				parseVisibleItems(chart, function(element) {
  					if (element.inRange(position.x, position.y)) {
  						elements.push(element);
  						return elements;
  					}
  				});
  
  				return elements.slice(0, 1);
  			},
  
  			/**
  			 * @function Chart.Interaction.modes.label
  			 * @deprecated since version 2.4.0
  			 */
  			label: indexMode,
  
  			/**
  			 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
  			 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
  			 * @function Chart.Interaction.modes.index
  			 * @since v2.4.0
  			 * @param chart {chart} the chart we are returning items from
  			 * @param e {Event} the event we are find things at
  			 * @param options {IInteractionOptions} options to use during interaction
  			 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  			 */
  			index: indexMode,
  
  			/**
  			 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
  			 * If the options.intersect is false, we find the nearest item and return the items in that dataset
  			 * @function Chart.Interaction.modes.dataset
  			 * @param chart {chart} the chart we are returning items from
  			 * @param e {Event} the event we are find things at
  			 * @param options {IInteractionOptions} options to use during interaction
  			 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  			 */
  			dataset: function(chart, e, options) {
  				var position = getRelativePosition(e, chart.chart);
  				var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false);
  
  				if (items.length > 0) {
  					items = chart.getDatasetMeta(items[0]._datasetIndex).data;
  				}
  
  				return items;
  			},
  
  			/**
  			 * @function Chart.Interaction.modes.x-axis
  			 * @deprecated since version 2.4.0. Use index mode and intersect == true
  			 */
  			'x-axis': function(chart, e) {
  				return indexMode(chart, e, true);
  			},
  
  			/**
  			 * Point mode returns all elements that hit test based on the event position
  			 * of the event
  			 * @function Chart.Interaction.modes.intersect
  			 * @param chart {chart} the chart we are returning items from
  			 * @param e {Event} the event we are find things at
  			 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  			 */
  			point: function(chart, e) {
  				var position = getRelativePosition(e, chart.chart);
  				return getIntersectItems(chart, position);
  			},
  
  			/**
  			 * nearest mode returns the element closest to the point
  			 * @function Chart.Interaction.modes.intersect
  			 * @param chart {chart} the chart we are returning items from
  			 * @param e {Event} the event we are find things at
  			 * @param options {IInteractionOptions} options to use
  			 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  			 */
  			nearest: function(chart, e, options) {
  				var position = getRelativePosition(e, chart.chart);
  				var nearestItems = getNearestItems(chart, position, options.intersect);
  
  				// We have multiple items at the same distance from the event. Now sort by smallest
  				if (nearestItems.length > 1) {
  					nearestItems.sort(function(a, b) {
  						var sizeA = a.getArea();
  						var sizeB = b.getArea();
  						var ret = sizeA - sizeB;
  
  						if (ret === 0) {
  							// if equal sort by dataset index
  							ret = a._datasetIndex - b._datasetIndex;
  						}
  
  						return ret;
  					});
  				}
  
  				// Return only 1 item
  				return nearestItems.slice(0, 1);
  			},
  
  			/**
  			 * x mode returns the elements that hit-test at the current x coordinate
  			 * @function Chart.Interaction.modes.x
  			 * @param chart {chart} the chart we are returning items from
  			 * @param e {Event} the event we are find things at
  			 * @param options {IInteractionOptions} options to use
  			 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  			 */
  			x: function(chart, e, options) {
  				var position = getRelativePosition(e, chart.chart);
  				var items = [];
  				var intersectsItem = false;
  
  				parseVisibleItems(chart, function(element) {
  					if (element.inXRange(position.x)) {
  						items.push(element);
  					}
  
  					if (element.inRange(position.x, position.y)) {
  						intersectsItem = true;
  					}
  				});
  
  				// If we want to trigger on an intersect and we don't have any items
  				// that intersect the position, return nothing
  				if (options.intersect && !intersectsItem) {
  					items = [];
  				}
  				return items;
  			},
  
  			/**
  			 * y mode returns the elements that hit-test at the current y coordinate
  			 * @function Chart.Interaction.modes.y
  			 * @param chart {chart} the chart we are returning items from
  			 * @param e {Event} the event we are find things at
  			 * @param options {IInteractionOptions} options to use
  			 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  			 */
  			y: function(chart, e, options) {
  				var position = getRelativePosition(e, chart.chart);
  				var items = [];
  				var intersectsItem = false;
  
  				parseVisibleItems(chart, function(element) {
  					if (element.inYRange(position.y)) {
  						items.push(element);
  					}
  
  					if (element.inRange(position.x, position.y)) {
  						intersectsItem = true;
  					}
  				});
  
  				// If we want to trigger on an intersect and we don't have any items
  				// that intersect the position, return nothing
  				if (options.intersect && !intersectsItem) {
  					items = [];
  				}
  				return items;
  			}
  		}
  	};
  };
  