import { isEqual, isNil } from 'lodash-es';
import { EventEmitter } from 'eventemitter3';
import { MotaOffscreenCanvas2D } from './canvas2d';
import { Ticker, TickerFn } from 'mutate-animate';
import { ITransformUpdatable, Transform } from './transform';
import { logger } from '@motajs/common';
import { ElementNamespace, ComponentInternalInstance } from 'vue';
import { transformCanvas } from './utils';
import {
    ActionEventMap,
    ActionType,
    ERenderItemActionEvent,
    eventNameMap,
    EventProgress,
    IActionEvent,
    MouseType
} from './event';
import { vec3 } from 'gl-matrix';

export type RenderFunction = (
    canvas: MotaOffscreenCanvas2D,
    transform: Transform
) => void;

export type RenderItemPosition = 'absolute' | 'static';

export interface IRenderUpdater {
    /**
     * 更新这个渲染元素
     * @param item 触发更新事件的元素，不填默认为元素自身触发
     */
    update(item?: RenderItem): void;
}

export interface IRenderAnchor {
    /** 锚点横坐标，0表示最左端，1表示最右端 */
    anchorX: number;
    /** 锚点纵坐标，0表示最上端，1表示最下端 */
    anchorY: number;

    /**
     * 设置渲染元素的位置锚点
     * @param x 锚点的横坐标，小数，0表示最左边，1表示最右边
     * @param y 锚点的纵坐标，小数，0表示最上边，1表示最下边
     */
    setAnchor(x: number, y: number): void;
}

export interface IRenderConfig {
    /** 是否是高清画布 */
    highResolution: boolean;
    /** 是否启用抗锯齿 */
    antiAliasing: boolean;

    /**
     * 设置当前渲染元素是否使用高清画布
     * @param hd 是否高清
     */
    setHD(hd: boolean): void;

    /**
     * 设置当前渲染元素是否启用抗锯齿
     * @param anti 是否抗锯齿
     */
    setAntiAliasing(anti: boolean): void;
}

export interface IRenderChildable {
    /** 当前元素的子元素 */
    children: Set<RenderItem>;

    /**
     * 向这个元素添加子元素
     * @param child 添加的元素
     */
    appendChild(...child: RenderItem<any>[]): void;

    /**
     * 移除这个元素中的某个子元素
     * @param child 要移除的元素
     */
    removeChild(...child: RenderItem<any>[]): void;

    /**
     * 在下一个tick的渲染前对子元素进行排序
     */
    requestSort(): void;
}

export interface IRenderFrame {
    /**
     * 在下一帧渲染之前执行函数，常用于渲染前数据更新，理论上不应当用于渲染，不保证运行顺序
     * @param fn 执行的函数
     */
    requestBeforeFrame(fn: () => void): void;

    /**
     * 在下一帧渲染之后执行函数，理论上不应当用于渲染，不保证运行顺序
     * @param fn 执行的函数
     */
    requestAfterFrame(fn: () => void): void;

    /**
     * 在下一帧渲染时执行函数，理论上应当只用于渲染（即{@link RenderItem.update}方法），且不保证运行顺序
     * @param fn 执行的函数
     */
    requestRenderFrame(fn: () => void): void;
}

export interface IRenderTickerSupport {
    /**
     * 委托ticker，让其在指定时间范围内每帧执行对应函数，超过时间后自动删除
     * @param fn 每帧执行的函数
     * @param time 函数持续时间，不填代表不会自动删除，需要手动删除
     * @param end 持续时间结束后执行的函数
     * @returns 委托id，可用于删除
     */
    delegateTicker(fn: TickerFn, time?: number, end?: () => void): number;

    /**
     * 移除ticker函数
     * @param id 函数id，也就是{@link IRenderTickerSupport.delegateTicker}的返回值
     * @param callEnd 是否调用结束函数，即{@link IRenderTickerSupport.delegateTicker}的end参数，默认调用
     * @returns 是否删除成功，比如对应ticker不存在，就是删除失败
     */
    removeTicker(id: number, callEnd?: boolean): boolean;

    /**
     * 检查是否包含一个委托函数
     * @param id 函数id
     */
    hasTicker(id: number): boolean;
}

export interface IRenderEvent {
    /**
     * 当触发缩放事件时，此函数执行的内容
     * @param scale 缩放至的缩放比
     */
    onResize(scale: number): void;
}

export interface IRenderVueSupport {
    /**
     * 在 jsx, vue 中当属性改变后触发此函数，用于处理响应式等情况
     * @param key 属性键名
     * @param prevValue 该属性先前的数值
     * @param nextValue 该属性当前的数值
     * @param namespace 元素命名空间
     * @param parentComponent 元素的父组件
     */
    patchProp(
        key: string,
        prevValue: any,
        nextValue: any,
        namespace?: ElementNamespace,
        parentComponent?: ComponentInternalInstance | null
    ): void;
}

