/*
author:cy
version:1.0.0
date:2023.06.02
qq:1183875513
使用过程中遇到问题可以联系我
*/

import {
    CCClass,
    CCString,
    Color,
    Component,
    Enum,
    Label,
    Node,
    RichText, Sprite, SpriteFrame, UIRenderer,
    Widget,
    _decorator, assetManager
} from "cc";
import { EDITOR } from "cc/env";

let isInitDebugComp = false;

enum States {
    Default
}

/**
 * 会记录的组件及其属性
 */
const COMP_ATTR_RECORD = <const>{
    "UITransform": ["width", "height", "anchorX", "anchorY"],
    "Widget": ["isAlignBottom", "isAlignTop", "isAlignLeft", "isAlignRight", "isAlignVerticalCenter", "isAlignHorizontalCenter", 
                "isAbsoluteTop", "isAbsoluteBottom", "isAbsoluteLeft", "isAbsoluteRight", "isAbsoluteHorizontalCenter", "isAbsoluteVerticalCenter",
                "left", "right", "top", "bottom", "horizontalCenter", "verticalCenter", "alignMode", "alignFlags"],
    "UIOpacity": ["opacity"],
    "UIRenderer": ["color"],
    "Label": ["color", "string", "horizontalAlign", "verticalAlign", "fontSize", "fontFamily", "lineHeight", "overflow", "isBold", "isItalic", "isUnderline", "underlineHeight"],
    "RichText": ["string", "horizontalAlign", "verticalAlign", "fontSize", "fontFamily", "maxWidth", "lineHeight"],
    "Sprite": ["color", "spriteFrame", "grayscale", "sizeMode", "type", "trim"]
}

type KEY_OF_COMP_ATTR_RECORD = keyof typeof COMP_ATTR_RECORD;
type STRUCT_OF_COMP_ATTR_RECORD<K extends KEY_OF_COMP_ATTR_RECORD> = typeof COMP_ATTR_RECORD[K];

type RecordProps = {
    [K in KEY_OF_COMP_ATTR_RECORD]?: {[key in STRUCT_OF_COMP_ATTR_RECORD<K>[number]]:any};
} & {
    node: Node;
    x: number;
    y: number;
    scaleX: number;
    scaleY: number;
    angle: number;
    active: boolean;
};

/**
 * 判断在真正的编辑器模式中。
 * 由于编辑器预览 EDITOR 也为 true，
 * 但又不想让特定代码在编辑器预览执行
 */
//@ts-ignore
const REAL_EDITOR = EDITOR && !cc.GAME_VIEW;

const { ccclass, property, executeInEditMode, disallowMultiple } = _decorator;
@ccclass("UIState")
@executeInEditMode
@disallowMultiple
export default class UIState extends Component {
    @property
    private _states: string[] = ["Default"];

    @property({ type: [CCString], step: 1 })
    set states(value: string[]) {
        if (EDITOR) {
            // 状态数量减少时
            if (value.length < this._states.length){
                let hasData = false;
                for (let i = value.length; i < this._states.length; i++) {
                    hasData = !!(this._records![i] && this._records![i].length);
                    if (hasData) break;
                }
                // 二次确认
                if (hasData){
                    Editor.Dialog.warn("要删除的状态中含有数据,删除操作不可逆,是否继续?", {
                        cancel:1,
                        buttons: ["是", "否"]
                    }).then(returnValue=>{
                        // 否
                        if(returnValue.response === 1)
                            return;
                        
                        for (let i = value.length; i < this._states.length; i++) {
                            delete this._records![i];
                        }
                        this._states = value;
                        this.updateStateEnumList();

                        // 软刷新场景,编辑器会闪一下,应该有更好的接口可以不闪的刷新吧,不过懒得找了
                        Editor.Message.request("scene", "soft-reload");
                    });
                    return;
                }
            }
            this._states = value;
            this.updateStateEnumList();
        }else{
            this._states = value;
        }        
    }

    get states() {
        return this._states;
    }

    @property
    private _state: States = States.Default;

