import { Component } from '../component';
import { Tools } from '../../tools';
import { DOMUtils } from '../../services';
import { ChartModel } from '../../model';
import { Events, TruncationTypes } from '../../interfaces';
import * as Configuration from '../../configuration';

// Carbon position service
import Position, { PLACEMENTS } from '@carbon/utils-position';

// import the settings for the css prefix
import settings from 'carbon-components/es/globals/js/settings';

// D3 Imports
import { select, mouse } from 'd3-selection';

export class Tooltip extends Component {
	type = 'tooltip';

	// flag for checking whether tooltip event listener is added or not
	isEventListenerAdded = false;
	tooltip: any;
	positionService = new Position();

	constructor(model: ChartModel, services: any, configs?: any) {
		super(model, services, configs);

		this.init();
	}

	handleShowTooltip = (e) => {
		const data = e.detail.data || e.detail.items;
		const defaultHTML = this.getTooltipHTML(e);

		const tooltipTextContainer = DOMUtils.appendOrSelect(
			this.tooltip,
			'div.content-box'
		);

		// if there is a provided tooltip HTML function call it
		if (Tools.getProperty(this.getOptions(), 'tooltip', 'customHTML')) {
			if (e.detail.content) {
				const labelHTML = `<div class="title-tooltip"><p>${e.detail.content}</p></div>`;
				tooltipTextContainer.html(labelHTML);
			} else {
				tooltipTextContainer.html(
					this.model
						.getOptions()
						.tooltip.customHTML(data, defaultHTML)
				);
			}
		} else {
			// Use default tooltip
			tooltipTextContainer.html(defaultHTML);
		}

		// Position the tooltip
		this.positionTooltip(e);

		// Fade in
		this.tooltip.classed('hidden', false).attr('aria-hidden', false);
	};

	handleHideTooltip = () => {
		this.tooltip.classed('hidden', true).attr('aria-hidden', true);
	};

	addTooltipEventListener() {
		// listen to move-tooltip Custom Events to move the tooltip
		this.services.events.addEventListener(
			Events.Tooltip.MOVE,
			(e: CustomEvent) => {
				this.positionTooltip(e);
			}
		);

		// listen to show-tooltip Custom Events to render the tooltip
		this.services.events.addEventListener(
			Events.Tooltip.SHOW,
			this.handleShowTooltip
		);

		// listen to hide-tooltip Custom Events to hide the tooltip
		this.services.events.addEventListener(
			Events.Tooltip.HIDE,
			this.handleHideTooltip
		);

		// listen to chart-mouseout event to hide the tooltip
		this.services.events.addEventListener(
			Events.Chart.MOUSEOUT,
			this.handleHideTooltip
		);
	}

	removeTooltipEventListener() {
		// remove move-tooltip Custom Events
		this.services.events.removeEventListener(Events.Tooltip.MOVE, null);

		// remove show-tooltip Custom Events
		this.services.events.removeEventListener(
			Events.Tooltip.SHOW,
			this.handleShowTooltip
		);

		// remove hide-tooltip Custom Events
		this.services.events.removeEventListener(
			Events.Tooltip.HIDE,
			this.handleHideTooltip
		);

		// remove the listener on chart-mouseout
		this.services.events.removeEventListener(
			Events.Chart.MOUSEOUT,
			this.handleHideTooltip
		);
	}

	getItems(e: CustomEvent) {
		if (e.detail.items) {
			return e.detail.items;
		}

		return [];
	}

	formatItems(items) {
		const options = this.getOptions();

		// get user provided custom values for truncation
		const truncationType = Tools.getProperty(
			options,
			'tooltip',
			'truncation',
			'type'
		);

		const truncationThreshold = Tools.getProperty(
			options,
			'tooltip',
			'truncation',
			'threshold'
		);

		const truncationNumCharacter = Tools.getProperty(
			options,
			'tooltip',
			'truncation',
			'numCharacter'
		);

		// truncate the label if it's too long
		// only applies to discrete type
		if (truncationType !== TruncationTypes.NONE) {
			return items.map((item) => {
				item.value = item.value
					? this.valueFormatter(item.value)
					: item.value;
				if (item.label && item.label.length > truncationThreshold) {
					item.label = Tools.truncateLabel(
						item.label,
						truncationType,
						truncationNumCharacter
					);
				}

				if (item.value && item.value.length > truncationThreshold) {
					item.value = Tools.truncateLabel(
						item.value,
						truncationType,
						truncationNumCharacter
					);
				}

				return item;
			});
		}

		return items;
	}

