"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const uplot_1 = __importDefault(require("uplot"));
const legend_1 = __importDefault(require("./plugins/legend/legend"));
const common_1 = require("./utils/common");
const colors_1 = __importDefault(require("./utils/colors"));
const defaults_1 = __importStar(require("./defaults"));
const locale_1 = __importDefault(require("./locale"));
const create_options_1 = require("./mixins/create-options");
const transform_series_1 = require("./mixins/transform-series");
const dynamic_updates_1 = require("./mixins/dynamic-updates");
const mixins_1 = require("./utils/mixins");
const batch_updates_1 = require("./mixins/batch-updates");
/*
 * Main core-module of Yagr.
 * Implements data processing and autoconfigurable wrapper
 * for uPlot chart.
 * Entrypoint of every Yagr chart.
 */
class Yagr {
    get isEmpty() {
        return this.state.isEmptyDataSet;
    }
    constructor(root, pConfig) {
        this.plugins = {};
        this._meta = {};
        this._uHooks = {};
        this.init = () => {
            var _a;
            if (((_a = this.config.chart.size) === null || _a === void 0 ? void 0 : _a.adaptive) && !this.resizeOb) {
                this.resizeOb = new ResizeObserver((0, common_1.debounce)(this.onResize, this.config.chart.size.resizeDebounceMs || 100));
                this.resizeOb.observe(this.root);
            }
            if (!this.config.hooks.dispose) {
                this.config.hooks.dispose = [];
            }
            /** Unsubscribe in init required to avoid chars been synced without action from developer */
            this.unsubscribe();
            this.config.hooks.dispose.push(this.trackMouse());
        };
        this.execHooks = (hookName, ...args) => {
            const hooks = this.config.hooks[hookName];
            if (Array.isArray(hooks)) {
                for (const hook of hooks) {
                    if (!hook) {
                        continue;
                    }
                    typeof hook === 'function' && hook(...args);
                }
            }
        };
        this.initRender = (u, done) => {
            var _a, _b, _c;
            /**
             * Reimplementing appending u.root to root
             * and ensure that uPlot instance will be placed
             * correctly relative to legend
             */
            const position = (_a = this.config.legend) === null || _a === void 0 ? void 0 : _a.position;
            if (this.root.firstChild) {
                !position || position === 'bottom'
                    ? this.root.insertBefore(u.root, this.root.firstChild)
                    : this.root.appendChild(u.root);
            }
            else {
                !position || position === 'bottom'
                    ? this.root.appendChild(u.root)
                    : this.root.insertBefore(u.root, this.root.firstChild);
            }
            if ((_b = this.config.legend) === null || _b === void 0 ? void 0 : _b.show) {
                (_c = this.plugins.legend) === null || _c === void 0 ? void 0 : _c.init(u);
                this.reflow(false);
            }
            this.initTitle();
            done();
        };
        /*
         * Resize handler. Should cache height and width to avoid unneccesary resize handling,
         * when actial width and height of contentRect doesn't changed
         */
        this.onResize = (args) => {
            var _a;
            const [resize] = args;
            if (this._cache.height === resize.contentRect.height && this._cache.width === resize.contentRect.width) {
                return;
            }
            (_a = this.plugins.tooltip) === null || _a === void 0 ? void 0 : _a.reset();
            this.reflow();
            this.execHooks('resize', { entries: args, chart: this });
        };
        this.initMixins();
        this._startTime = performance.now();
        this.state = {
            isEmptyDataSet: false,
            isMouseOver: false,
            stage: 'config',
            y2uIdx: {},
            subscribed: false,
        };
        const config = Object.assign({
            title: {},
            data: [],
            axes: {},
            series: [],
            scales: {},
            hooks: {},
            settings: {},
            chart: {},
            cursor: {},
            plugins: {},
            legend: {
                show: false,
            },
            tooltip: {
                show: true,
            },
            grid: null,
            markers: {},
        }, pConfig);
        this.config = config;
        this.inStage('config', () => {
            var _a;
            this.id = root.id || (0, common_1.genId)();
            this.root = root;
            this.root.classList.add('yagr');
            if (!this.root.id) {
                this.root.id = this.id;
            }
            const colorParser = new colors_1.default();
            const sync = this.config.cursor.sync;
            const chart = this.config.chart;
            chart.series || (chart.series = { type: 'line' });
            chart.size || (chart.size = { adaptive: true });
            chart.appearance || (chart.appearance = { locale: 'en' });
            chart.select || (chart.select = {});
            this.utils = {
                colors: colorParser,
                i18n: (0, locale_1.default)(((_a = config.chart.appearance) === null || _a === void 0 ? void 0 : _a.locale) || 'en'),
                theme: new defaults_1.default(colorParser),
            };
            colorParser.setContext(root);
            if (sync) {
                this.utils.sync = uplot_1.default.sync(typeof sync === 'string' ? sync : defaults_1.DEFAULT_SYNC_KEY);
            }
            if (!chart.size.adaptive && chart.size.width && chart.size.height) {
                root.style.width = (0, common_1.px)(chart.size.width);
                root.style.height = (0, common_1.px)(chart.size.height);
            }
            this.plugins.legend = new legend_1.default();
            this.setTheme(chart.appearance.theme || 'light');
            this.createUplotOptions();
            this._cache = {
                height: this.options.height,
                width: this.options.width,
            };
            if (config.editUplotOptions) {
                this.options = config.editUplotOptions(this.options);
            }
        })
            .inStage('processing', () => {
            this.transformSeries();
        })
            .inStage('uplot', () => {
            this.uplot = new uplot_1.default(this.options, this.series, this.initRender);
            this.canvas = root.querySelector('canvas');
            this.init();
            const processTime = performance.now() - this._startTime;
            this._meta.processTime = processTime;
        })
            .inStage('render');
    }
    /**
     * Redraws Yagr instance by given options.
     */
    redraw(series = true, axes = true) {
        this.uplot.redraw(series, axes);
    }
    /**
     *  Get uPlot's Series from series id
     */
    getSeriesById(id) {
        return this.uplot.series[this.state.y2uIdx[id]];
    }
    getSerieLegendColor(serie) {
        const { legendColorKey, color, lineColor } = serie;
        let serieColor = color;
        switch (legendColorKey) {
            case 'lineColor': {
                if (lineColor) {
                    serieColor = lineColor;
                }
                break;
            }
            case 'color': {
                serieColor = color;
            }
        }
        return serieColor;
    }
    dispose() {
        var _a, _b, _c, _d;
        this.resizeOb && this.resizeOb.unobserve(this.root);
        this.unsubscribe();
        (_b = (_a = this.plugins) === null || _a === void 0 ? void 0 : _a.tooltip) === null || _b === void 0 ? void 0 : _b.dispose();
        (_d = (_c = this.plugins) === null || _c === void 0 ? void 0 : _c.legend) === null || _d === void 0 ? void 0 : _d.destroy();
        this.uplot.destroy();
        this._uHooks = {};
        this.execHooks('dispose', { chart: this });
    }
    toDataUrl() {
        return this.canvas.toDataURL('img/png');
    }
    subscribe() {
        var _a;
        if (this.state.subscribed) {
            return;
        }
        (_a = this.utils.sync) === null || _a === void 0 ? void 0 : _a.sub(this.uplot);
        this.state.subscribed = true;
    }
    unsubscribe() {
        var _a;
        (_a = this.utils.sync) === null || _a === void 0 ? void 0 : _a.unsub(this.uplot);
        this.state.subscribed = false;
    }
    inStage(stage, fn) {
        this.state.stage === stage;
        this.execHooks('stage', { chart: this, stage });
        try {
            fn && fn();
        }
        catch (error) {
            console.error(error);
            this.onError(error);
        }
        return this;
    }
    initTitle() {
        /** Setup font size for title if required */
        if (this.config.title && this.config.title.fontSize) {
            const size = this.config.title.fontSize;
            const t = this.root.querySelector('.u-title');
            t.setAttribute('style', `font-size:${size}px;line-height:${size}px;`);
            t.innerHTML = this.config.title.text;
        }
    }
    onError(error) {
        this.execHooks('error', {
            stage: this.state.stage,
            error,
            chart: this,
        });
        return error;
    }
    trackMouse() {
        const mouseOver = () => {
            this.state.isMouseOver = true;
        };
        const mouseLeave = () => {
            this.state.isMouseOver = false;
        };
        this.root.addEventListener('mouseover', mouseOver);
        this.root.addEventListener('mouseleave', mouseLeave);
        return () => {
            this.root.removeEventListener('mouseover', mouseOver);
            this.root.removeEventListener('mouseleave', mouseLeave);
        };
    }
    get clientHeight() {
        var _a;
        const MARGIN = 8;
        const offset = this.config.title.text ? (this.config.title.fontSize || defaults_1.DEFAULT_TITLE_FONT_SIZE) + MARGIN : 0;
        // https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements#how_much_room_does_it_use_up
        // getBoundingClientRect() provides more precise decimal point values.
        // To prevent extra scroll we need round to floor 
        return Math.floor(this.root.getBoundingClientRect().height) - offset - (((_a = this.plugins.legend) === null || _a === void 0 ? void 0 : _a.state.totalSpace) || 0);
    }
    reflow(redraw = true) {
        const width = this.root.clientWidth;
        const height = this.clientHeight;
        this._cache.width = width;
        this.options.width = width;
        this._cache.height = height;
        this.options.height = height;
        redraw &&
            this.uplot.setSize({
                width: this.options.width,
                height: this.options.height,
            });
        redraw && this.uplot.redraw();
    }
}
(0, mixins_1.applyMixins)(Yagr, [create_options_1.CreateUplotOptionsMixin, transform_series_1.TransformSeriesMixin, dynamic_updates_1.DynamicUpdatesMixin, batch_updates_1.BatchMixin]);
exports.default = Yagr;