    set state(val: number) {
        if (this._state === val) return;
        // 编辑器模式时，切换状态前保存当前状态数据
        if (REAL_EDITOR) {
            this.walkNode(this.node, child => {
                this.recordBeforeStateChange(child);
                // this.removeListener(child);
            });
        }
        
        let stateRecord = this.records[val];
        // 新的状态不存在的话
        if (!stateRecord) {
            // 编辑器模式下,从当前状态复制
            if (REAL_EDITOR) {
                stateRecord = this.createState(val);
                const currStateRecord = this.records[this._state];

                currStateRecord.forEach(record => {
                    stateRecord.push(this.cloneRecord(record));
                });
            }
            else return;
        }

        this._state = val;
        this.applyState();
        if (REAL_EDITOR) this.onFocusInEditor!();
    }

    @property({ type: Enum(States) })
    get state() {
        return this._state;
    }

    // creator bug 用二维数组的数据结构，在编辑器里删除一个节点，再撤销会报错
    // 找不到原因，只能用对象实现了
    // private _records:RecordProps[][] = [];
    /** 关键：加上 @property 让编辑器序列化保存这个数据 */
    @property
    private _records?: { [k in number]: RecordProps[] } = undefined;

    private _backupRecords?: { [k in number]: RecordProps[] };

    get records() {
        // creator bug 在编辑器里删除一个节点，再撤销会重新赋初始值给组件所有
        // 带 @property 装饰器的属性，导致数据丢失，无奈只能用另一个变量来恢复数据了
        if (!this._records) this._records = this._backupRecords;
        return this._records!;
    }

    /** 根据 uuid 快速找到记录（当前状态） */
    private _uuidRecordMap?: Map<string, RecordProps>;

    /** 当前状态的节点记录，用于判断节点是否修改 */
    private _defaultNodeState = new Map<string, RecordProps>();

    onLoad() {
        // 编辑器模式下，确保一个场景或预制只初始化一次
        if (REAL_EDITOR) {
            if (!isInitDebugComp){
                isInitDebugComp = true;
                UIStateDecorator(Component);
            }
        }

        if (!this._records) this._records = {};
        this._backupRecords = this._records;

        if (REAL_EDITOR) this.updateStateEnumList();

        if (!this.records[this._state])
            this.createState(this._state);
        this.applyState();
    }

    private updateStateEnumList() {
        const enumList: { name: string; value: number }[] = [];
        this.states.forEach((state, index) => {
            enumList.push({ name: state, value: index });
        });
        CCClass.Attr.setClassAttr(this, "state", "enumList", enumList);
    }

    onDestroy() {
        if (REAL_EDITOR) this.saveCurrentState();
        
    }

    /**
     * 保存当前状态
     */
    saveCurrentState() {
        // 编辑器模式时
        this.walkNode(this.node, child => {
            this.recordBeforeStateChange(child);
        });
        console.log("已保存当前状态");
    }

    /** 必须要有个默认状态 */
    private createState(state:number) {
        const stateRecord:RecordProps[] = [];
        this.records[state] = stateRecord;
        return stateRecord;
    }

