// Internal Imports
import { Component } from '../component';
import * as Configuration from '../../configuration';
import {
	Roles,
	ScaleTypes,
	Events,
	ColorClassNameTypes,
	CartesianOrientations,
} from '../../interfaces';

// D3 Imports
import { area } from 'd3-shape';

export class StackedArea extends Component {
	type = 'area-stacked';

	areaGenerator: any;

	init() {
		const eventsFragment = this.services.events;

		// Highlight correct area on legend item hovers
		eventsFragment.addEventListener(
			Events.Legend.ITEM_HOVER,
			this.handleLegendOnHover
		);

		// Un-highlight area on legend item mouseouts
		eventsFragment.addEventListener(
			Events.Legend.ITEM_MOUSEOUT,
			this.handleLegendMouseOut
		);
	}

	render(animate = true) {
		const svg = this.getContainerSVG({ withinChartClip: true });
		const self = this;
		const options = this.getOptions();
		const { groupMapsTo } = options.data;

		const percentage = Object.keys(options.axes).some(
			(axis) => options.axes[axis].percentage
		);

		const stackedData = this.model.getStackedData({
			percentage,
			groups: this.configs.groups,
		});

		// area doesnt have to use the main range and domain axes - they can be mapped to the secondary (in the case of a combo chart)
		// however area _cannot_ have multiple datasets that are mapped to _different_ ranges and domains so we can use the first data item
		const domainAxisPosition = this.services.cartesianScales.getDomainAxisPosition(
			{ datum: stackedData[0][0] }
		);
		const rangeAxisPosition = this.services.cartesianScales.getRangeAxisPosition(
			{ datum: stackedData[0][0] }
		);
		const mainYScale = this.services.cartesianScales.getScaleByPosition(
			rangeAxisPosition
		);

		const areas = svg
			.selectAll('path.area')
			.data(stackedData, (d) => d[0][groupMapsTo]);

		// D3 area generator function
		this.areaGenerator = area()
			.x((d: any, i) =>
				this.services.cartesianScales.getValueThroughAxisPosition(
					domainAxisPosition,
					d.data.sharedStackKey,
					i
				)
			)
			.y0((d) => mainYScale(d[0]))
			.y1((d) => mainYScale(d[1]))
			.curve(this.services.curves.getD3Curve());

		areas.exit().attr('opacity', 0).remove();

		const enteringAreas = areas.enter().append('path').attr('opacity', 0);

		enteringAreas
			.merge(areas)
			.data(stackedData, (d) => d[0][groupMapsTo])
			.attr('class', 'area')
			.attr('class', (d) =>
				this.model.getColorClassName({
					classNameTypes: [ColorClassNameTypes.FILL],
					dataGroupName: d[0][groupMapsTo],
					originalClassName: 'area',
				})
			)
			.attr('fill', (d) => self.model.getFillColor(d[0][groupMapsTo]))
			.attr('role', Roles.GRAPHICS_SYMBOL)
			.attr('aria-roledescription', 'area')
			.transition(
				this.services.transitions.getTransition(
					'area-update-enter',
					animate
				)
			)
			.attr('opacity', Configuration.area.opacity.selected)
			.attr('d', this.areaGenerator);
	}

	handleLegendOnHover = (event: CustomEvent) => {
		const { hoveredElement } = event.detail;
		const options = this.getOptions();
		const { groupMapsTo } = options.data;

		this.parent
			.selectAll('path.area')
			.transition(
				this.services.transitions.getTransition('legend-hover-area')
			)
			.attr('opacity', (d) => {
				if (d[0][groupMapsTo] !== hoveredElement.datum().name) {
					return Configuration.area.opacity.unselected;
				}

				return Configuration.area.opacity.selected;
			});
	};

	handleLegendMouseOut = (event: CustomEvent) => {
		this.parent
			.selectAll('path.area')
			.transition(
				this.services.transitions.getTransition('legend-mouseout-area')
			)
			.attr('opacity', Configuration.area.opacity.selected);
	};

	destroy() {
		// Remove event listeners
		this.parent
			.selectAll('path.area')
			.on('mouseover', null)
			.on('mousemove', null)
			.on('mouseout', null);
	}
}