	getTooltipHTML(e: CustomEvent) {
		let defaultHTML;
		if (e.detail.content) {
			defaultHTML = `<div class="title-tooltip"><p>${e.detail.content}</p></div>`;
		} else {
			const items = this.getItems(e);
			const formattedItems = this.formatItems(items);

			defaultHTML =
				`<ul class='multi-tooltip'>` +
				formattedItems
					.map(
						(item) =>
							`<li>
							<div class="datapoint-tooltip ${item.bold ? 'bold' : ''}">
								${item.class ? `<a class="tooltip-color ${item.class}"></a>` : ''}
								${
									item.color
										? '<a style="background-color: ' +
										  item.color +
										  '" class="tooltip-color"></a>'
										: ''
								}
								<p class="label">${item.label || ''}</p>
								<p class="value">${item.value || ''}</p>
							</div>
						</li>`
					)
					.join('') +
				`</ul>`;
		}

		return defaultHTML;
	}

	valueFormatter(value: any) {
		const options = this.getOptions();
		const valueFormatter = Tools.getProperty(
			options,
			'tooltip',
			'valueFormatter'
		);

		if (valueFormatter) {
			return valueFormatter(value);
		}

		return value.toLocaleString();
	}

	render() {
		const options = this.getOptions();
		const isTooltipEnabled = Tools.getProperty(
			options,
			'tooltip',
			'enabled'
		);
		if (isTooltipEnabled) {
			// Grab the tooltip element
			const holder = select(this.services.domUtils.getHolder());
			const chartprefix = Tools.getProperty(options, 'style', 'prefix');
			this.tooltip = DOMUtils.appendOrSelect(
				holder,
				`div.${settings.prefix}--${chartprefix}--tooltip`
			);

			this.tooltip.style('max-width', null);
			if (!this.isEventListenerAdded) {
				this.addTooltipEventListener();
				this.isEventListenerAdded = true;
			}
			this.tooltip.classed('hidden', true);
		} else if (!isTooltipEnabled && this.isEventListenerAdded) {
			// remove tooltip eventListener
			this.removeTooltipEventListener();
			this.isEventListenerAdded = false;
		}
	}

	positionTooltip(e: CustomEvent) {
		const holder = this.services.domUtils.getHolder();
		const target = this.tooltip.node();
		const options = this.getOptions();
		const isTopZoomBarEnabled = Tools.getProperty(
			options,
			'zoomBar',
			'top',
			'enabled'
		);

		let mouseRelativePos = Tools.getProperty(e, 'detail', 'mousePosition');
		if (!mouseRelativePos) {
			mouseRelativePos = mouse(holder);
		} else {
			const zoombarType = Tools.getProperty(
				options,
				'zoomBar',
				'top',
				'type'
			);
			const zoombarHeight = Configuration.zoomBar.height[zoombarType];

			// if the mouse position is from event (ruler)
			// we need add zoom bar height
			if (isTopZoomBarEnabled) {
				mouseRelativePos[1] +=
					zoombarHeight + Configuration.zoomBar.spacerHeight;

				// TODO - we need to add toolbar height when toolbar is available
			}
		}

		let pos;

		// Find out whether tooltip should be shown on the left or right side
		const bestPlacementOption = this.positionService.findBestPlacementAt(
			{
				left: mouseRelativePos[0],
				top: mouseRelativePos[1],
			},
			target,
			[
				PLACEMENTS.RIGHT,
				PLACEMENTS.LEFT,
				PLACEMENTS.TOP,
				PLACEMENTS.BOTTOM,
			],
			() => ({
				width: holder.offsetWidth,
				height: holder.offsetHeight,
			})
		);

		let { horizontalOffset } = Configuration.tooltips;
		if (bestPlacementOption === PLACEMENTS.LEFT) {
			horizontalOffset *= -1;
		}

		// Get coordinates to where tooltip should be positioned
		pos = this.positionService.findPositionAt(
			{
				left: mouseRelativePos[0] + horizontalOffset,
				top: mouseRelativePos[1],
			},
			target,
			bestPlacementOption
		);

		this.positionService.setElement(target, pos);
	}
}
