"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpreadSheet = void 0;
const tslib_1 = require("tslib");
const event_emitter_1 = tslib_1.__importDefault(require("@antv/event-emitter"));
const g_1 = require("@antv/g");
const g_canvas_1 = require("@antv/g-canvas");
const lodash_1 = require("lodash");
const cell_1 = require("../cell");
const constant_1 = require("../common/constant");
const debug_1 = require("../common/debug");
const i18n_1 = require("../common/i18n");
const factory_1 = require("../common/icons/factory");
const store_1 = require("../common/store");
const root_1 = require("../interaction/root");
const theme_1 = require("../theme");
const hd_adapter_1 = require("../ui/hd-adapter");
const tooltip_1 = require("../ui/tooltip");
const canvas_1 = require("../utils/canvas");
const state_controller_1 = require("../utils/condition/state-controller");
const hide_columns_1 = require("../utils/hide-columns");
const is_mobile_1 = require("../utils/is-mobile");
const merge_1 = require("../utils/merge");
const tooltip_2 = require("../utils/tooltip");
class SpreadSheet extends event_emitter_1.default {
    constructor(dom, dataCfg, options) {
        super();
        this.store = new store_1.Store();
        /**
         * 表格是否已销毁
         */
        this.destroyed = false;
        /**
         * 获取文本在画布中的测量信息
         * @param text 待计算的文本
         * @param font 文本 css 样式
         * @returns 文本测量信息 TextMetrics
         */
        this.measureText = (0, lodash_1.memoize)((text, font) => {
            if (!font) {
                return null;
            }
            const canvas = (0, canvas_1.getOffscreenCanvas)() || this.getCanvasElement();
            const ctx = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('2d');
            const { fontSize, fontFamily, fontWeight, fontStyle, fontVariant } = font;
            ctx.font = [
                fontStyle,
                fontVariant,
                fontWeight,
                `${fontSize}px`,
                fontFamily,
            ]
                .join(' ')
                .trim();
            return ctx.measureText(String(text));
        }, (text, font) => [text, ...(0, lodash_1.values)(font)].join(''));
        /**
         * 计算文本在画布中的宽度
         * @param text 待计算的文本
         * @param font 文本 css 样式
         * @returns 文本宽度
         */
        this.measureTextWidth = (text, font) => {
            const textMetrics = this.measureText(text, font);
            return (textMetrics === null || textMetrics === void 0 ? void 0 : textMetrics.width) || 0;
        };
        /**
         * 计算文本在画布中的宽度 https://developer.mozilla.org/zh-CN/docs/Web/API/TextMetrics
         * @param text 待计算的文本
         * @param font 文本 css 样式
         * @returns 文本高度
         */
        this.measureTextHeight = (text, font) => {
            const textMetrics = this.measureText(text, font);
            if (!textMetrics) {
                return 0;
            }
            return (textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent);
        };
        /**
         * 粗略计算文本在画布中的宽度
         * @param text 待计算的文本
         * @param font 文本 css 样式
         * @returns 文本宽度
         */
        this.measureTextWidthRoughly = (text, font) => {
            const alphaWidth = this.measureTextWidth('a', font);
            const chineseWidth = this.measureTextWidth('蚂', font);
            let w = 0;
            if (!text) {
                return w;
            }
            // eslint-disable-next-line no-restricted-syntax
            for (const char of String(text)) {
                const code = char.charCodeAt(0);
                // /[\u0000-\u00ff]/
                w += code >= 0 && code <= 255 ? alphaWidth : chineseWidth;
            }
            return w;
        };
        this.setupDataConfig(dataCfg);
        this.setupOptions(options);
        this.dataSet = this.getDataSet();
        this.setDebug();
        this.initTooltip();
        this.initContainer(dom);
        this.bindEvents();
        this.initInteraction();
        this.initTheme();
        this.initHdAdapter();
        this.registerIcons();
        this.setOverscrollBehavior();
        this.mountSheetInstance();
    }
    setupDataConfig(dataCfg) {
        this.dataCfg = (0, merge_1.setupDataConfig)(dataCfg);
    }
    setupOptions(options) {
        this.options = (0, merge_1.setupOptions)(options);
    }
    isCustomHeaderFields(fieldType) {
        const { fields } = this.dataCfg;
        if (!fieldType) {
            return (0, lodash_1.some)([...fields === null || fields === void 0 ? void 0 : fields.rows, ...fields === null || fields === void 0 ? void 0 : fields.columns], (field) => !(0, lodash_1.isString)(field));
        }
        return (0, lodash_1.some)(fields === null || fields === void 0 ? void 0 : fields[fieldType], (field) => !(0, lodash_1.isString)(field));
    }
    isCustomColumnFields() {
        return this.isCustomHeaderFields('columns');
    }
    setOverscrollBehavior() {
        const { overscrollBehavior } = this.options.interaction;
        // 行内样式 + css 样式
        const initOverscrollBehavior = window
            .getComputedStyle(document.body)
            .getPropertyValue('overscroll-behavior');
        // 用户没有在 body 上主动设置过 overscrollBehavior，才进行更新
        const hasInitOverscrollBehavior = initOverscrollBehavior && initOverscrollBehavior !== 'auto';
        if (hasInitOverscrollBehavior) {
            this.store.set('initOverscrollBehavior', initOverscrollBehavior);
        }
        else if (overscrollBehavior) {
            document.body.style.overscrollBehavior = overscrollBehavior;
        }
    }
    restoreOverscrollBehavior() {
        document.body.style.overscrollBehavior =
            this.store.get('initOverscrollBehavior') || '';
    }
    setDebug() {
        debug_1.DebuggerUtil.getInstance().setDebug(this.options.debug);
    }
    initTheme() {
        // When calling spreadsheet directly, there is no theme and initialization is required
        this.setThemeCfg({
            name: 'default',
        });
    }
    getMountContainer(dom) {
        const mountContainer = (0, lodash_1.isString)(dom) ? document.querySelector(dom) : dom;
        if (!mountContainer) {
            throw new Error('Target mount container is not a DOM element');
        }
        return mountContainer;
    }
    initHdAdapter() {
        if (this.options.hd) {
            this.hdAdapter = new hd_adapter_1.HdAdapter(this);
            this.hdAdapter.init();
        }
    }
    initInteraction() {
        var _a, _b;
        (_b = (_a = this.interaction) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
        this.interaction = new root_1.RootInteraction(this);
    }
    initTooltip() {
        var _a, _b, _c, _d;
        (_b = (_a = this.tooltip) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
        this.tooltip = this.renderTooltip();
        if (!(this.tooltip instanceof tooltip_1.BaseTooltip)) {
            // eslint-disable-next-line no-console
            console.warn(`[Custom Tooltip]: ${(_d = (_c = this.tooltip) === null || _c === void 0 ? void 0 : _c.constructor) === null || _d === void 0 ? void 0 : _d.toString()} should be extends from BaseTooltip`);
        }
    }
    renderTooltip() {
        var _a, _b;
        return ((_b = (_a = this.options.tooltip) === null || _a === void 0 ? void 0 : _a.render) === null || _b === void 0 ? void 0 : _b.call(_a, this)) || new tooltip_1.BaseTooltip(this);
    }
    getTargetCell(target) {
        // 刷选等场景, 以最后一个发生交互的单元格为准
        return this.getCell(target) || (0, lodash_1.last)(this.interaction.getInteractedCells());
    }
    /**
     * 展示 Tooltip 提示
     * @alias s2.tooltip.show()
     * @example
        s2.showTooltip({
          position: {
            x: event.clientX,
            y: event.clientY,
          },
          content: '<div>xxx</div>',
          options: {}
        })
     */
    showTooltip(showOptions) {
        const { content, event } = showOptions;
        const cell = this.getTargetCell(event === null || event === void 0 ? void 0 : event.target);
        const displayContent = (0, lodash_1.isFunction)(content)
            ? content(cell, showOptions)
            : content;
        return new Promise((resolve) => {
            var _a, _b;
            const options = Object.assign(Object.assign({}, showOptions), { content: displayContent, onMounted: resolve });
            (_b = (_a = this.tooltip) === null || _a === void 0 ? void 0 : _a.show) === null || _b === void 0 ? void 0 : _b.call(_a, options);
        });
    }
    showTooltipWithInfo(event, cellInfos, options) {
        var _a;
        const { enable: showTooltip, content } = (0, tooltip_2.getTooltipOptions)(this, event);
        if (!showTooltip) {
            return;
        }
        const targetCell = this.getTargetCell(event === null || event === void 0 ? void 0 : event.target);
        const tooltipData = (_a = options === null || options === void 0 ? void 0 : options.data) !== null && _a !== void 0 ? _a : (0, tooltip_2.getTooltipData)({
            spreadsheet: this,
            cellInfos,
            targetCell,
            options: Object.assign({ enableFormat: true }, options),
        });
        return this.showTooltip({
            data: tooltipData,
            position: {
                x: event.clientX,
                y: event.clientY,
            },
            options,
            event,
            content,
        });
    }
    hideTooltip() {
        var _a, _b;
        (_b = (_a = this.tooltip) === null || _a === void 0 ? void 0 : _a.hide) === null || _b === void 0 ? void 0 : _b.call(_a);
    }
    destroyTooltip() {
        var _a, _b;
        (_b = (_a = this.tooltip) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
    }
    registerIcons() {
        const customSVGIcons = this.options.customSVGIcons;
        if ((0, lodash_1.isEmpty)(customSVGIcons)) {
            return;
        }
        (0, lodash_1.forEach)(customSVGIcons, (customSVGIcon) => {
            (0, factory_1.registerIcon)(customSVGIcon.name, customSVGIcon.src);
        });
    }
    /**
     * 更新表格数据
     * @param dataCfg 数据源配置
     * @param reset 是否重置数据源配置, 直接使用传入的 dataCfg，不再与之前的配置进行合并
     * @example s2.setDataCfg(dataCfg)
     * @example s2.setDataCfg(dataCfg, true)
     */
    setDataCfg(dataCfg, reset) {
        this.store.set('originalDataCfg', dataCfg);
        if (reset) {
            this.dataCfg = (0, merge_1.setupDataConfig)(dataCfg);
        }
        else {
            this.dataCfg = (0, merge_1.setupDataConfig)(this.dataCfg, dataCfg);
        }
        // clear value ranger after each updated data cfg
        (0, state_controller_1.clearValueRangeState)(this);
    }
    /**
     * 更新表格配置
     * @param options 配置
     * @param reset 是否重置配置, 直接使用传入的 options，不再与之前的配置进行合并
     * @example s2.setOptions(dataCfg)
     * @example s2.setOptions(dataCfg, true)
     */
    setOptions(options, reset) {
        var _a;
        this.hideTooltip();
        if (reset) {
            this.setupOptions(options);
        }
        else {
            this.options = (0, merge_1.customMerge)(this.options, options);
        }
        if (reset || ((_a = options === null || options === void 0 ? void 0 : options.tooltip) === null || _a === void 0 ? void 0 : _a.render)) {
            this.initTooltip();
        }
        this.resetHiddenColumnsDetailInfoIfNeeded();
        this.registerIcons();
    }
    /**
     * 重置表格数据
     * @example s2.resetDataCfg()
     */
    resetDataCfg() {
        this.setDataCfg(null, true);
    }
    /**
     * 重置表格配置
     * @example s2.resetOptions()
     */
    resetOptions() {
        this.setOptions(null, true);
    }
    resetHiddenColumnsDetailInfoIfNeeded() {
        var _a;
        if (!(0, lodash_1.isEmpty)((_a = this.options.interaction) === null || _a === void 0 ? void 0 : _a.hiddenColumnFields)) {
            this.store.set('hiddenColumnsDetail', []);
        }
    }
    doRender(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // 防止表格卸载后, 再次调用 render 函数的报错
            const canvasElement = this.getCanvasElement();
            // 使用 isConnected 替代 body.contains 检查 DOM 连接状态，兼容Shadow DOM
            if (!canvasElement || !canvasElement.isConnected) {
                return;
            }
            const { reloadData = true, rebuildDataSet = false, rebuildHiddenColumnsDetail = true, } = options || {};
            this.emit(constant_1.S2Event.LAYOUT_BEFORE_RENDER);
            if (rebuildDataSet) {
                this.dataSet = this.getDataSet();
            }
            if (reloadData) {
                this.clearDrillDownData('', true);
                this.dataSet.setDataCfg(this.dataCfg);
            }
            this.buildFacet();
            if (rebuildHiddenColumnsDetail) {
                yield this.initHiddenColumnsDetail();
            }
            this.emit(constant_1.S2Event.LAYOUT_AFTER_RENDER);
        });
    }
    /**
     * 渲染表格
     * @param reloadData
     * @param options
     * @example
        s2.render(true)
        s2.render(false)
        s2.render({
          reloadData: true;
          rebuildDataSet: true;
          rebuildHiddenColumnsDetail: true;
        })
     */
    render(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (this.destroyed) {
                return;
            }
            const renderOptions = typeof options === 'boolean'
                ? {
                    reloadData: options,
                }
                : options;
            yield this.container.ready;
            yield this.doRender(renderOptions);
        });
    }
    mountSheetInstance() {
        const canvas = this.getCanvasElement();
        if (canvas) {
            // eslint-disable-next-line no-underscore-dangle
            canvas.__s2_instance__ = this;
        }
    }
    unmountSheetInstance() {
        const canvas = this.getCanvasElement();
        if (canvas) {
            // @ts-ignore
            // eslint-disable-next-line no-underscore-dangle
            delete canvas.__s2_instance__;
        }
    }
    /**
     * 卸载表格
     * @example s2.destroy()
     */
    destroy() {
        var _a, _b, _c, _d, _e;
        if (this.destroyed) {
            return;
        }
        this.destroyed = true;
        this.restoreOverscrollBehavior();
        this.emit(constant_1.S2Event.LAYOUT_DESTROY);
        (_a = this.facet) === null || _a === void 0 ? void 0 : _a.destroy();
        (_b = this.hdAdapter) === null || _b === void 0 ? void 0 : _b.destroy();
        (_c = this.interaction) === null || _c === void 0 ? void 0 : _c.destroy();
        (_d = this.store) === null || _d === void 0 ? void 0 : _d.clear();
        this.destroyTooltip();
        this.clearCanvasEvent();
        this.unmountSheetInstance();
        (_e = this.container) === null || _e === void 0 ? void 0 : _e.destroy();
        (0, canvas_1.removeOffscreenCanvas)();
    }
    setThemeName(name) {
        this.themeName = name;
    }
    setThemeCfg(themeCfg = {}, getCustomTheme) {
        const theme = (themeCfg === null || themeCfg === void 0 ? void 0 : themeCfg.theme) || {};
        const newTheme = (0, theme_1.getTheme)(Object.assign(Object.assign({}, themeCfg), { spreadsheet: this, getCustomTheme }));
        this.theme = (0, merge_1.customMerge)(newTheme, theme);
        this.setThemeName(themeCfg === null || themeCfg === void 0 ? void 0 : themeCfg.name);
    }
    setTheme(theme) {
        this.theme = (0, merge_1.customMerge)(this.theme, theme);
    }
    getTheme() {
        return this.theme;
    }
    getThemeName() {
        return this.themeName;
    }
    /**
     * 更新分页配置
     */
    updatePagination(pagination) {
        this.options = (0, merge_1.customMerge)(this.options, {
            pagination,
        });
        // 清空滚动进度
        this.facet.resetScrollOffset();
    }
    /**
     * 修改表格画布大小，不用重新加载数据
     * @param width
     * @param height
     */
    changeSheetSize(width = this.options.width, height = this.options.height) {
        const canvas = this.getCanvasElement();
        const { width: containerWidth, height: containerHeight } = this.container.getConfig();
        const isSizeChanged = width !== containerWidth || height !== containerHeight;
        if (!isSizeChanged || !canvas) {
            return;
        }
        this.options = (0, merge_1.customMerge)(this.options, { width, height });
        // resize the canvas
        this.container.resize(width, height);
    }
    on(event, listener) {
        return super.on(event, listener);
    }
    emit(event, ...args) {
        super.emit(event, ...args);
    }
    /**
     * 获取 G Canvas 实例
     * @see https://g.antv.antgroup.com/api/renderer/canvas
     */
    getCanvas() {
        return this.container;
    }
    /**
     * 获取 G Canvas 配置
     * @see https://g.antv.antgroup.com/api/canvas/options
     */
    getCanvasConfig() {
        return this.getCanvas().getConfig();
    }
    /**
     * 获取 <canvas/> HTML 元素
     */
    getCanvasElement() {
        return this.getCanvas()
            .getContextService()
            .getDomElement();
    }
    getLayoutWidthType() {
        var _a;
        return (_a = this.options.style) === null || _a === void 0 ? void 0 : _a.layoutWidthType;
    }
    isCellType(cell) {
        return cell instanceof cell_1.BaseCell;
    }
    // 获取当前 cell 实例
    getCell(target) {
        let parent = target;
        // 一直索引到 g 顶层的 Canvas 来检查是否在指定的 cell 中
        while (parent && !(parent instanceof g_1.Canvas)) {
            if (this.isCellType(parent)) {
                // 在单元格中则返回
                return parent;
            }
            parent = parent === null || parent === void 0 ? void 0 : parent.parentNode;
        }
        return null;
    }
    // 获取当前 cell 类型
    getCellType(target) {
        const cell = this.getCell(target);
        return cell === null || cell === void 0 ? void 0 : cell.cellType;
    }
    /**
     * 获取当前维度对应的汇总配置
     */
    getTotalsConfig(dimension) {
        const { totals } = this.options;
        const { rows } = this.dataSet.fields;
        const totalConfig = (0, lodash_1.get)(totals, (0, lodash_1.includes)(rows, dimension) ? 'row' : 'col', {});
        const showSubTotals = totalConfig.showSubTotals &&
            (0, lodash_1.includes)(totalConfig.subTotalsDimensions, dimension)
            ? totalConfig.showSubTotals
            : false;
        return Object.assign(Object.assign({ grandTotalsLabel: (0, i18n_1.i18n)('总计'), subTotalsLabel: (0, i18n_1.i18n)('小计'), grandTotalsGroupDimensions: [], subTotalsGroupDimensions: [] }, totalConfig), { showSubTotals });
    }
    /**
     * Create all related groups, contains:
     * 1. container -- base canvas group
     * 2. backgroundGroup
     * 3. panelGroup -- main facet group belongs to
     * 4. foregroundGroup
     * @param dom
     * @private
     */
    initContainer(dom) {
        const { width, height, device, transformCanvasConfig, rendererConfig } = this.options;
        const renderer = new g_canvas_1.Renderer(rendererConfig);
        const canvasConfig = transformCanvasConfig === null || transformCanvasConfig === void 0 ? void 0 : transformCanvasConfig(renderer, this);
        /**
         * https://github.com/antvis/S2/issues/2857
         * 开启 supportsPointerEvents 后, G Canvas 会禁用 `touchAction`: https://github.com/antvis/G/blob/910c58e9bcba48cfa7bb0585064d27d3ae0bff4c/packages/g-plugin-dom-interaction/src/DOMInteractionPlugin.ts#L135
         */
        const supportsPointerEvents = !(0, is_mobile_1.isMobile)(device);
        this.container = new g_1.Canvas(Object.assign({ container: this.getMountContainer(dom), width,
            height,
            renderer,
            supportsPointerEvents, future: {
                // https://github.com/antvis/G/blob/master/packages/g-lite/src/types.ts#L563
                experimentalRICSyncRTree: true,
                experimentalCancelEventPropagation: true,
                // experimentalAttributeUpdateOptimization: true,
            } }, canvasConfig));
        this.setupContainerStyle();
    }
    setupContainerStyle() {
        const canvas = this.getCanvasElement();
        if (canvas) {
            // canvas 需要设置为块级元素, 不然和父元素有 5px 的高度差
            canvas.style.display = 'block';
            // 避免双击 canvas 造成的外部文本选中
            canvas.style.userSelect = 'none';
        }
    }
    // 初次渲染时, 如果配置了隐藏列, 则生成一次相关配置信息
    initHiddenColumnsDetail() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { hiddenColumnFields } = this.options.interaction;
            const lastHiddenColumnsDetail = this.store.get('hiddenColumnsDetail');
            // 隐藏列为空, 并且没有操作的情况下, 则无需生成
            if ((0, lodash_1.isEmpty)(hiddenColumnFields) && (0, lodash_1.isEmpty)(lastHiddenColumnsDetail)) {
                return;
            }
            yield (0, hide_columns_1.hideColumnsByThunkGroup)(this, hiddenColumnFields, true);
        });
    }
    clearCanvasEvent() {
        const canvasEvents = this.getEvents();
        (0, lodash_1.forIn)(canvasEvents, (_, event) => {
            this.off(event);
        });
    }
    updateSortMethodMap(nodeId, sortMethod, replace = false) {
        const lastSortMethodMap = !replace ? this.store.get('sortMethodMap') : null;
        this.store.set('sortMethodMap', Object.assign(Object.assign({}, lastSortMethodMap), { [nodeId]: sortMethod }));
    }
    getMenuDefaultSelectedKeys(nodeId) {
        const sortMethodMap = this.store.get('sortMethodMap');
        const selectedSortMethod = (0, lodash_1.get)(sortMethodMap, nodeId);
        return selectedSortMethod ? [selectedSortMethod] : [];
    }
    handleGroupSort(event, meta) {
        event.stopPropagation();
        this.interaction.addIntercepts([constant_1.InterceptType.HOVER]);
        const selectedKeys = this.getMenuDefaultSelectedKeys(meta === null || meta === void 0 ? void 0 : meta.id);
        const menuItems = this.isTableMode()
            ? (0, constant_1.getTooltipOperatorTableSortMenus)()
            : (0, constant_1.getTooltipOperatorSortMenus)();
        const operator = {
            menu: {
                selectedKeys,
                items: menuItems,
                onClick: (_a) => tslib_1.__awaiter(this, [_a], void 0, function* ({ key: sortMethod }) {
                    yield this.groupSortByMethod(sortMethod, meta);
                    this.emit(constant_1.S2Event.RANGE_SORTED, event);
                }),
            },
        };
        this.showTooltipWithInfo(event, [], {
            operator,
            onlyShowOperator: true,
        });
    }
    getSeriesNumberText() {
        var _a;
        const { text, enable } = (_a = this.options.seriesNumber) !== null && _a !== void 0 ? _a : {};
        if (!enable) {
            return '';
        }
        return text !== null && text !== void 0 ? text : (0, constant_1.getDefaultSeriesNumberText)();
    }
    enableAsyncExport() {
        return true;
    }
}
exports.SpreadSheet = SpreadSheet;
//# sourceMappingURL=spread-sheet.js.map