export interface IRenderTreeRoot {
    readonly isRoot: true;

    /**
     * 将一个渲染元素连接到此根元素
     * @param item 要连接到此根元素的渲染元素
     */
    connect(item: RenderItem): void;

    /**
     * 将已连接的渲染元素从此根元素中去掉
     * @param item 要取消连接的渲染元素
     */
    disconnect(item: RenderItem): void;

    /**
     * 修改已连接的元素的 id
     * @param item 修改了 id 的元素
     * @param previous 先前的元素 id
     * @param current 现在的元素 id
     */
    modifyId(item: RenderItem, previous: string, current: string): void;

    /**
     * 获取渲染至的目标画布，即显示在画面上的画布
     */
    getCanvas(): HTMLCanvasElement;

    /**
     * 当鼠标覆盖在某个元素上时执行
     * @param element 鼠标覆盖的元素
     */
    hoverElement(element: RenderItem): void;
}

interface RenderItemCanvasData {
    autoScale: boolean;
}

export interface ERenderItemEvent extends ERenderItemActionEvent {
    beforeRender: [transform: Transform];
    afterRender: [transform: Transform];
    destroy: [];
    transform: [item: RenderItem, transform: Transform];
}

interface TickerDelegation {
    fn: TickerFn;
    timeout?: number;
    endFn?: () => void;
}

const beforeFrame: (() => void)[] = [];
const afterFrame: (() => void)[] = [];
const renderFrame: (() => void)[] = [];

