"use strict";
/* eslint-disable complexity, no-nested-ternary */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const placement_1 = __importDefault(require("./placement"));
const defaults_1 = require("../../defaults");
const common_1 = require("../../utils/common");
const render_1 = require("./render");
const utils_1 = require("./utils");
// eslint-disable-next-line complexity
const findValue = (cursor, data, serie, idx, interpolation) => {
    var _a, _b, _c;
    const source = Array.isArray(serie.$c) ? serie.$c : data;
    let value = source[idx];
    if (interpolation && value === interpolation.value) {
        const snapTo = (_a = interpolation.snapToValues) !== null && _a !== void 0 ? _a : 'closest';
        const nonNullIdx = (0, common_1.findDataIdx)(source, serie, idx, snapTo, interpolation.value);
        value = source[nonNullIdx];
    }
    else if (value === null) {
        const cursorSnapToValues = (_b = cursor.snapToValues) !== null && _b !== void 0 ? _b : 'closest';
        const snapTo = (_c = serie.snapToValues) !== null && _c !== void 0 ? _c : cursorSnapToValues;
        const nonNullIdx = (0, common_1.findDataIdx)(source, serie, idx, snapTo, null);
        value = source[nonNullIdx];
    }
    return value;
};
const DEFAULT_TOOLTIP_OPTIONS = {
    maxLines: defaults_1.TOOLTIP_DEFAULT_MAX_LINES,
    highlight: true,
    sum: false,
    render: render_1.renderTooltip,
    pinable: true,
    strategy: 'pin',
    sort: undefined,
    showIndicies: false,
    hideNoData: false,
    className: 'yagr-tooltip_default',
    xOffset: defaults_1.TOOLTIP_X_OFFSET,
    yOffset: defaults_1.TOOLTIP_Y_OFFSET,
    virtual: false,
    showEmpty: false,
    onUpdate: 'reset',
};
class YagrTooltip {
    constructor(yagr, options = {}) {
        var _a, _b;
        this.handlers = {
            init: [],
            mount: [],
            show: [],
            pin: [],
            unpin: [],
            hide: [],
            render: [],
            destroy: [],
            reset: [],
        };
        this.placement = placement_1.default;
        this.renderTooltipCloses = () => { };
        /**
         * Flag to skip next mouseup event, used for unpinning
         * tooltip on any mousedown, but continiously pinning on drag
         */
        this.skipNextMouseUp = false;
        this.emit = (action, data) => {
            this.handlers[action].forEach((handler) => {
                handler(this.tOverlay, {
                    state: this.state,
                    actions: {
                        pin: this.pin,
                        show: this.show,
                        hide: this.hide,
                        dispose: this.dispose,
                        reset: this.reset,
                    },
                    data,
                    yagr: this.yagr,
                    event: action,
                });
            });
        };
        this.reset = () => {
            var _a;
            if (this.opts.onUpdate === 'none') {
                (_a = this.yagr.plugins.cursor) === null || _a === void 0 ? void 0 : _a.pin(false);
                return;
            }
            if (this.state.visible) {
                this.hide();
            }
            if (this.state.pinned) {
                this.pin(false);
            }
            this.emit('reset');
        };
        this.show = () => {
            const shouldEmit = !this.state.visible;
            this.state.visible = true;
            this.tOverlay.style.display = 'block';
            shouldEmit && this.emit('show');
        };
        this.hide = () => {
            const shouldEmit = this.state.visible;
            this.state.visible = false;
            this.tOverlay.style.display = 'none';
            this.emit('hide');
            shouldEmit && this.emit('show');
        };
        this.pin = (pinState, position) => {
            var _a;
            this.state.pinned = pinState;
            const range = this.state.range || [];
            (_a = this.yagr.plugins.cursor) === null || _a === void 0 ? void 0 : _a.pin(pinState && (range[1] === null || range.length < 2));
            if (this.opts.virtual) {
                return this.emit(pinState ? 'pin' : 'unpin');
            }
            if (position) {
                this.placement(this.tOverlay, {
                    left: position.x + this.bLeft,
                    top: this.bTop + position.y - (this.opts.yOffset || 0),
                }, 'right', {
                    bound: this.bound,
                    xOffset: this.opts.xOffset,
                    yOffset: this.opts.yOffset,
                });
            }
            const list = this.tOverlay.querySelector('._tooltip-list');
            if (pinState) {
                if (!this.state.visible) {
                    this.show();
                }
                this.tOverlay.classList.add('yagr-tooltip_pinned');
                if (list && (list === null || list === void 0 ? void 0 : list.clientHeight)) {
                    list.style.height = (0, common_1.px)(list.clientHeight);
                }
                if (this.opts.render === render_1.renderTooltip) {
                    document.addEventListener('mousemove', this.checkFocus);
                    document.addEventListener('mousedown', this.detectClickOutside);
                }
            }
            else {
                this.tOverlay.classList.remove('yagr-tooltip_pinned');
                if (this.opts.render === render_1.renderTooltip) {
                    document.removeEventListener('mousemove', this.checkFocus);
                    document.removeEventListener('mousedown', this.detectClickOutside);
                }
            }
            this.emit(pinState ? 'pin' : 'unpin');
        };
        this.checkFocus = (event) => {
            const target = event.target;
            let serieIdx;
            if (target &&
                this.tOverlay.contains(target) &&
                target.classList.contains('yagr-tooltip__item')) {
                serieIdx = target.dataset['series'];
            }
            const serie = serieIdx ? this.yagr.uplot.series[Number(serieIdx)] : null;
            if (serieIdx && serie) {
                this.state.focusedSeries = serieIdx;
                this.yagr.setFocus(serie.id, true);
            }
            else if (this.state.focusedSeries) {
                this.state.focusedSeries = null;
                this.yagr.setFocus(null, true);
            }
        };
        this.render = (props) => {
            const u = this.yagr.uplot;
            let { left, top } = props;
            const { idx } = props;
            const { opts, state } = this;
            if (opts.show && typeof opts.show === 'function' && opts.show(this.yagr) === false) {
                this.hide();
                return;
            }
            if ((left < 0 || top < 0) && !state.pinned && this.isNotInDrag) {
                this.hide();
            }
            top = (0, common_1.inBetween)(top, 0, (u.bbox.top + u.bbox.height) / window.devicePixelRatio);
            left = (0, common_1.inBetween)(left, 0, (u.bbox.left + u.bbox.width) / window.devicePixelRatio);
            const { data } = u;
            if (data === null || (0, common_1.isNil)(idx) || top === undefined) {
                return;
            }
            const x = data[0][idx];
            const sum = {};
            const sections = {};
            const rowsBySections = {};
            let i = u.series.length - 1;
            while (i >= 1) {
                const serie = u.series[i];
                if (!serie.show) {
                    i -= 1;
                    continue;
                }
                const scale = serie.scale || defaults_1.DEFAULT_Y_SCALE;
                rowsBySections[scale] = rowsBySections[scale] || [];
                rowsBySections[scale].push(i);
                i -= 1;
            }
            const rowEntries = Object.entries(rowsBySections);
            rowEntries.forEach(([scale, serieIndicies]) => {
                var _a;
                sections[scale] = sections[scale] || {
                    rows: [],
                };
                const section = sections[scale];
                const cursorValue = Number(u.posToVal(top, scale).toFixed(2));
                const valueRender = (0, utils_1.getOptionValue)(opts.value, scale);
                for (const seriesIdx of serieIndicies) {
                    const seriesData = u.data[seriesIdx];
                    const serie = u.series[seriesIdx];
                    let value = findValue(this.yagr.config.cursor, seriesData, serie, idx, this.interpolation);
                    let dValue = value;
                    if (typeof value === 'string') {
                        dValue = value;
                        value = null;
                    }
                    if ((0, utils_1.getOptionValue)(opts.sum, scale)) {
                        sum[scale] = sum[scale] || 0;
                        sum[scale] += value || 0;
                    }
                    const realY = seriesData[idx];
                    const yValue = serie.$c && serie.$c[idx] === this.stripValue ? value : realY;
                    if ((value === null && opts.hideNoData) || serie.showInTooltip === false) {
                        continue;
                    }
                    const seriesPrecision = (_a = serie.precision) !== null && _a !== void 0 ? _a : (0, utils_1.getOptionValue)(opts.precision, scale);
                    const displayValue = serie.formatter
                        ? serie.formatter(dValue, serie)
                        : valueRender(dValue, seriesPrecision);
                    const rowData = {
                        id: serie.id,
                        name: serie.name,
                        dataValue: serie.$c[idx],
                        originalValue: value,
                        value: displayValue,
                        y: yValue,
                        displayY: realY,
                        color: this.yagr.getSerieLegendColor(serie),
                        seriesIdx,
                        rowIdx: section.rows.length
                            ? section.rows[section.rows.length - 1].rowIdx + 1
                            : 0,
                    };
                    if (serie.normalizedData) {
                        rowData.transformed = serie.normalizedData[idx];
                    }
                    if (serie._transformed) {
                        rowData.transformed = seriesData[idx];
                    }
                    if (opts.omitBy && opts.omitBy(rowData)) {
                        continue;
                    }
                    section.rows.push(rowData);
                }
                if ((0, utils_1.getOptionValue)(opts.highlight, scale) && section.rows.length) {
                    const tracking = (0, utils_1.getOptionValue)(opts.tracking, scale);
                    let activeIndex = 0;
                    if (tracking === 'area') {
                        activeIndex = (0, common_1.findInRange)(section, cursorValue, (0, utils_1.getOptionValue)(opts.stickToRanges, scale));
                    }
                    else if (tracking === 'sticky') {
                        activeIndex = (0, common_1.findSticky)(section, cursorValue);
                    }
                    else if (typeof tracking === 'function') {
                        activeIndex = tracking(section, cursorValue, {
                            x: u.posToVal(left, 'x'),
                            y: u.posToVal(top, scale),
                            idx,
                            scale,
                            series: this.yagr.series,
                            serieIndicies,
                            interpolation: this.interpolation,
                        });
                    }
                    if (activeIndex !== null) {
                        section.rows[activeIndex].active = true;
                    }
                }
                const sort = (0, utils_1.getOptionValue)(opts.sort, scale);
                if (sort) {
                    section.rows.sort(sort);
                }
            });
            const hasOneRow = Object.values(sections).some(({ rows }) => rows.length > 0);
            if (hasOneRow || opts.showEmpty) {
                this.onMouseEnter();
            }
            else {
                this.hide();
                return;
            }
            const bbox = this.over.getBoundingClientRect();
            this.bLeft = bbox.left;
            this.bTop = bbox.top;
            this.bWidth = bbox.width;
            const anchor = {
                left: left + this.bLeft,
                top: this.bTop + top - (opts.yOffset || 0),
            };
            this.renderTooltipCloses = () => {
                const renderData = {
                    scales: Object.entries(sections).map(([scale, sec]) => {
                        return {
                            scale,
                            rows: sec.rows,
                            sum: sum[scale],
                        };
                    }),
                    options: opts,
                    x,
                };
                const hasDataNow = Object.values(sections).some(({ rows }) => rows.filter(({ id }) => { var _a; return (_a = this.yagr.getSeriesById(id)) === null || _a === void 0 ? void 0 : _a.show; }).length > 0);
                if (!hasDataNow && !opts.showEmpty) {
                    this.hide();
                    return;
                }
                if (!opts.virtual) {
                    this.tOverlay.innerHTML = opts.render({
                        ...renderData,
                        state,
                        yagr: this.yagr,
                        defaultRender: DEFAULT_TOOLTIP_OPTIONS.render,
                    });
                    this.placement(this.tOverlay, anchor, 'right', {
                        bound: this.bound,
                        xOffset: opts.xOffset,
                        yOffset: opts.yOffset,
                    });
                }
                this.emit('render', { ...renderData, anchor });
            };
            if (state.pinned) {
                return;
            }
            this.renderTooltipCloses();
        };
        this.initWithUplot = (u) => {
            this.over = u.root.querySelector('.u-over');
            this.over.addEventListener('mousedown', this.onMouseDown);
            this.over.addEventListener('mousemove', this.onMouseMove);
            this.over.addEventListener('mouseenter', this.onMouseEnter);
            this.over.addEventListener('mouseleave', this.onMouseLeave);
            document.addEventListener('mouseup', this.onMouseUp);
            this.mountTooltip();
        };
        this.setSize = () => {
            const bbox = this.over.getBoundingClientRect();
            this.bLeft = bbox.left;
            this.bTop = bbox.top;
        };
        this.dispose = () => {
            /** Free overlay listeners */
            this.over.removeEventListener('mousedown', this.onMouseDown);
            this.over.removeEventListener('mousemove', this.onMouseMove);
            this.over.removeEventListener('mouseenter', this.onMouseEnter);
            this.over.removeEventListener('mouseleave', this.onMouseLeave);
            document.removeEventListener('mouseup', this.onMouseUp);
            document.removeEventListener('mousemove', this.checkFocus);
            document.removeEventListener('mousedown', this.detectClickOutside);
            /** Removing tooltip on destroy */
            this.tOverlay.remove();
            this.state.mounted = false;
            this.emit('destroy');
        };
        this.updateOptions = (newOptions) => {
            Object.assign(this.opts, newOptions);
            this.tOverlay.className = `yagr-tooltip ${this.opts.className || ''}`;
        };
        this.on = (event, handler) => {
            this.handlers[event].push(handler);
        };
        this.off = (event, handler) => {
            this.handlers[event] = this.handlers[event].filter((h) => h !== handler);
        };
        this.mountTooltip = () => {
            if (!this.opts.virtual) {
                this.renderNode = this.opts.renderClassName
                    ? document.querySelector(this.opts.renderClassName) || document.body
                    : document.body;
                this.renderNode.appendChild(this.tOverlay);
                this.state.mounted = true;
                this.emit('mount');
            }
        };
        this.detectClickOutside = (event) => {
            const target = event.target;
            if (target instanceof Element) {
                const isClickInsideTooltip = target && this.tOverlay.contains(target);
                const isClickOnUplotOver = target && this.over.contains(target);
                if (!isClickInsideTooltip && !isClickOnUplotOver) {
                    this.pin(false);
                    this.hide();
                }
            }
        };
        this.onMouseDown = (evt) => {
            this.state.range = [this.getCursorPosition(), null];
            if (this.state.pinned) {
                this.pin(false);
                this.hide();
                this.render({
                    left: evt.clientX - this.bLeft,
                    top: evt.clientY - this.bTop,
                    idx: this.yagr.uplot.posToIdx(evt.clientX - this.bLeft),
                });
                this.skipNextMouseUp = true;
            }
        };
        this.onMouseMove = () => {
            var _a;
            if ((_a = this.state.range) === null || _a === void 0 ? void 0 : _a.length) {
                this.state.range[1] = this.getCursorPosition();
            }
        };
        /**
         * Calculates where exactly cursor leaved the chart
         * and sets range[1] to this position
         */
        this.setCursorLeaved = (e) => {
            const rect = this.over.getBoundingClientRect();
            const x = e.clientX;
            const range = this.state.range;
            const startPoint = range[0];
            const xInOver = x - rect.left;
            const end = xInOver > startPoint.clientX;
            const timeline = this.yagr.config.timeline;
            let result;
            if (end) {
                range[1] = {
                    clientX: this.bWidth,
                    value: this.yagr.uplot.posToVal(this.bWidth, 'x'),
                    idx: timeline.length - 1,
                };
                result = range[1];
            }
            else {
                /** Swap range[1] and range[0] in case if tooltip leaved chart in begining of element */
                range[1] = range[0];
                range[0] = {
                    clientX: 0,
                    value: this.yagr.uplot.posToVal(0, 'x'),
                    idx: 0,
                };
                result = range[0];
            }
            this.yagr.uplot.setCursor({
                left: result.clientX,
                top: e.clientY - rect.top,
            });
        };
        this.onMouseUp = (e) => {
            if (this.state.range === null) {
                return;
            }
            const [from] = this.state.range || [];
            let cursor;
            if (e.target === this.over) {
                cursor = this.getCursorPosition();
            }
            else {
                cursor = this.state.range[1];
            }
            if (this.opts.strategy === 'none') {
                return;
            }
            const click = from && from.clientX === (cursor === null || cursor === void 0 ? void 0 : cursor.clientX);
            const drag = from && from.clientX !== (cursor === null || cursor === void 0 ? void 0 : cursor.clientX);
            const strategy = this.opts.strategy;
            if ((click && !this.skipNextMouseUp && strategy !== 'drag') ||
                (drag && (strategy === 'all' || strategy === 'drag'))) {
                this.pin(!this.state.pinned);
                this.show();
                this.renderTooltipCloses();
            }
            this.state.range = null;
            this.skipNextMouseUp = false;
        };
        this.onMouseEnter = () => {
            this.show();
        };
        this.onMouseLeave = (e) => {
            var _a;
            const isPinned = this.state.pinned;
            if ((_a = this.state.range) === null || _a === void 0 ? void 0 : _a[0]) {
                this.setCursorLeaved(e);
            }
            if (!isPinned && this.isNotInDrag) {
                this.hide();
            }
        };
        this.defaultTooltipValueFormatter = (n, precision) => {
            const pSettings = this.yagr.config.processing || {};
            if (typeof n === 'string') {
                if (pSettings.nullValues && pSettings.nullValues.hasOwnProperty(n)) {
                    return pSettings.nullValues[n];
                }
                return '-';
            }
            if (typeof n === 'number') {
                return n.toFixed(
                // eslint-disable-next-line no-nested-ternary
                typeof precision === 'number'
                    ? precision
                    : typeof this.opts.precision === 'number'
                        ? this.opts.precision
                        : 2);
            }
            return '-';
        };
        this.getCursorPosition = () => {
            const x = this.yagr.uplot.cursor.left;
            if (x === undefined) {
                return null;
            }
            return {
                clientX: x,
                value: this.yagr.uplot.posToVal(x, 'x'),
                idx: this.yagr.uplot.posToIdx(x),
            };
        };
        this.yagr = yagr;
        this.over = (_a = yagr === null || yagr === void 0 ? void 0 : yagr.uplot) === null || _a === void 0 ? void 0 : _a.over;
        this.opts = {
            ...DEFAULT_TOOLTIP_OPTIONS,
            strategy: options.pinable ? 'pin' : DEFAULT_TOOLTIP_OPTIONS.strategy,
            tracking: ((_b = yagr.config.chart.series) === null || _b === void 0 ? void 0 : _b.type) === 'area' ? 'area' : 'sticky',
            value: this.defaultTooltipValueFormatter,
            ...options,
        };
        this.bound = this.opts.boundClassName
            ? document.querySelector(this.opts.boundClassName) || document.body
            : document.body;
        this.renderNode = this.opts.renderClassName
            ? document.querySelector(this.opts.renderClassName) || document.body
            : document.body;
        this.tOverlay = document.createElement('div');
        this.tOverlay.id = `${yagr.id}_tooltip`;
        this.tOverlay.className = `yagr-tooltip ${this.opts.className || ''}`;
        this.tOverlay.style.display = 'none';
        this.state = {
            mounted: false,
            pinned: false,
            visible: false,
            range: null,
            focusedSeries: null,
        };
        this.bLeft = 0;
        this.bTop = 0;
        this.bWidth = 0;
        if (this.opts.virtual) {
            this.placement = () => { };
        }
    }
    get interpolation() {
        var _a;
        return (_a = this.yagr.config.processing) === null || _a === void 0 ? void 0 : _a.interpolation;
    }
    get stripValue() {
        return this.interpolation ? this.interpolation.value : undefined;
    }
    get isNotInDrag() {
        var _a;
        if (this.opts.strategy === 'none' || this.opts.strategy === 'pin') {
            return true;
        }
        return !((_a = this.state.range) === null || _a === void 0 ? void 0 : _a[1]);
    }
}
/*
 * Tooltip plugin constructor .
 * Every charts has it's own tooltip plugin instance
 */