    private applyState() {
        let stateRecord = this.records[this._state];

        // 建立当前状态的缓存映射关系
        this._uuidRecordMap = new Map();
        stateRecord.forEach(record => {
            if (record.node) this._uuidRecordMap?.set(record.node.uuid, record);
        });

        // 应用状态
        for (let i = stateRecord.length - 1; i >= 0; i--) {
            const record = stateRecord[i];
            const node = record.node;

            // 删除无效的记录
            if (!node || !node.parent){
                stateRecord.splice(i, 1);
                continue;
            }

            if (node === this.node) continue;
            node.active = record.active!;
            node.angle = record.angle;
            node.setScale(record.scaleX, record.scaleY);

            // 在编辑器模式下设置属性会有额外的操作，比如很多组件的属性都会影响 Widget 的属性
            // 为了避免异常，先从节点树移除
            let temp:Node;
            let tempSiblingIndex:number;
            if (REAL_EDITOR){
                tempSiblingIndex = node.getSiblingIndex();
                temp = node.parent;
                node.parent = null;
            }
            Object.keys(COMP_ATTR_RECORD).forEach(compName=>{
                const comp = node.getComponent(`cc.${compName}`);
                if (comp){
                    const recordCompAttr = record[compName as keyof RecordProps];
                    if (recordCompAttr) {
                        switch(compName){
                            case "Label":
                                Object.keys(recordCompAttr).forEach(attr => {
                                    this.applyLabelAttr(attr, comp as Label, recordCompAttr);
                                });
                                break;
                            case "Sprite":
                                Object.keys(recordCompAttr).forEach(attr => {
                                    this.applySpriteAttr(attr, comp as Sprite, recordCompAttr);
                                });
                                break;
                            case "UIRenderer":
                                Object.keys(recordCompAttr).forEach(attr => {
                                    this.applyUIRendererAttr(attr, comp as UIRenderer, recordCompAttr);
                                });
                                break;
                            case "Widget":
                                Object.assign(comp, recordCompAttr);
                                break;
                            default:
                                Object.assign(comp, recordCompAttr);
                                break;
                        }
                    }
                }
            });
            if (REAL_EDITOR){
                temp.insertChild(node, tempSiblingIndex);
            }

            // 应用组件启用状态
            node.components.forEach((comp, index) => {
                const compName = (comp as any).__proto__.constructor.name as keyof RecordProps;
                const recordCompAttr = record[compName];
                // 没有记录且没在 COMP_ATTR_RECORD 中表明是在其他状态新增的组件,那么在当前状态就需要禁用
                if(!recordCompAttr && COMP_ATTR_RECORD[compName as KEY_OF_COMP_ATTR_RECORD]){
                    comp.enabled = false;
                }
            });
            const widget = node.getComponent(Widget);
            if (!widget || !widget.enabled)
                node.setPosition(record.x, record.y);
        }

        this._defaultNodeState.clear();
        this.walkNodeWithSubUIState(this.node, child => {
            this._defaultNodeState.set(child.uuid, this.recordNode(child));
        });
    }

    /**
     * 记录节点
     * @param node
     * @returns
     */
    private recordNode(node: Node, record?: RecordProps) {
        if (!record)
            record = {
                node,
                active: node.active,
                x: node.position.x,
                y: node.position.y,
                angle: node.angle,
                scaleX: node.scale.x,
                scaleY: node.scale.y
            };
        else{
            record.active = node.active;
            record.x = node.position.x;
            record.y = node.position.y;
            record.angle = node.angle;
            record.scaleX = node.scale.x;
            record.scaleY = node.scale.y;
        }
        
        // 记录组件启用状态
        node.components.forEach(comp => {
            const compName = (comp as any).__proto__.constructor.name as KEY_OF_COMP_ATTR_RECORD;
            if (COMP_ATTR_RECORD[compName]){
                const compAttrs = COMP_ATTR_RECORD[compName].slice();
                // 记录组件的 enabled 
                compAttrs.push("enabled" as never);
                const recordCompAttr:any = {};
                record![compName] = recordCompAttr;
                switch(compName){
                    case "Label":
                        compAttrs.forEach(attr => {
                            this.recordLabelAttr(attr, comp as Label, recordCompAttr);
                        });
                        break;
                    case "Sprite":
                        compAttrs.forEach(attr => {
                            this.recordSpriteAttr(attr, comp as Sprite, recordCompAttr);
                        });
                        break;
                    default:
                        compAttrs.forEach(attr => {
                            recordCompAttr[attr] = comp[attr as keyof typeof comp];
                        });
                        break;
                }
            }
        });

        return record;
    }

    private recordLabelAttr(attr:string, comp:Label, recordCompAttr:any){
        switch(attr){
            case "color":
                recordCompAttr[attr] = comp.color.toHEX();
                break;
            case "string":
                // 有多语言组件时不处理
                if (comp.getComponent("L10nLabel")) break;
            default:
                recordCompAttr[attr] = comp[attr as keyof typeof comp];
                break;
        }
    }

    private applyLabelAttr(attr:string, comp:Label, recordCompAttr:any){
        switch(attr){
            case "color":
                comp.color.fromHEX(recordCompAttr[attr]);
                (comp as any)["_updateColor"]();
                break;
            default:
                (comp as any)[attr] = recordCompAttr[attr];
                break;
        }
    }