let count = 0;
export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
    extends EventEmitter<ERenderItemEvent | E>
    implements
        IRenderUpdater,
        IRenderAnchor,
        IRenderConfig,
        IRenderFrame,
        IRenderTickerSupport,
        IRenderChildable,
        IRenderVueSupport,
        ITransformUpdatable,
        IRenderEvent
{
    /** 渲染的全局ticker */
    static ticker: Ticker = new Ticker();
    /** 包括但不限于怪物、npc、自动元件的动画帧数 */
    static animatedFrame: number = 0;
    /** ticker委托映射 */
    static tickerMap: Map<number, TickerDelegation> = new Map();
    /** ticker委托id */
    static tickerId: number = 0;

    readonly uid: number = count++;

    //#region 元素属性

    /** 是否是注释元素 */
    readonly isComment: boolean = false;

    private _id: string = '';
    /**
     * 元素的 id，原则上不可重复
     */
    get id(): string {
        return this._id;
    }
    set id(v: string) {
        this.checkRoot();
        const prev = this._id;
        this._id = v;
        this._root?.modifyId(this, prev, v);
    }

    /** 元素纵深，表示了遮挡关系 */
    zIndex: number = 0;

    width: number = 200;
    height: number = 200;

    /** 渲染锚点，(0,0)表示左上角，(1,1)表示右下角 */
    anchorX: number = 0;
    /** 渲染锚点，(0,0)表示左上角，(1,1)表示右下角 */
    anchorY: number = 0;

    /** 渲染模式，absolute表示绝对位置，static表示跟随摄像机移动 */
    type: RenderItemPosition = 'static';
    /** 是否是高清画布 */
    highResolution: boolean = true;
    /** 是否抗锯齿 */
    antiAliasing: boolean = true;
    /** 是否被隐藏 */
    hidden: boolean = false;
    /** 滤镜 */
    filter: string = 'none';
    /** 混合方式 */
    composite: GlobalCompositeOperation = 'source-over';
    /** 不透明度 */
    alpha: number = 1;
    /** 缩放比 */
    protected scale: number = 1;

    /** 鼠标覆盖在此元素上时的光标样式 */
    cursor: string = 'inherit';
    /** 该元素是否忽略交互事件 */
    noEvent: boolean = false;

    get x() {
        return this._transform.x;
    }
    get y() {
        return this._transform.y;
    }

    /** 该元素的变换矩阵 */
    private _transform: Transform = new Transform();
    set transform(value: Transform) {
        this._transform.bind();
        this._transform = value;
        value.bind(this);
    }
    get transform() {
        return this._transform;
    }

    //#endregion

    //#region 父子关系

    private _parent?: RenderItem;
    /** 当前元素的父元素 */
    get parent() {
        return this._parent;
    }
    /** 当前元素是否为根元素，如果是根元素，那么必须实现 `IRenderTreeRoot` 接口 */
    readonly isRoot: boolean = false;

    private _root?: RenderItem & IRenderTreeRoot;
    get root() {
        return this._root;
    }

    /** 当前元素是否已经连接至任意根元素 */
    get connected() {
        return !!this._root;
    }

    /** 该渲染元素的子元素 */
    children: Set<RenderItem<ERenderItemEvent>> = new Set();

    //#endregion

    //#region 渲染配置与缓存
    /** 渲染缓存信息 */
    protected cache: MotaOffscreenCanvas2D;
    /** 是否需要更新缓存 */
    protected cacheDirty: boolean = false;
    /** 是否启用缓存机制 */
    readonly enableCache: boolean = true;
    /** 是否启用transform下穿机制，即画布的变换是否会继续作用到下一层画布 */
    readonly transformFallThrough: boolean = false;
    /** 这个渲染元素使用到的所有画布 */
    protected readonly canvases: Set<MotaOffscreenCanvas2D> = new Set();
    /** 这个渲染元素每个画布的配置信息 */
    private readonly canvasMap: WeakMap<
        MotaOffscreenCanvas2D,
        RenderItemCanvasData
    > = new WeakMap();
    //#endregion

    //#region 交互事件

    /** 是否调用了 `ev.stopPropagation` */
    protected propagationStoped: Map<ActionType, boolean> = new Map();
    /** 捕获阶段缓存的事件对象 */
    private cachedEvent: Map<ActionType, IActionEvent> = new Map();
    /** 下穿模式下当前下穿过来的变换矩阵 */
    private fallTransform?: Transform;
    /** 是否在元素内 */
    private inElement: boolean = false;
    /** 鼠标标识符映射，键为按下的鼠标按键类型，值表示本次操作的唯一标识符，在按下、移动、抬起过程中保持一致 */
    protected mouseId: Map<MouseType, number> = new Map();
    /** 当前所有的触摸标识符 */
    readonly touchId: Set<number> = new Set();

    //#endregion

    //#region debug

    /** 是否需要禁用更新，如果出现更新，那么发出警告并停止更新操作 */
    private forbidUpdate: boolean = false;

    //#endregion

    constructor(
        type: RenderItemPosition,
        enableCache: boolean = true,
        transformFallThrough: boolean = false
    ) {
        super();

        this.enableCache = enableCache;
        this.transformFallThrough = transformFallThrough;
        this.type = type;

        this._transform.bind(this);
        this.cache = this.requireCanvas();
        if (!enableCache) {
            this.cache.size(1, 1);
            this.deleteCanvas(this.cache);
        }
    }

    /**
     * 渲染函数
     * @param canvas 渲染至的画布
     * @param transform 当前变换矩阵的，渲染时已经进行变换处理，不需要对画布再次进行变换处理。
     *                  此参数可用于自己对元素进行变换处理，也会用于对子元素的处理。
     *                  例如对于`absolute`类型的元素，同时有对视角改变的需求，就可以通过此参数进行变换。
     *                  样板内置的`Layer`及`Damage`元素就是通过此方式实现的
     */
    protected abstract render(
        canvas: MotaOffscreenCanvas2D,
        transform: Transform
    ): void;

    /**
     * 渲染当前对象
     * @param canvas 渲染至的画布
     * @param transform 由父元素传递过来的变换矩阵
     */
    renderContent(canvas: MotaOffscreenCanvas2D, transform: Transform) {
        if (this.hidden) return;
        this.forbidUpdate = true;
        this.emit('beforeRender', transform);
        if (this.transformFallThrough) {
            this.fallTransform = transform;
        }
        const tran = this.transformFallThrough ? transform : this._transform;

        const ax = -this.anchorX * this.width;
        const ay = -this.anchorY * this.height;

        const ctx = canvas.ctx;
        ctx.save();
        if (this.type === 'static') transformCanvas(canvas, tran);
        ctx.filter = this.filter;
        ctx.globalAlpha = this.alpha;
        ctx.globalCompositeOperation = this.composite;
        if (this.enableCache) {
            const { width, height } = this.cache;
            if (this.cacheDirty) {
                this.cache.clear();
                this.render(this.cache, tran);
                this.cacheDirty = false;
            }

            canvas.setAntiAliasing(false);
            canvas.ctx.drawImage(this.cache.canvas, ax, ay, width, height);
        } else {
            canvas.setAntiAliasing(this.antiAliasing);
            canvas.ctx.translate(ax, ay);
            this.render(canvas, tran);
            this.cacheDirty = false;
        }
        ctx.restore();
        this.emit('afterRender', transform);
        this.forbidUpdate = false;
    }

    /**
     * 申请一个 `MotaOffscreenCanvas2D`，即申请一个画布
     * @param alpha 是否启用画布的 alpha 通道
     * @param autoScale 是否自动跟随缩放
     */
    requireCanvas(alpha: boolean = true, autoScale: boolean = true) {
        const canvas = new MotaOffscreenCanvas2D(alpha);
        if (autoScale) {
            canvas.setScale(this.scale);
        } else {
            canvas.setScale(1);
        }
        this.canvases.add(canvas);
        this.canvasMap.set(canvas, { autoScale });
        return canvas;
    }

    /**
     * 删除由 `requireCanvas` 申请的画布，当画布不再使用时，可以用该方法删除画布
     * @param canvas 要删除的画布
     */
    deleteCanvas(canvas: MotaOffscreenCanvas2D) {
        this.canvases.delete(canvas);
        this.canvasMap.delete(canvas);
    }

    //#region 事件处理

    onResize(scale: number): void {
        this.scale = scale;
        this.canvases.forEach(v => {
            if (this.canvasMap.get(v)?.autoScale) {
                v.setScale(scale);
            }
        });
        this.update();
    }

    /**
     * 获取当前元素的缩放比，它与根元素应当保持一致
     */
    getScale() {
        return this.scale;
    }

    //#region 修改元素属性

    /**
     * 修改这个对象的大小
     */
    size(width: number, height: number): void {
        if (width === this.width && height === this.height) return;
        this.width = width;
        this.height = height;
        if (this.enableCache) {
            this.cache.size(width, height);
        }
        this.update(this);
    }

    /**
     * 设置这个元素的位置，等效于`transform.setTranslate(x, y)`
     * @param x 横坐标
     * @param y 纵坐标
     */
    pos(x: number, y: number) {
        // 这个函数会调用 update，因此不再手动调用 update
        this._transform.setTranslate(x, y);
    }

    /**
     * 设置本元素的滤镜
     * @param filter 滤镜
     */
    setFilter(filter: string) {
        this.filter = filter;
        // 设置滤镜时，不需要更新自身的缓存，直接调用父元素的更新即可
        this._parent?.update();
    }

    /**
     * 设置本元素渲染时的混合方式
     * @param composite 混合方式
     */
    setComposite(composite: GlobalCompositeOperation) {
        this.composite = composite;
        // 设置混合模式时，不需要更新自身的缓存，直接调用父元素的更新即可
        this._parent?.update();
    }

    /**
     * 设置本元素的不透明度
     * @param alpha 不透明度
     */
    setAlpha(alpha: number) {
        this.alpha = alpha;
        // 设置不透明度时，不需要更新自身的缓存，直接调用父元素的更新即可
        this._parent?.update();
    }

    setHD(hd: boolean): void {
        this.highResolution = hd;
        if (this.enableCache) {
            this.cache.setHD(hd);
        }
        this.update(this);
    }

    setAntiAliasing(anti: boolean): void {
        this.antiAliasing = anti;
        if (this.enableCache) {
            this.cache.setAntiAliasing(anti);
        }
        this.update(this);
    }

    setZIndex(zIndex: number) {
        this.zIndex = zIndex;
        this.parent?.requestSort();
    }

    setAnchor(x: number, y: number): void {
        this.anchorX = x;
        this.anchorY = y;
        // 设置锚点时，不需要更新自身的缓存，直接调用父元素的更新即可
        this._parent?.update();
    }

    /**
     * 隐藏这个元素
     */
    hide() {
        if (this.hidden) return;
        this.hidden = true;
        this.update(this);
    }

    /**
     * 显示这个元素
     */
    show() {
        if (!this.hidden) return;
        this.hidden = false;
        this.refreshAllChildren();
    }

    //#endregion

    //#region 功能方法

    /**
     * 获取当前元素的绝对位置（不建议使用，因为应当很少会有获取绝对位置的需求）
     */
    getAbsolutePosition(x: number = 0, y: number = 0): [number, number] {
        if (this.type === 'absolute') {
            if (this.parent) return this.parent.getAbsolutePosition(0, 0);
            else return [0, 0];
        }
        const [px, py] = this._transform.transformed(x, y);
        if (!this.parent) return [px, py];
        else {
            const [px, py] = this.parent.getAbsolutePosition();
            return [x + px, y + py];
        }
    }

    /**
     * 获取到可以包围这个元素的最小矩形，相对于父元素
     */
    getBoundingRect(): DOMRectReadOnly {
        if (this.type === 'absolute') {
            return new DOMRectReadOnly(0, 0, this.width, this.height);
        }
        const tran = this.transformFallThrough
            ? this.fallTransform
            : this._transform;
        if (!tran) return new DOMRectReadOnly(0, 0, this.width, this.height);
        const [x1, y1] = tran.transformed(
            -this.anchorX * this.width,
            -this.anchorY * this.height
        );
        const [x2, y2] = tran.transformed(
            this.width * (1 - this.anchorX),
            -this.anchorY * this.height
        );
        const [x3, y3] = tran.transformed(
            -this.anchorX * this.width,
            this.height * (1 - this.anchorY)
        );
        const [x4, y4] = tran.transformed(
            this.width * (1 - this.anchorX),
            this.height * (1 - this.anchorY)
        );
        const left = Math.min(x1, x2, x3, x4);
        const right = Math.max(x1, x2, x3, x4);
        const top = Math.min(y1, y2, y3, y4);
        const bottom = Math.max(y1, y2, y3, y4);
        return new DOMRectReadOnly(left, top, right - left, bottom - top);
    }

    update(item: RenderItem<any> = this): void {
        if (import.meta.env.DEV) {
            if (this.forbidUpdate) {
                logger.warn(61, this.constructor.name, this.uid.toString());
            }
        }
        if (this._parent) {
            if (this.cacheDirty && this._parent.cacheDirty) return;
            this.cacheDirty = true;
            if (this.hidden) return;
            this._parent.update(item);
        } else {
            if (this.cacheDirty) return;
            this.cacheDirty = true;
        }
    }

    updateTransform() {
        // 更新变换矩阵时，不需要更新自身的缓存，直接调用父元素的更新即可
        this._parent?.update();
        this.emit('transform', this, this._transform);
    }

    //#endregion

    //#region 动画帧与 ticker

    requestBeforeFrame(fn: () => void): void {
        beforeFrame.push(fn);
    }

    requestAfterFrame(fn: () => void): void {
        afterFrame.push(fn);
    }

    requestRenderFrame(fn: () => void): void {
        renderFrame.push(fn);
    }

    delegateTicker(fn: TickerFn, time?: number, end?: () => void): number {
        const id = RenderItem.tickerId++;
        if (typeof time === 'number' && time === 0) return id;
        const delegation: TickerDelegation = {
            fn,
            endFn: end
        };
        RenderItem.tickerMap.set(id, delegation);
        if (typeof time === 'number' && time < 2147438647 && time > 0) {
            delegation.timeout = window.setTimeout(() => {
                RenderItem.tickerMap.delete(id);
                end?.();
            }, time);
        }
        return id;
    }

    removeTicker(id: number, callEnd: boolean = true): boolean {
        const delegation = RenderItem.tickerMap.get(id);
        if (!delegation) return false;
        RenderItem.ticker.remove(delegation.fn);
        window.clearTimeout(delegation.timeout);
        if (callEnd) delegation.endFn?.();
        RenderItem.tickerMap.delete(id);
        return true;
    }

    hasTicker(id: number): boolean {
        return RenderItem.tickerMap.has(id);
    }

    //#endregion

    //#region 父子关系

    setRoot(item: RenderItem & IRenderTreeRoot) {
        this._root?.disconnect(this);
        this._root = item;
        item.connect(item);
    }

    checkRoot(): RenderItem | null {
        if (this._root) return this._root;
        if (this.isRoot) return this;
        let ele: RenderItem = this;
        while (!ele.isRoot) {
            if (ele._root) {
                this._root = ele._root;
                return this._root;
            }
            if (!ele._parent) {
                return null;
            } else {
                ele = ele._parent;
            }
        }
        this._root = ele as RenderItem & IRenderTreeRoot;
        return ele;
    }

    /**
     * 刷新所有子元素
     */
    refreshAllChildren() {
        if (this.children.size > 0) {
            const stack: RenderItem[] = [this];
            while (stack.length > 0) {
                const item = stack.pop();
                if (!item) continue;
                item.cacheDirty = true;
                item.children.forEach(v => stack.push(v));
            }
        }
        this.update(this);
    }

    /**
     * 将这个渲染元素添加到其他父元素上
     * @param parent 父元素
     */
    appendTo(parent: RenderItem) {
        this.remove();
        parent.children.add(this);
        this._parent = parent;
        parent.requestSort();
        this.update();
        this.checkRoot();
        this._root?.connect(this);
        this._transform.bind(this);
        this.onResize(parent.scale);
    }

    /**
     * 从渲染树中移除这个节点
     * @returns 是否移除成功
     */
    remove(): boolean {
        if (!this.parent) return false;
        const parent = this.parent;
        const success = parent.children.delete(this);
        this._parent = void 0;
        parent.requestSort();
        parent.update();
        this._transform.bind();
        if (!success) return false;
        this._root?.disconnect(this);
        this._root = void 0;
        return true;
    }

    /**
     * 添加子元素，默认没有任何行为且会抛出警告，你需要在自己的RenderItem继承类中复写它，才可以使用
     * @param child 子元素
     */
    appendChild(..._child: RenderItem<any>[]): void {
        logger.warn(35);
    }

    /**
     * 移除子元素，默认没有任何行为且会抛出警告，你需要在自己的RenderItem继承类中复写它，才可以使用
     * @param child 子元素
     */
    removeChild(..._child: RenderItem<any>[]): void {
        logger.warn(36);
    }

    /**
     * 申请对元素进行排序，默认没有任何行为且会抛出警告，你需要在自己的RenderItem继承类中复写它，才可以使用
     */
    requestSort(): void {
        logger.warn(37);
    }

    //#endregion

    //#region 交互事件

    /**
     * 根据事件类型和事件阶段获取事件名称
     * @param type 事件类型
     * @param progress 事件阶段
     */
    getEventName(
        type: ActionType,
        progress: EventProgress
    ): keyof ERenderItemActionEvent {
        if (type === ActionType.Enter || type === ActionType.Leave) {
            return eventNameMap[type];
        } else if (progress === EventProgress.Capture) {
            return `${eventNameMap[type]}Capture` as keyof ERenderItemActionEvent;
        } else {
            return eventNameMap[type];
        }
    }

    /**
     * 传递事件，即将事件传递给父元素或子元素等，可以通过 override 来实现自己的事件传递，
     * 例如 Container 元素就需要在捕获阶段将事件传递给所有子元素，
     * 默认行为是，捕获阶段触发自身冒泡，冒泡阶段触发父元素冒泡，适用于大部分不包含子元素的元素
     * @param type 事件类型
     * @param progress 事件阶段，捕获阶段或冒泡阶段
     * @param event 正在处理的事件对象
     */
    protected propagateEvent<T extends ActionType>(
        type: T,
        progress: EventProgress,
        event: ActionEventMap[T]
    ): void {
        if (progress === EventProgress.Capture) {
            this.bubbleEvent(type, event);
        } else {
            this.parent?.bubbleEvent(type, event);
        }
    }

    private handleEvent<T extends ActionType>(
        type: T,
        progress: EventProgress,
        event: ActionEventMap[T]
    ) {
        const ev = this.processEvent(type, progress, event);
        if (ev) {
            const name = this.getEventName(type, progress);
            this.emit(name, ev);
            if (!this.propagationStoped.get(type)) {
                this.propagateEvent(type, progress, ev);
            }
        }
        this.propagationStoped.set(type, false);
        return ev;
    }

    /**
     * 捕获事件
     * @param type 事件类型
     * @param event 由父元素传递来的事件
     */
    captureEvent<T extends ActionType>(type: T, event: ActionEventMap[T]) {
        return this.handleEvent(type, EventProgress.Capture, event);
    }

    /**
     * 冒泡事件
     * @param type 事件类型
     * @param event 由子元素传递来的事件
     */
    bubbleEvent<T extends ActionType>(type: T, event: ActionEventMap[T]) {
        return this.handleEvent(type, EventProgress.Bubble, event);
    }

    /**
     * 处理事件，用于根据上一级传递的事件内容生成新的事件内容，并执行一些事件的默认行为
     * @param type 事件类型
     * @param progress 事件阶段，捕获阶段还是冒泡阶段
     * @param event 由上一级（捕获阶段的父元素，冒泡阶段的子元素）传递来的事件内容
     */
    protected processEvent<T extends ActionType>(
        type: T,
        progress: EventProgress,
        event: ActionEventMap[T]
    ): ActionEventMap[T] | null {
        if (this.noEvent) return null;
        if (progress === EventProgress.Capture) {
            // 捕获阶段需要计算鼠标位置
            const tran = this.transformFallThrough
                ? this.fallTransform
                : this._transform;
            if (!tran) return null;
            const [nx, ny] = this.calActionPosition(event, tran);
            const inElement = this.isActionInElement(nx, ny);
            // 在元素范围内，执行事件
            const newEvent: ActionEventMap[T] = {
                ...event,
                offsetX: nx,
                offsetY: ny,
                target: this,
                stopPropagation: () => {
                    this.propagationStoped.set(type, true);
                }
            };
            this.inElement = inElement;
            if (!this.processCapture(type, newEvent, inElement)) return null;
            this.cachedEvent.set(type, newEvent);
            return newEvent;
        } else {
            const newEvent = this.cachedEvent.get(type) as ActionEventMap[T];
            this.processBubble(type, newEvent, this.inElement);
            this.cachedEvent.delete(type);
            return newEvent;
        }
    }

    /**
     * 处理捕获阶段的事件，可以通过 override 来添加新内容，注意调用 `super.processCapture` 来执行默认行为
     * @param type 事件类型
     * @param event 正在处理的事件对象
     * @param inElement 当前鼠标是否在元素内
     * @returns 是否继续传递事件
     */
    protected processCapture<T extends ActionType>(
        type: T,
        event: ActionEventMap[T],
        inElement: boolean
    ): boolean {
        switch (type) {
            case ActionType.Move: {
                if (inElement) {
                    this._root?.hoverElement(this);
                }
                break;
            }
            case ActionType.Down: {
                // 记录标识符，用于判定 click
                if (!inElement) return false;
                if (event.touch) {
                    this.touchId.add(event.identifier);
                } else {
                    this.mouseId.set(event.type, event.identifier);
                }
                break;
            }
            case ActionType.Click: {
                if (!inElement) return false;
                if (event.touch) {
                    if (!this.touchId.has(event.identifier)) {
                        return false;
                    }
                    this.touchId.delete(event.identifier);
                } else {
                    if (this.mouseId.get(event.type) !== event.identifier) {
                        this.mouseId.delete(event.type);
                        return false;
                    }
                    this.mouseId.delete(event.type);
                }
                break;
            }
        }

        return inElement;
    }

    /**
     * 处理冒泡阶段的事件，可以通过 override 来添加新内容，注意调用 `super.processBubble` 来执行默认行为
     * @param type 事件类型
     * @param event 正在处理的事件对象
     * @param inElement 当前鼠标是否在元素内
     * @returns 是否继续传递事件
     */
    protected processBubble<T extends ActionType>(
        type: T,
        _event: ActionEventMap[T],
        inElement: boolean
    ): boolean {
        switch (type) {
            case ActionType.Enter:
            case ActionType.Leave:
                return false;
        }
        return inElement;
    }

    /**
     * 计算一个点击事件在该元素上的位置
     * @param event 触发的事件
     * @param transform 当前的变换矩阵
     */
    protected calActionPosition(
        event: IActionEvent,
        transform: Transform
    ): vec3 {
        const ax = this.anchorX * this.width;
        const ay = this.anchorY * this.height;
        if (this.type === 'absolute') {
            return [event.offsetX + ax, event.offsetY + ay, 0];
        } else {
            const [tx, ty] = transform.untransformed(
                event.offsetX,
                event.offsetY
            );
            return [tx + ax, ty + ay, 0];
        }
    }

    /**
     * 判断一个点击事件是否在元素内，可以通过 override 来修改其行为
     * @param x 横坐标
     * @param y 纵坐标
     */
    protected isActionInElement(x: number, y: number) {
        return x >= 0 && x < this.width && y >= 0 && y < this.height;
    }

    actionClick() {}

    actionDown() {}

    actionUp() {}

    actionMove() {}

    actionEnter() {}

    actionLeave() {}

    actionWheel() {}

    //#endregion

    //#region vue支持 props处理

    /**
     * 判断一个prop是否是期望类型
     * @param value 实际值
     * @param expected 期望类型
     * @param key 键名
     */
    protected assertType(value: any, expected: string, key: string): boolean;
    /**
     * 判断一个prop是否是期望类型
     * @param value 实际值
     * @param expected 期望类型
     * @param key 键名
     */
    protected assertType<T>(
        value: any,
        expected: new (...params: any[]) => T,
        key: string
    ): value is T;
    protected assertType(
        value: any,
        expected: string | (new (...params: any[]) => any),
        key: string
    ) {
        if (typeof expected === 'string') {
            const type = typeof value;
            if (type !== expected) {
                logger.error(21, key, expected, type);
                return false;
            } else {
                return true;
            }
        } else {
            if (value instanceof expected) {
                return true;
            } else {
                logger.error(
                    21,
                    key,
                    expected.name,
                    value?.constructor?.name ?? typeof value
                );
                return false;
            }
        }
    }

    /**
     * 解析事件key
     * @param key 键名
     * @returns 返回字符串表示解析后的键名，返回布尔值表示不是事件
     */
    protected parseEvent(key: string): string | false {
        if (key.startsWith('on')) {
            const code = key.charCodeAt(2);
            if (code >= 65 && code <= 90) {
                return key[2].toLowerCase() + key.slice(3);
            }
        }
        return false;
    }

    /**
     * 自定义处理 props，自定义元素需要 override 此函数来处理 props
     * @param key 传入的 props 的键名
     * @param prevValue 这个 props 之前的值
     * @param nextValue 这个 props 传入的值
     * @returns 是否处理成功
     */
    protected handleProps(
        _key: string,
        _prevValue: any,
        _nextValue: any
    ): boolean {
        return false;
    }

    patchProp(
        key: string,
        prevValue: any,
        nextValue: any,
        _namespace?: ElementNamespace,
        _parentComponent?: ComponentInternalInstance | null
    ): void {
        if (isNil(prevValue) && isNil(nextValue)) return;
        if (this.handleProps(key, prevValue, nextValue)) return;
        switch (key) {
            case 'x': {
                if (!this.assertType(nextValue, 'number', key)) return;
                this.pos(nextValue, this._transform.y);
                return;
            }
            case 'y': {
                if (!this.assertType(nextValue, 'number', key)) return;
                this.pos(this._transform.x, nextValue);
                return;
            }
            case 'anchorX': {
                if (!this.assertType(nextValue, 'number', key)) return;
                this.setAnchor(nextValue, this.anchorY);
                return;
            }
            case 'anchorY': {
                if (!this.assertType(nextValue, 'number', key)) return;
                this.setAnchor(this.anchorX, nextValue);
                return;
            }
            case 'zIndex': {
                if (!this.assertType(nextValue, 'number', key)) return;
                this.setZIndex(nextValue);
                return;
            }
            case 'width': {
                if (!this.assertType(nextValue, 'number', key)) return;
                this.size(nextValue, this.height);
                return;
            }
            case 'height': {
                if (!this.assertType(nextValue, 'number', key)) return;
                this.size(this.width, nextValue);
                return;
            }
            case 'filter': {
                if (!this.assertType(nextValue, 'string', key)) return;
                this.setFilter(nextValue);
                return;
            }
            case 'hd': {
                if (!this.assertType(nextValue, 'boolean', key)) return;
                this.setHD(nextValue);
                return;
            }
            case 'anti': {
                if (!this.assertType(nextValue, 'boolean', key)) return;
                this.setAntiAliasing(nextValue);
                return;
            }
            case 'noanti': {
                if (!this.assertType(nextValue, 'boolean', key)) return;
                this.setAntiAliasing(!nextValue);
                return;
            }
            case 'hidden': {
                if (!this.assertType(nextValue, 'boolean', key)) return;
                if (nextValue) this.hide();
                else this.show();
                return;
            }
            case 'transform': {
                if (!this.assertType(nextValue, Transform, key)) return;
                this.transform = nextValue;
                this.update();
                return;
            }
            case 'type': {
                if (!this.assertType(nextValue, 'string', key)) return;
                this.type = nextValue;
                this.update();
                return;
            }
            case 'id': {
                if (!this.assertType(nextValue, 'string', key)) return;
                this.id = nextValue;
                return;
            }
            case 'alpha': {
                if (!this.assertType(nextValue, 'number', key)) return;
                this.setAlpha(nextValue);
                return;
            }
            case 'composite': {
                if (!this.assertType(nextValue, 'string', key)) return;
                this.setComposite(nextValue);
                return;
            }
            case 'loc': {
                if (isEqual(nextValue, prevValue)) return;
                if (!this.assertType(nextValue, Array, key)) return;
                if (!isNil(nextValue[0]) && !isNil(nextValue[1])) {
                    this.pos(nextValue[0] as number, nextValue[1] as number);
                }
                if (!isNil(nextValue[2]) && !isNil(nextValue[3])) {
                    this.size(nextValue[2] as number, nextValue[3] as number);
                }
                if (!isNil(nextValue[4]) && !isNil(nextValue[5])) {
                    this.setAnchor(
                        nextValue[4] as number,
                        nextValue[5] as number
                    );
                }
                return;
            }
            case 'anc': {
                if (isEqual(nextValue, prevValue)) return;
                if (!this.assertType(nextValue, Array, key)) return;
                this.setAnchor(nextValue[0] as number, nextValue[1] as number);
                return;
            }
            case 'cursor': {
                if (!this.assertType(nextValue, 'string', key)) return;
                this.cursor = nextValue;
                return;
            }
            case 'scale': {
                if (isEqual(nextValue, prevValue)) return;
                if (!this.assertType(nextValue, Array, key)) return;
                this._transform.setScale(
                    nextValue[0] as number,
                    nextValue[1] as number
                );
                return;
            }
            case 'rotate': {
                if (!this.assertType(nextValue, 'number', key)) return;
                this._transform.setRotate(nextValue);
                return;
            }
            case 'noevent': {
                if (!this.assertType(nextValue, 'boolean', key)) return;
                this.noEvent = nextValue;
                return;
            }
        }
        const ev = this.parseEvent(key);
        if (ev) {
            if (prevValue) {
                this.off(ev as keyof ERenderItemEvent, prevValue);
            }
            this.on(ev as keyof ERenderItemEvent, nextValue);
        }
    }

    //#endregion

    /**
     * 摧毁这个渲染元素，摧毁后不应继续使用
     */
    destroy(): void {
        this.remove();
        this.emit('destroy');
        this.removeAllListeners();
        this.canvases.clear();
        this.cache.clear();
        this.propagationStoped.clear();
        this.cachedEvent.clear();
        this.mouseId.clear();
        this.touchId.clear();
        this.children.clear();
        this._root = void 0;
        this._parent = void 0;
    }
}

RenderItem.ticker.add(time => {
    // slice 是为了让函数里面的 request 进入下一帧执行
    if (beforeFrame.length > 0) {
        const arr = beforeFrame.slice();
        beforeFrame.splice(0);
        arr.forEach(v => v());
    }
    RenderItem.tickerMap.forEach(v => {
        v.fn(time);
    });
    if (renderFrame.length > 0) {
        const arr = renderFrame.slice();
        renderFrame.splice(0);
        arr.forEach(v => v());
    }
    if (afterFrame.length > 0) {
        const arr = afterFrame.slice();
        afterFrame.splice(0);
        arr.forEach(v => v());
    }
});