function YagrTooltipPlugin(yagr, options = {}) {
    const tooltip = new YagrTooltip(yagr, options);
    const getUplotPlugin = () => ({
        hooks: {
            destroy: () => {
                tooltip.dispose();
            },
            init: (u) => {
                tooltip.initWithUplot(u);
            },
            setSize: () => {
                tooltip.setSize();
            },
            setCursor: (u) => {
                tooltip.render(u.cursor);
            },
        },
    });
    const uPlotPlugin = getUplotPlugin();
    function reInit(u) {
        const uPlugin = getUplotPlugin();
        tooltip.reset();
        u.hooks.init.push(uPlugin.hooks.init);
        u.hooks.destroy.push(uPlugin.hooks.destroy);
        u.hooks.setSize.push(uPlugin.hooks.setSize);
        u.hooks.setCursor.push(uPlugin.hooks.setCursor);
    }
    return {
        state: tooltip.state,
        pin: tooltip.pin,
        show: tooltip.show,
        hide: tooltip.hide,
        uplot: uPlotPlugin,
        display: tooltip.render,
        updateOptions: tooltip.updateOptions,
        on: tooltip.on,
        off: tooltip.off,
        tooltip,
        dispose: tooltip.dispose,
        reInit,
        reset: tooltip.reset,
    };
}
exports.default = YagrTooltipPlugin;