    private recordSpriteAttr(attr:string, comp:Sprite, recordCompAttr:any){
        switch(attr){
            case "color":
                recordCompAttr[attr] = comp.color.toHEX();
                break;
            case "spriteFrame":
                recordCompAttr[attr] = comp.spriteFrame?.uuid;
                break;
            default:
                recordCompAttr[attr] = comp[attr as keyof typeof comp];
                break;
        }
    }

    private applySpriteAttr(attr:string, comp:Sprite, recordCompAttr:any){
        switch(attr){
            case "color":
                comp.color.fromHEX(recordCompAttr[attr]);
                (comp as any)["_updateColor"]();
                break;
            case "spriteFrame":
                if (comp.spriteFrame?.uuid === recordCompAttr[attr]) return;

                if (recordCompAttr[attr])
                    assetManager.loadAny<SpriteFrame>(recordCompAttr[attr], (err, asset) => {
                        if (err) {
                            console.warn(err);
                            return;
                        }
                        comp.spriteFrame = asset;

                        // 特定情况下会出现SpriteFrame没有更新，点击 Creator 能够刷新
                        // 使用软刷新场景的接口，编辑器会闪一下，体验不是太好，不过可以保证显示正确
                        REAL_EDITOR && Editor.Message.request("scene", "soft-reload");
                    });
                else comp.spriteFrame = null;
                break;
            default:
                (comp as any)[attr] = recordCompAttr[attr];
                break;
        }
    }

    private applyUIRendererAttr(attr:string, comp:UIRenderer, recordCompAttr:any){
        switch(attr){
            case "color":
                comp.color.fromHEX(recordCompAttr[attr]);
                (comp as any)["_updateColor"]();
                break;
            default:
                (comp as any)[attr] = recordCompAttr[attr];
                break;
        }
    }

    /** 
     * 保存状态
     * 新增的节点不需要处理
     * 修改的节点
     *      有记录：更新状态当前记录
     *      无记录：保存当前状态，并在其他状态上保存默认的状态
     */
    private recordBeforeStateChange(node: Node) {
        const defaultNodeState = this._defaultNodeState.get(node.uuid)!;

        // 新增的节点记录到 _defaultNodeState
        if (!defaultNodeState){
            this._defaultNodeState.set(node.uuid, this.recordNode(node));
            return;
        }

        let isModify = false;
        let record = this._uuidRecordMap?.get(node.uuid);
        // 清理已经删除的组件
        Object.keys(COMP_ATTR_RECORD).some(compName=>{
            if (node.getComponent(`cc.${compName}`)) return false;

            if (defaultNodeState[compName as keyof RecordProps]){
                isModify = true;

                // 如果没有记录就退出循环,因为已经知道了修改状态,而且也不需要更新记录
                if (!record) return true;

                delete record![compName as keyof RecordProps];
                return false;
            }else if (record){
                delete record![compName as keyof RecordProps];
                return false;
            }
            return false;
        });

        if (!isModify){
            if (defaultNodeState.active !== node.active || defaultNodeState.x!== node.position.x || 
                defaultNodeState.y!== node.position.y|| defaultNodeState.angle !== node.angle ||
                defaultNodeState.scaleX!== node.scale.x || defaultNodeState.scaleY!== node.scale.y)
                    isModify = true;
        }

        if (!isModify)
            // 检查节点是否有增加或修改
            isModify = node.components.some(component =>{
                const compName = (component as any).__proto__.constructor.name as keyof RecordProps;

                // 不在 COMP_ATTR_RECORD 里的组件不记录
                if (!COMP_ATTR_RECORD[compName as KEY_OF_COMP_ATTR_RECORD])
                    return false;

                // 新增的组件
                if (!defaultNodeState[compName])
                    return true;
                
                const compAttrRecord = defaultNodeState[compName]!;
                return Object.keys(compAttrRecord).some(key => {
                    switch(key){
                        case "color":
                            return (compAttrRecord as any)[key] !== ((component as any)[key] as Color).toHEX();
                        case "spriteFrame":
                            return (compAttrRecord as any)[key] !== ((component as any)[key] as SpriteFrame).uuid;
                        default:
                            if ((compAttrRecord as any)[key] !== (component as any)[key])
                                return true;
                            return false;
                    }
                })
            })

        if (isModify){
            if (record){
                this.recordNode(node, record);
            }else{
                Object.values(this.records).forEach((stateRecord, state) => {
                    if (this._state === state) {
                        record = this.recordNode(node);
                        this._uuidRecordMap?.set(node.uuid, record);
                    }else {
                        // 深度拷贝
                        record = this.cloneRecord(this._defaultNodeState.get(node.uuid)!);
                    }
                    stateRecord.push(record);
                });
            }
        }
    }
    
