"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const defaults_1 = require("../../defaults");
const types_1 = require("../../utils/types");
const common_1 = require("../../utils/common");
const utils_1 = require("./utils");
const MAX_X_SCALE_LINE_OFFSET = 0;
const DRAW_MAP = {
    [types_1.DrawOrderKey.Series]: 0,
    [types_1.DrawOrderKey.Axes]: 1,
    plotLines: 2,
};
const HOOKS_MAP = {
    '012': 'draw',
    '102': 'draw',
    '201': 'drawClear',
    '210': 'drawClear',
    '120': 'drawAxes',
    '021': 'drawSeries',
};
/*
 * Plugin renders custom lines and bands on chart based on axis config.
 * Axis should be bound to scale.
 */
function plotLinesPlugin(options) {
    let plotLines = new Map();
    return function (yagr) {
        var _a;
        const drawOrder = (_a = yagr.config.chart.appearance) === null || _a === void 0 ? void 0 : _a.drawOrder;
        const drawIndicies = (drawOrder ? drawOrder.map((key) => DRAW_MAP[key]) : [0, 1, 2]).join('');
        const hook = HOOKS_MAP[drawIndicies] || 'drawClear';
        function getLineId(line) {
            var _a;
            if (line.id) {
                return line.id;
            }
            const lineWithoutId = (_a = Array.from(plotLines.entries()).find(([_, l]) => (0, common_1.deepIsEqual)(l, line))) === null || _a === void 0 ? void 0 : _a[0];
            return lineWithoutId || (0, common_1.genId)();
        }
        function renderPlotLines(u) {
            const { ctx } = u;
            const { height, top, width, left } = u.bbox;
            const timeline = u.data[0];
            for (const plotLineConfig of plotLines.values()) {
                if (!plotLineConfig.scale) {
                    continue;
                }
                if (options.render) {
                    options.render(u, plotLineConfig);
                    continue;
                }
                ctx.save();
                ctx.fillStyle = yagr.utils.colors.parse(plotLineConfig.color);
                const { scale, value } = plotLineConfig;
                const isBand = Array.isArray(value);
                const [from, to] = isBand
                    ? (0, utils_1.calculateFromTo)(value, scale, timeline, u)
                    : [u.valToPos(value, scale, true), 0];
                if (isBand) {
                    const accent = plotLineConfig.accent;
                    if (scale === defaults_1.DEFAULT_X_SCALE) {
                        ctx.fillRect(from, top, to - from, height);
                        if (accent) {
                            ctx.fillStyle = accent.color;
                            ctx.fillRect(from, top - accent.space, to - from, accent.space);
                        }
                    }
                    else {
                        ctx.fillRect(left, from, width, to - from);
                        if (accent) {
                            ctx.fillStyle = accent.color;
                            ctx.fillRect(width + left, from, accent.space, to - from);
                        }
                    }
                }
                else {
                    const pConf = plotLineConfig;
                    ctx.beginPath();
                    if (scale === defaults_1.DEFAULT_X_SCALE) {
                        /** Workaround to ensure that plot line will not be drawn over axes */
                        const last = u.data[0][u.data[0].length - 1];
                        const lastValue = u.valToPos(last, scale, true);
                        if (from - lastValue > MAX_X_SCALE_LINE_OFFSET) {
                            continue;
                        }
                        ctx.moveTo(from, top);
                        ctx.lineTo(from, height + top);
                    }
                    else {
                        ctx.moveTo(left, from);
                        ctx.lineTo(width + left, from);
                    }
                    ctx.lineWidth = pConf.width || defaults_1.DEFAULT_CANVAS_PIXEL_RATIO;
                    ctx.strokeStyle = pConf.color || '#000';
                    if (pConf.dash) {
                        ctx.setLineDash(pConf.dash);
                    }
                    if (pConf.lineCap) {
                        ctx.lineCap = pConf.lineCap;
                    }
                    ctx.stroke();
                }
                ctx.restore();
            }
        }
        const handler = hook === 'drawSeries'
            ? (u, sIdx) => {
                if (sIdx === u.series.length - 1) {
                    renderPlotLines(u);
                }
            }
            : renderPlotLines;
        function addPlotLines(additionalPlotLines) {
            for (const line of additionalPlotLines) {
                const lineId = getLineId(line);
                plotLines.set(lineId, line);
            }
        }
        function removePlotLines(plotLinesToRemove) {
            for (const lineToRemove of plotLinesToRemove) {
                const lineId = getLineId(lineToRemove);
                plotLines.delete(lineId);
            }
        }
        function getPlotLines() {
            return Array.from(plotLines.values());
        }
        function updatePlotLines(newPlotLines, scale) {
            if (!newPlotLines || newPlotLines.length === 0) {
                clearPlotLines(scale);
                return;
            }
            const existingKeys = new Set();
            for (const newLine of newPlotLines) {
                const lineId = getLineId(newLine);
                plotLines.set(lineId, newLine);
                existingKeys.add(lineId);
            }
            // Delete not actual lines from map
            for (const [key, line] of plotLines.entries()) {
                if ((!scale || line.scale === scale) && !existingKeys.has(key)) {
                    plotLines.delete(key);
                }
            }
        }
        function clearPlotLines(scale) {
            if (scale) {
                plotLines.forEach((line, key) => {
                    if (line.scale === scale) {
                        plotLines.delete(key);
                    }
                });
            }
            else {
                plotLines.clear();
            }
        }
        const plugin = {
            get: getPlotLines,
            clear: clearPlotLines,
            remove: removePlotLines,
            add: addPlotLines,
            update: updatePlotLines,
            uplot: {
                opts: () => {
                    const config = yagr.config;
                    plotLines = new Map();
                    for (const scale in config.axes) {
                        if (config.axes.hasOwnProperty(scale)) {
                            const axisConfig = config.axes[scale];
                            if (axisConfig.plotLines) {
                                for (const plotLine of axisConfig.plotLines) {
                                    plotLines.set(plotLine.id || (0, common_1.genId)(), {
                                        ...plotLine,
                                        scale,
                                    });
                                }
                            }
                        }
                    }
                },
                hooks: {
                    // @TODO Add feature to draw plot lines over series
                    [hook]: handler,
                },
            },
        };
        return plugin;
    };
}
exports.default = plotLinesPlugin;
