import { __assign, __extends, __rest } from "tslib";
import { contains, deepMix, each, get, isArray, isFunction, isNil, isString, keys, upperFirst, find, includes, } from '@antv/util';
import { Annotation as AnnotationComponent } from '../../dependents';
import { DEFAULT_ANIMATE_CFG } from '../../animate/';
import { COMPONENT_TYPE, DIRECTION, GEOMETRY_LIFE_CIRCLE, LAYER, VIEW_LIFE_CIRCLE } from '../../constant';
import { getAngleByPoint, getDistanceToCenter } from '../../util/coordinate';
import { omit } from '../../util/helper';
import { getNormalizedValue } from '../../util/annotation';
import { Controller } from './base';
/** 需要在图形绘制完成后才渲染的辅助组件类型列表 */
var ANNOTATIONS_AFTER_RENDER = ['regionFilter', 'shape'];
/**
 * Annotation controller, 主要作用:
 * 1. 创建 Annotation: line、text、arc ...
 * 2. 生命周期: init、layout、render、clear、destroy
 */
var Annotation = /** @class */ (function (_super) {
    __extends(Annotation, _super);
    function Annotation(view) {
        var _this = _super.call(this, view) || this;
        /* 组件更新的 cache，组件配置 object : 组件 */
        _this.cache = new Map();
        _this.foregroundContainer = _this.view.getLayer(LAYER.FORE).addGroup();
        _this.backgroundContainer = _this.view.getLayer(LAYER.BG).addGroup();
        _this.option = [];
        return _this;
    }
    Object.defineProperty(Annotation.prototype, "name", {
        get: function () {
            return 'annotation';
        },
        enumerable: false,
        configurable: true
    });
    Annotation.prototype.init = function () { };
    /**
     * 因为 annotation 需要依赖坐标系信息，所以 render 阶段为空方法，实际的创建逻辑都在 layout 中
     */
    Annotation.prototype.layout = function () {
        this.update();
    };
    // 因为 Annotation 不参与布局，但是渲染的位置依赖于坐标系，所以可以将绘制阶段延迟到 layout() 进行
    Annotation.prototype.render = function () { };
    /**
     * 更新
     */
    Annotation.prototype.update = function () {
        var _this = this;
        // 1. 先处理需要在图形渲染之后的辅助组件 需要在 Geometry 完成之后，拿到图形信息
        this.onAfterRender(function () {
            var updated = new Map();
            // 先看是否有 regionFilter/shape 要更新
            each(_this.option, function (option) {
                if (includes(ANNOTATIONS_AFTER_RENDER, option.type)) {
                    var co = _this.updateOrCreate(option);
                    // 存储已经处理过的
                    if (co) {
                        updated.set(_this.getCacheKey(option), co);
                    }
                }
            });
            // 处理完成之后，更新 cache
            // 处理完成之后，销毁删除的
            _this.cache = _this.syncCache(updated);
        });
        // 2. 处理非 regionFilter
        var updateCache = new Map();
        each(this.option, function (option) {
            if (!includes(ANNOTATIONS_AFTER_RENDER, option.type)) {
                var co = _this.updateOrCreate(option);
                // 存储已经处理过的
                if (co) {
                    updateCache.set(_this.getCacheKey(option), co);
                }
            }
        });
        this.cache = this.syncCache(updateCache);
    };
    /**
     * 清空
     * @param includeOption 是否清空 option 配置项
     */
    Annotation.prototype.clear = function (includeOption) {
        if (includeOption === void 0) { includeOption = false; }
        _super.prototype.clear.call(this);
        this.clearComponents();
        this.foregroundContainer.clear();
        this.backgroundContainer.clear();
        // clear all option
        if (includeOption) {
            this.option = [];
        }
    };
    Annotation.prototype.destroy = function () {
        this.clear(true);
        this.foregroundContainer.remove(true);
        this.backgroundContainer.remove(true);
    };
    /**
     * 复写基类的方法
     */
    Annotation.prototype.getComponents = function () {
        var co = [];
        this.cache.forEach(function (value) {
            co.push(value);
        });
        return co;
    };
    /**
     * 清除当前的组件
     */
    Annotation.prototype.clearComponents = function () {
        this.getComponents().forEach(function (co) {
            co.component.destroy();
        });
        this.cache.clear();
    };
    /**
     * region filter 比较特殊的渲染时机
     * @param doWhat
     */
    Annotation.prototype.onAfterRender = function (doWhat) {
        if (this.view.getOptions().animate) {
            this.view.geometries.forEach(function (g) {
                // 如果 geometry 开启，则监听
                if (g.animateOption) {
                    g.once(GEOMETRY_LIFE_CIRCLE.AFTER_DRAW_ANIMATE, function () {
                        doWhat();
                    });
                }
            });
        }
        else {
            this.view.getRootView().once(VIEW_LIFE_CIRCLE.AFTER_RENDER, function () {
                doWhat();
            });
        }
    };
    Annotation.prototype.createAnnotation = function (option) {
        var type = option.type;
        var Ctor = AnnotationComponent[upperFirst(type)];
        if (Ctor) {
            var theme = this.getAnnotationTheme(type);
            var cfg = this.getAnnotationCfg(type, option, theme);
            // 不创建
            if (!cfg) {
                return null;
            }
            var annotation = new Ctor(cfg);
            return {
                component: annotation,
                layer: this.isTop(cfg) ? LAYER.FORE : LAYER.BG,
                direction: DIRECTION.NONE,
                type: COMPONENT_TYPE.ANNOTATION,
                extra: option,
            };
        }
    };
    // APIs for creating annotation component
    Annotation.prototype.annotation = function (option) {
        this.option.push(option);
    };
    /**
     * 创建 Arc
     * @param option
     * @returns AnnotationController
     */
    Annotation.prototype.arc = function (option) {
        this.annotation(__assign({ type: 'arc' }, option));
        return this;
    };
    /**
     * 创建 image
     * @param option
     * @returns AnnotationController
     */
    Annotation.prototype.image = function (option) {
        this.annotation(__assign({ type: 'image' }, option));
        return this;
    };
    /**
     * 创建 Line
     * @param option
     * @returns AnnotationController
     */
    Annotation.prototype.line = function (option) {
        this.annotation(__assign({ type: 'line' }, option));
        return this;
    };
    /**
     * 创建 Region
     * @param option
     * @returns AnnotationController
     */
    Annotation.prototype.region = function (option) {
        this.annotation(__assign({ type: 'region' }, option));
        return this;
    };
    /**
     * 创建 Text
     * @param option
     * @returns AnnotationController
     */
    Annotation.prototype.text = function (option) {
        this.annotation(__assign({ type: 'text' }, option));
        return this;
    };
    /**
     * 创建 DataMarker
     * @param option
     * @returns AnnotationController
     */
    Annotation.prototype.dataMarker = function (option) {
        this.annotation(__assign({ type: 'dataMarker' }, option));
        return this;
    };
    /**
     * 创建 DataRegion
     * @param option
     * @returns AnnotationController
     */
    Annotation.prototype.dataRegion = function (option) {
        this.annotation(__assign({ type: 'dataRegion' }, option));
    };
    /**
     * 创建 RegionFilter
     * @param option
     * @returns AnnotationController
     */
    Annotation.prototype.regionFilter = function (option) {
        this.annotation(__assign({ type: 'regionFilter' }, option));
    };
    /**
     * 创建 ShapeAnnotation
     * @param option
     */
    Annotation.prototype.shape = function (option) {
        this.annotation(__assign({ type: 'shape' }, option));
    };
    /**
     * 创建 HtmlAnnotation
     * @param option
     */
    Annotation.prototype.html = function (option) {
        this.annotation(__assign({ type: 'html' }, option));
    };
    // end API
    /**
     * parse the point position to [x, y]
     * @param p Position
     * @returns { x, y }
     */
    Annotation.prototype.parsePosition = function (p) {
        var xScale = this.view.getXScale();
        // 转成 object
        var yScales = this.view.getScalesByDim('y');
        var position = isFunction(p) ? p.call(null, xScale, yScales) : p;
        var x = 0;
        var y = 0;
        // 入参是 [24, 24] 这类时
        if (isArray(position)) {
            var xPos = position[0], yPos = position[1];
            // 如果数据格式是 ['50%', '50%'] 的格式
            // fix: 原始数据中可能会包含 'xxx5%xxx' 这样的数据，需要判断下 https://github.com/antvis/f2/issues/590
            // @ts-ignore
            if (isString(xPos) && xPos.indexOf('%') !== -1 && !isNaN(xPos.slice(0, -1))) {
                return this.parsePercentPosition(position);
            }
            x = getNormalizedValue(xPos, xScale);
            y = getNormalizedValue(yPos, Object.values(yScales)[0]);
        }
        else if (!isNil(position)) {
            // 入参是 object 结构，数据点
            for (var _i = 0, _a = keys(position); _i < _a.length; _i++) {
                var key = _a[_i];
                var value = position[key];
                if (key === xScale.field) {
                    x = getNormalizedValue(value, xScale);
                }
                if (yScales[key]) {
                    y = getNormalizedValue(value, yScales[key]);
                }
            }
        }
        if (isNaN(x) || isNaN(y)) {
            return null;
        }
        return this.view.getCoordinate().convert({ x: x, y: y });
    };
    /**
     * parse all the points between start and end
     * @param start
     * @param end
     * @return Point[]
     */
    Annotation.prototype.getRegionPoints = function (start, end) {
        var _this = this;
        var xScale = this.view.getXScale();
        var yScales = this.view.getScalesByDim('y');
        var yScale = Object.values(yScales)[0];
        var xField = xScale.field;
        var viewData = this.view.getData();
        var startXValue = isArray(start) ? start[0] : start[xField];
        var endXValue = isArray(end) ? end[0] : end[xField];
        var arr = [];
        var startIndex;
        each(viewData, function (item, idx) {
            if (item[xField] === startXValue) {
                startIndex = idx;
            }
            if (idx >= startIndex) {
                var point = _this.parsePosition([item[xField], item[yScale.field]]);
                if (point) {
                    arr.push(point);
                }
            }
            if (item[xField] === endXValue) {
                return false;
            }
        });
        return arr;
    };
    /**
     * parse percent position
     * @param position
     */
    Annotation.prototype.parsePercentPosition = function (position) {
        var xPercent = parseFloat(position[0]) / 100;
        var yPercent = parseFloat(position[1]) / 100;
        var coordinate = this.view.getCoordinate();
        var start = coordinate.start, end = coordinate.end;
        var topLeft = {
            x: Math.min(start.x, end.x),
            y: Math.min(start.y, end.y),
        };
        var x = coordinate.getWidth() * xPercent + topLeft.x;
        var y = coordinate.getHeight() * yPercent + topLeft.y;
        return { x: x, y: y };
    };
    /**
     * get coordinate bbox
     */
    Annotation.prototype.getCoordinateBBox = function () {
        var coordinate = this.view.getCoordinate();
        var start = coordinate.start, end = coordinate.end;
        var width = coordinate.getWidth();
        var height = coordinate.getHeight();
        var topLeft = {
            x: Math.min(start.x, end.x),
            y: Math.min(start.y, end.y),
        };
        return {
            x: topLeft.x,
            y: topLeft.y,
            minX: topLeft.x,
            minY: topLeft.y,
            maxX: topLeft.x + width,
            maxY: topLeft.y + height,
            width: width,
            height: height,
        };
    };
    /**
     * get annotation component config by different type
     * @param type
     * @param option 用户的配置
     * @param theme
     */
    Annotation.prototype.getAnnotationCfg = function (type, option, theme) {
        var _this = this;
        var coordinate = this.view.getCoordinate();
        var canvas = this.view.getCanvas();
        var o = {};
        if (isNil(option)) {
            return null;
        }
        var start = option.start, end = option.end, position = option.position;
        ;
        var sp = this.parsePosition(start);
        var ep = this.parsePosition(end);
        var textPoint = this.parsePosition(position);
        if (['arc', 'image', 'line', 'region', 'regionFilter'].includes(type) && (!sp || !ep)) {
            return null;
        }
        else if (['text', 'dataMarker', 'html'].includes(type) && !textPoint) {
            return null;
        }
        if (type === 'arc') {
            var _a = option, start_1 = _a.start, end_1 = _a.end, rest = __rest(_a, ["start", "end"]);
            var startAngle = getAngleByPoint(coordinate, sp);
            var endAngle = getAngleByPoint(coordinate, ep);
            if (startAngle > endAngle) {
                endAngle = Math.PI * 2 + endAngle;
            }
            o = __assign(__assign({}, rest), { center: coordinate.getCenter(), radius: getDistanceToCenter(coordinate, sp), startAngle: startAngle, endAngle: endAngle });
        }
        else if (type === 'image') {
            var _b = option, start_2 = _b.start, end_2 = _b.end, rest = __rest(_b, ["start", "end"]);
            o = __assign(__assign({}, rest), { start: sp, end: ep, src: option.src });
        }
        else if (type === 'line') {
            var _c = option, start_3 = _c.start, end_3 = _c.end, rest = __rest(_c, ["start", "end"]);
            o = __assign(__assign({}, rest), { start: sp, end: ep, text: get(option, 'text', null) });
        }
        else if (type === 'region') {
            var _d = option, start_4 = _d.start, end_4 = _d.end, rest = __rest(_d, ["start", "end"]);
            o = __assign(__assign({}, rest), { start: sp, end: ep });
        }
        else if (type === 'text') {
            var filteredData = this.view.getData();
            var _e = option, position_1 = _e.position, content = _e.content, rest = __rest(_e, ["position", "content"]);
            var textContent = content;
            if (isFunction(content)) {
                textContent = content(filteredData);
            }
            o = __assign(__assign(__assign({}, textPoint), rest), { content: textContent });
        }
        else if (type === 'dataMarker') {
            var _f = option, position_2 = _f.position, point = _f.point, line = _f.line, text = _f.text, autoAdjust = _f.autoAdjust, direction = _f.direction, rest = __rest(_f, ["position", "point", "line", "text", "autoAdjust", "direction"]);
            o = __assign(__assign(__assign({}, rest), textPoint), { coordinateBBox: this.getCoordinateBBox(), point: point, line: line, text: text, autoAdjust: autoAdjust, direction: direction });
        }
        else if (type === 'dataRegion') {
            var _g = option, start_5 = _g.start, end_5 = _g.end, region = _g.region, text = _g.text, lineLength = _g.lineLength, rest = __rest(_g, ["start", "end", "region", "text", "lineLength"]);
            o = __assign(__assign({}, rest), { points: this.getRegionPoints(start_5, end_5), region: region, text: text, lineLength: lineLength });
        }
        else if (type === 'regionFilter') {
            var _h = option, start_6 = _h.start, end_6 = _h.end, apply_1 = _h.apply, color = _h.color, rest = __rest(_h, ["start", "end", "apply", "color"]);
            var geometries = this.view.geometries;
            var shapes_1 = [];
            var addShapes_1 = function (item) {
                if (!item) {
                    return;
                }
                if (item.isGroup()) {
                    item.getChildren().forEach(function (child) { return addShapes_1(child); });
                }
                else {
                    shapes_1.push(item);
                }
            };
            each(geometries, function (geom) {
                if (apply_1) {
                    if (contains(apply_1, geom.type)) {
                        each(geom.elements, function (elem) {
                            addShapes_1(elem.shape);
                        });
                    }
                }
                else {
                    each(geom.elements, function (elem) {
                        addShapes_1(elem.shape);
                    });
                }
            });
            o = __assign(__assign({}, rest), { color: color, shapes: shapes_1, start: sp, end: ep });
        }
        else if (type === 'shape') {
            var _j = option, render_1 = _j.render, restOptions = __rest(_j, ["render"]);
            var wrappedRender = function (container) {
                if (isFunction(option.render)) {
                    return render_1(container, _this.view, { parsePosition: _this.parsePosition.bind(_this) });
                }
            };
            o = __assign(__assign({}, restOptions), { render: wrappedRender });
        }
        else if (type === 'html') {
            var _k = option, html_1 = _k.html, position_3 = _k.position, restOptions = __rest(_k, ["html", "position"]);
            var wrappedHtml = function (container) {
                if (isFunction(html_1)) {
                    return html_1(container, _this.view);
                }
                return html_1;
            };
            o = __assign(__assign(__assign({}, restOptions), textPoint), { 
                // html 组件需要指定 parent
                parent: canvas.get('el').parentNode, html: wrappedHtml });
        }
        // 合并主题，用户配置优先级高于默认主题
        var cfg = deepMix({}, theme, __assign(__assign({}, o), { top: option.top, style: option.style, offsetX: option.offsetX, offsetY: option.offsetY }));
        if (type !== 'html') {
            // html 类型不使用 G container
            cfg.container = this.getComponentContainer(cfg);
        }
        cfg.animate = this.view.getOptions().animate && cfg.animate && get(option, 'animate', cfg.animate); // 如果 view 关闭动画，则不执行
        cfg.animateOption = deepMix({}, DEFAULT_ANIMATE_CFG, cfg.animateOption, option.animateOption);
        return cfg;
    };
    /**
     * is annotation render on top
     * @param option
     * @return whethe on top
     */
    Annotation.prototype.isTop = function (option) {
        return get(option, 'top', true);
    };
    /**
     * get the container by option.top
     * default is on top
     * @param option
     * @returns the container
     */
    Annotation.prototype.getComponentContainer = function (option) {
        return this.isTop(option) ? this.foregroundContainer : this.backgroundContainer;
    };
    Annotation.prototype.getAnnotationTheme = function (type) {
        return get(this.view.getTheme(), ['components', 'annotation', type], {});
    };
    /**
     * 创建或者更新 annotation
     * @param option
     */
    Annotation.prototype.updateOrCreate = function (option) {
        // 拿到缓存的内容
        var co = this.cache.get(this.getCacheKey(option));
        // 存在则更新，不存在在创建
        if (co) {
            var type = option.type;
            var theme = this.getAnnotationTheme(type);
            var cfg = this.getAnnotationCfg(type, option, theme);
            // 忽略掉一些配置
            if (cfg) {
                omit(cfg, ['container']);
            }
            co.component.update(__assign(__assign({}, (cfg || {})), { visible: !!cfg }));
            // 对于 regionFilter/shape，因为生命周期的原因，需要额外 render
            if (includes(ANNOTATIONS_AFTER_RENDER, option.type)) {
                co.component.render();
            }
        }
        else {
            // 不存在，创建
            co = this.createAnnotation(option);
            if (co) {
                co.component.init();
                // Note：regionFilter/shape 特殊处理，regionFilter/shape 需要取到 Geometry 中的 Shape，需要在 view render 之后处理
                // 其他组件使用外层的统一 render 逻辑
                if (includes(ANNOTATIONS_AFTER_RENDER, option.type)) {
                    co.component.render();
                }
            }
        }
        return co;
    };
    /**
     * 更新缓存，以及销毁组件
     * @param updated 更新或者创建的组件
     */
    Annotation.prototype.syncCache = function (updated) {
        var _this = this;
        var newCache = new Map(this.cache); // clone 一份
        // 将 update 更新到 cache
        updated.forEach(function (co, key) {
            newCache.set(key, co);
        });
        // 另外和 options 进行对比，删除
        newCache.forEach(function (co, key) {
            // option 中已经找不到，那么就是删除的
            if (!find(_this.option, function (option) {
                return key === _this.getCacheKey(option);
            })) {
                co.component.destroy();
                newCache.delete(key);
            }
        });
        return newCache;
    };
    /**
     * 获得缓存组件的 key
     * @param option
     */
    Annotation.prototype.getCacheKey = function (option) {
        // 如果存在 id，则使用 id string，否则直接使用 option 引用作为 key
        return option;
        // 后续扩展 id 用
        // const id = get(option, 'id');
        // return id ? id : option;
    };
    return Annotation;
}(Controller));
export default Annotation;
//# sourceMappingURL=annotation.js.map