    /**
     * 遍历时包括拥有UIState的子节点
     * @param node 
     * @param func 
     */
    private walkNodeWithSubUIState(node: Node, func: (target: Node) => void) {
        let skipUuid = "";
        node.walk(
            child => {
                if (skipUuid) return;
                // if (child === node) return;
                if (child.getComponent(RichText)) {
                    skipUuid = child.uuid;
                }
                func(child);
            },
            (child: Node) => {
                if (skipUuid && skipUuid === child.uuid) {
                    skipUuid = "";
                }
            }
        );
    }

    private walkNode(node: Node, func: (target: Node) => void) {
        let skipUuid = "";
        node.walk(
            child => {
                if (skipUuid) return;
                // if (child === node) return;
                if (child.getComponent(RichText) || (child !== node && child.getComponent(UIState))) {
                    skipUuid = child.uuid;
                }
                func(child);
            },
            (child: Node) => {
                if (skipUuid && skipUuid === child.uuid) {
                    skipUuid = "";
                }
            }
        );
    }

    /**
     * 深拷贝记录
     * @param sourceRecord 要拷贝的记录
     * @returns 克隆的记录
     */
    private cloneRecord(sourceRecord:RecordProps){
        let clone = Object.assign({}, sourceRecord);
        (clone.node as any) = undefined;
        clone = JSON.parse(JSON.stringify(clone)) as RecordProps;
        clone.node = sourceRecord.node;
        return clone;
    }
}

// 场景编辑器左下角的自定义信息显示
const DIV_NAME = "UIStateElement";
const UIStateDecorator = function (ctr: Function) {
    let createUIStateElement = function () {
        var div = document.createElement("div");
        div.id = DIV_NAME;
        div.style.background = "#2b2b2b";
        div.style.position = "fixed";
        div.style.padding = "10px";
        div.style.color = "#cccccc";
        div.style.fontSize = "14px";
        div.style.left = "0px";
        div.style.bottom = "0px";
        div.style.zIndex = "99999";
        div.style.borderRadius = "calc(var(--size-normal-radius) * 2px)";
        div.style.boxShadow = "inset 0 0 0 calc(var(--size-normal-border) * 1px) var(--color-default-border-normal)";
        document.getElementById("GameDiv")!.append(div);
        return div;
    };

    let __oldOnFocusInEditor = ctr.prototype.onFocusInEditor;
    ctr.prototype.onFocusInEditor = function () {
        let targetElement = document.getElementById(DIV_NAME);
        if (!targetElement) targetElement = createUIStateElement();

        if (targetElement) {
            // 找到 UIState
            let uiState: UIState;
            let node = this.node;
            while (node) {
                uiState = node.getComponent(UIState);
                if (uiState) break;
                node = node.parent;
            }

            if (!node) return;

            Editor.Message.send("uistate-inspector", "record-uuid", uiState!.uuid);

            targetElement.innerHTML = `<span style="font-size:12px;color:#888">UIState</span> <br/> ${node.name}  <br/> state: ${
                uiState!.states[uiState!.state]
            } `;
        }
        __oldOnFocusInEditor?.apply(this, arguments);
    };

    let __oldOnLostFocusInEditor = ctr.prototype.onLostFocusInEditor;
    ctr.prototype.onLostFocusInEditor = function () {
        if (document.getElementById(DIV_NAME)) {
            document.getElementById(DIV_NAME)!.remove();
        }
        __oldOnLostFocusInEditor?.apply(this, arguments);
    };
};