import React from 'react';
import { scaleTime, scaleLinear } from 'd3-scale';
import { max, bisector } from 'd3-array';
import { line, area, curveMonotoneX } from 'd3-shape';
import { select, pointer } from 'd3-selection';
import { easeQuadIn } from 'd3-ease';
import { ChartTooltip } from './ChartTooltip';
import { dataHooks } from './constants';
import { classes } from './SparklineChart.st.css.js';
import 'd3-transition';
import Tooltip from '../Tooltip';
import Text from '../Text';
const LINE_WIDTH = 2;
const AREA_MASK_ID = 'areaMaskId';
const TOOLTIP_ELEMENT_RADIUS = 4;
const DEFAULT_GRADIENT_ID = 'DEFAULT_GRADIENT_ID';
/** SparklineChart */
class SparklineChart extends React.PureComponent {
    constructor(props) {
        super(props);
        this._shouldShowTooltip = () => {
            const { hoveredLabel } = this.state;
            const { getTooltipContent } = this.props;
            return (getTooltipContent &&
                typeof getTooltipContent === 'function' &&
                hoveredLabel);
        };
        this._useCreateContext = () => {
            const halfWidth = LINE_WIDTH / 2;
            const { width = 200, height = 40, data, highlightedStartingIndex = 0, color, } = this.props;
            const { highlightedEndingIndex = data.length - 1 } = this.props;
            const margin = {
                top: halfWidth + 2,
                right: halfWidth,
                bottom: halfWidth,
                left: halfWidth,
            };
            const innerTop = margin.top;
            const innerLeft = margin.left;
            const innerHeight = height - innerTop - margin.bottom;
            const innerWidth = width - innerLeft - margin.right;
            const maxValue = max(this._getValues(data));
            const firstLabel = this._getLabelAt(data, 0);
            const lastLabel = this._getLabelAt(data, data.length - 1);
            const xScale = scaleTime()
                .domain([firstLabel, lastLabel])
                .range([innerLeft, innerWidth]);
            const yScale = scaleLinear()
                .domain([0, maxValue])
                .range([innerHeight, innerTop]);
            const lineGenerator = line()
                .x((dataPoint, i) => {
                return xScale(this._getLabelAt(data, i));
            })
                .y(dataPoint => {
                return yScale(dataPoint);
            })
                .curve(curveMonotoneX);
            const areaGenerator = area()
                .x((dataPoint, i) => {
                return xScale(this._getLabelAt(data, i));
            })
                .y0(() => innerHeight)
                .y1(dataPoint => {
                return yScale(dataPoint);
            })
                .curve(curveMonotoneX);
            return {
                margin,
                width,
                height,
                innerTop,
                innerLeft,
                innerBottom: margin.top + innerHeight,
                innerWidth,
                innerHeight,
                data,
                xScale,
                yScale,
                highlightedStartingIndex,
                highlightedEndingIndex,
                lineGenerator,
                areaGenerator,
                color,
            };
        };
        this._getLabelAt = (data, position) => {
            return data[position] && data[position].label;
        };
        this._getValues = data => data.map(pair => pair.value);
        this._getLabels = data => data.map(pair => pair.label);
        this._drawSparkline = () => {
            const { width, height, data } = this.chartContext;
            const { onHover } = this.props;
            const labels = this._getLabels(data);
            const container = select(this.svgRef.current);
            container.attr('width', width).attr('height', height);
            const dataContainer = container.select(`[data-hook="${dataHooks.dataContainer}"]`);
            this._drawLines(dataContainer);
            select(this.componentRef.current)
                .on('mouseleave', () => {
                this.setState({ hoveredLabel: null });
            })
                .on('mousemove', d => {
                const dateUnderPointer = this.chartContext.xScale.invert(pointer(d)[0]);
                const currentDateIndex = bisector(function (date) {
                    return date;
                }).left(labels, dateUnderPointer, 1);
                const beforeDateIndex = currentDateIndex - 1;
                const beforeDate = labels[beforeDateIndex];
                const afterDate = labels[currentDateIndex];
                const closestDate = +dateUnderPointer - +beforeDate > +afterDate - +dateUnderPointer
                    ? afterDate
                    : beforeDate;
                if (typeof onHover === 'function' &&
                    !this._areDatesEqual(closestDate, this.state.hoveredLabel)) {
                    const labelIndex = labels.indexOf(closestDate);
                    onHover(labelIndex);
                }
                this.setState({ hoveredLabel: closestDate });
            });
        };
        this._drawLines = dataContainer => {
            const { data, lineGenerator, areaGenerator, color } = this.chartContext;
            const dataSets = [data];
            dataContainer
                .selectAll('.chartLines')
                .data(dataSets)
                .join('g')
                .attr('class', 'chartLines')
                .selectAll('g')
                .data(dataSet => {
                return [dataSet];
            })
                .join(enter => {
                const group = enter.append('g');
                group
                    .append('path')
                    .attr('class', 'innerArea')
                    .attr('mask', `url(#${this._getAreaMaskId(this.randomComponentId)})`)
                    .attr('fill', dataSet => {
                    return `url(#${color || DEFAULT_GRADIENT_ID})`;
                })
                    .attr('d', dataSet => {
                    return areaGenerator(dataSet.map(() => 0));
                });
                group
                    .append('path')
                    .attr('class', 'innerLineBack')
                    .attr('data-hook', dataHooks.innerLineBack)
                    .attr('fill', 'none')
                    .attr('stroke-width', LINE_WIDTH + 4)
                    .attr('stroke-linecap', 'round')
                    .attr('d', dataSet => {
                    return lineGenerator(dataSet.map(() => 0));
                });
                group
                    .append('path')
                    .attr('class', 'innerLine')
                    .attr('fill', 'none')
                    .attr('stroke-width', LINE_WIDTH)
                    .attr('stroke-linecap', 'round')
                    .attr('stroke', dataSet => {
                    return `url(#${this._getLineColorId(dataSet, this.randomComponentId)})`;
                })
                    .attr('d', dataSet => {
                    return lineGenerator(dataSet.map(() => 0));
                });
                this._updateLines(group);
                return group;
            }, update => {
                this._updateLines(update);
                return update;
            });
        };
        this._updateLines = container => {
            const { lineGenerator, areaGenerator } = this.chartContext;
            this._updateComponent(container, '.innerLine', set => {
                return lineGenerator(this._getValues(set));
            });
            this._updateComponent(container, '.innerLineBack', set => {
                return lineGenerator(this._getValues(set));
            });
            this._updateComponent(container, '.innerArea', set => {
                return areaGenerator(this._getValues(set));
            });
        };
        this._updateComponent = (container, className, fncUpdater) => {
            const { animationDuration } = this.props;
            container
                .select(className)
                .transition()
                .duration(animationDuration)
                .ease(easeQuadIn)
                .attr('d', fncUpdater);
        };
        this.randomComponentId = Math.random().toString();
        this.chartContext = {};
        this.svgRef = React.createRef(null);
        this.componentRef = React.createRef(null);
        this.state = {
            hoveredLabel: null,
        };
    }
    _getValueAt(data, position) {
        return data[position] && data[position].value;
    }
    _areDatesEqual(date1, date2) {
        const date1Time = date1 && date1.getTime();
        const date2Time = date2 && date2.getTime();
        return date1Time === date2Time;
    }
    _getLineColorId(dataSet, componentId) {
        return `${componentId}color`;
    }
    _getAreaMaskId(componentId) {
        return `${AREA_MASK_ID}${componentId}`;
    }
    componentDidMount() {
        this._drawSparkline();
    }
    componentDidUpdate(prevProps) {
        if (prevProps.data !== this.props.data) {
            this._drawSparkline();
        }
    }
    _updateContext() {
        this.chartContext = this._useCreateContext();
    }
    render() {
        this._updateContext();
        const { getTooltipContent, className, dataHook } = this.props;
        const { hoveredLabel } = this.state;
        const context = this.chartContext;
        const { data, highlightedStartingIndex, highlightedEndingIndex, innerWidth, height, width, color, } = context;
        const highlightedStart = context.xScale(this._getLabelAt(data, highlightedStartingIndex));
        const highlightedEnd = context.xScale(this._getLabelAt(data, highlightedEndingIndex));
        const highlightedStartRelativeLocation = highlightedStart / innerWidth;
        const highlightedEndRelativeLocation = highlightedEnd / innerWidth;
        const labels = this._getLabels(data);
        const hoveredLabelIndex = bisector(function (d) {
            return d;
        }).left(labels, hoveredLabel, 0);
        const currentHoveredLabel = this._getLabelAt(data, hoveredLabelIndex);
        const currentHoveredValue = this._getValueAt(data, hoveredLabelIndex);
        const dataPoint = {
            content: getTooltipContent &&
                typeof getTooltipContent === 'function' &&
                getTooltipContent(hoveredLabelIndex),
            xCoordinate: context.xScale(currentHoveredLabel),
            yCoordinate: context.yScale(currentHoveredValue) - TOOLTIP_ELEMENT_RADIUS / 2,
        };
        return (React.createElement("div", { style: { width, height, position: 'relative' }, ref: this.componentRef, className: className, "data-hook": dataHook },
            React.createElement("svg", { style: { overflow: 'visible', zIndex: 1 }, ref: this.svgRef },
                React.createElement("defs", null,
                    React.createElement("mask", { id: this._getAreaMaskId(this.randomComponentId) },
                        React.createElement("rect", { x: highlightedStart, y: "0", width: highlightedEnd - highlightedStart, height: height, fill: "white" })),
                    React.createElement("linearGradient", { gradientUnits: "userSpaceOnUse", key: `${this.randomComponentId}a`, id: this._getLineColorId(data, this.randomComponentId), x1: "0px", y1: "0px", x2: `${innerWidth}px`, y2: "0px" },
                        React.createElement("stop", { offset: highlightedStartRelativeLocation, style: { stopColor: '#dfe5eb', stopOpacity: 1 } }),
                        React.createElement("stop", { offset: highlightedStartRelativeLocation, className: classes.gradientStop, style: { stopColor: color } }),
                        React.createElement("stop", { offset: highlightedEndRelativeLocation, className: classes.gradientStop, style: {
                                stopColor: color,
                            } }),
                        React.createElement("stop", { offset: highlightedEndRelativeLocation, style: { stopColor: '#dfe5eb', stopOpacity: 1 } }),
                        React.createElement("stop", { offset: 1, style: { stopColor: '#dfe5eb', stopOpacity: 1 } })),
                    React.createElement("linearGradient", { gradientUnits: "userSpaceOnUse", key: this.randomComponentId, id: color || DEFAULT_GRADIENT_ID, x1: "0px", y1: `${context.innerHeight}px`, x2: "0px", y2: "0px" },
                        React.createElement("stop", { offset: "10%", className: classes.gradientStopWithoutOpacity, style: { stopColor: color } }),
                        React.createElement("stop", { offset: "90%", className: classes.gradientStopWithHalfOpacity, style: {
                                stopColor: color,
                            } }))),
                React.createElement("g", null,
                    React.createElement("g", { "data-hook": dataHooks.dataContainer }),
                    this._shouldShowTooltip() && (React.createElement("g", { transform: `translate(${dataPoint.xCoordinate}, ${dataPoint.yCoordinate + TOOLTIP_ELEMENT_RADIUS / 2})` },
                        React.createElement("circle", { r: TOOLTIP_ELEMENT_RADIUS, className: classes.tooltipElement, fill: color }))))),
            this._shouldShowTooltip() && React.createElement(ChartTooltip, { dataPoint: dataPoint })));
    }
}
SparklineChart.displayName = 'SparklineChart';
SparklineChart.defaultProps = {
    animationDuration: 300,
};
export default SparklineChart;
//# sourceMappingURL=SparklineChart.js.map