utils_react-nexacro_reconciler.js

import ReactReconciler from "react-reconciler";
import { DefaultEventPriority } from "react-reconciler/constants";
import { generateUUIDv4 } from "../crypto/generate-uuidv4";

const globalObj =
    typeof globalThis !== "undefined"
        ? globalThis
        : typeof window !== "undefined"
          ? window
          : typeof global !== "undefined"
            ? global
            : this || new Function("return this")();

// setTimeout / clearTimeout 폴리필 주입 (넥사크로 런타임 환경 지원용)
if (typeof globalObj.setTimeout === "undefined") {
    var __timerIdSeq = 0;
    var __timers = {};

    globalObj.setTimeout = function (callback, delay) {
        var id = ++__timerIdSeq;

        __timers[id] = true;
        Promise.resolve().then(function () {
            if (__timers[id]) {
                delete __timers[id];
                callback();
            }
        });
        return id;
    };

    globalObj.clearTimeout = function (id) {
        delete __timers[id];
    };
}



function applyLayoutProps(comp, lp) {
    if (!comp || !lp) return;

    // 1. 값 설정 (유효한 값 우선 대입하여 가로/세로 2개 좌표 원칙을 만족시킴)
    if (lp.top !== undefined && lp.top !== null && typeof comp.set_top === "function") {
        comp.set_top(lp.top);
    }
    if (lp.bottom !== undefined && lp.bottom !== null && typeof comp.set_bottom === "function") {
        comp.set_bottom(lp.bottom);
    }
    if (lp.left !== undefined && lp.left !== null && typeof comp.set_left === "function") {
        comp.set_left(lp.left);
    }
    if (lp.right !== undefined && lp.right !== null && typeof comp.set_right === "function") {
        comp.set_right(lp.right);
    }

    // 2. 값 제거 (반대편 및 지정된 null 좌표 해제)
    if ((lp.top === null || lp.top === undefined) && lp.bottom !== undefined && lp.bottom !== null && typeof comp.set_top === "function") {
        comp.set_top(null);
    }
    if ((lp.bottom === null || lp.bottom === undefined) && lp.top !== undefined && lp.top !== null && typeof comp.set_bottom === "function") {
        comp.set_bottom(null);
    }
    if ((lp.left === null || lp.left === undefined) && lp.right !== undefined && lp.right !== null && typeof comp.set_left === "function") {
        comp.set_left(null);
    }
    if ((lp.right === null || lp.right === undefined) && lp.left !== undefined && lp.left !== null && typeof comp.set_right === "function") {
        comp.set_right(null);
    }
}

// 렌더링 틱(Commit) 종료 후 일괄적으로 스크롤을 초기화하기 위해 모아두는 큐
const pendingScrollForms = new Set();
// 일괄 렌더링(Commit) 중 리드로우를 중지할 폼들을 기억하는 큐
const pendingRedrawForms = new Set();
const pendingLayoutRestores = new Set();
const pendingLayoutRestoreComps = new Set();
const pendingOrderRestoreParents = new Set();

/**
 * @typedef {Object} NexacroInstanceResult
 * @property {nexacro.Component} comp  생성된 컴포넌트
 * @property {nexacro.Form}      form  자식을 추가할 form.
 */

/**
 * 넥사크로 컴포넌트 타입 → 생성 함수 매핑
 *
 * @param {string} type
 * 컴포넌트 타입 문자열
 * @param {Object} props
 * 프로퍼티
 * @param {nexacro.Form | nexacro.Div} parentContainer
 * 부모 컨테이너
 * @returns {NexacroInstanceResult}
 * 생성된 컴포넌트와 자식을 추가할 form.
 * @access private
 */
function createNexacroInstance(type, props, parentContainer) {
    const id = props.id ?? "v" + generateUUIDv4().replace(/-/g, "");
    const { left, top, width, height, right, bottom } = props;

    let comp;

    switch (type) {
        case "nx-div":
            comp = new nexacro.Div(id, left, top, width, height, right, bottom);
            break;
        case "nx-static":
            comp = new nexacro.Static(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-button":
            comp = new nexacro.Button(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-edit":
            comp = new nexacro.Edit(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-textarea":
            comp = new nexacro.TextArea(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-checkbox":
            comp = new nexacro.CheckBox(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-combo":
            comp = new nexacro.Combo(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-calendar":
            comp = new nexacro.Calendar(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-spin":
            comp = new nexacro.Spin(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-grid":
            comp = new nexacro.Grid(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-tab":
            comp = new nexacro.Tab(id, left, top, width, height, right, bottom);
            break;
        case "nx-image":
            comp = new nexacro.Image(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-progressbar":
            comp = new nexacro.ProgressBar(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-listbox":
            comp = new nexacro.ListBox(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        case "nx-panel":
            comp = new nexacro.Panel(
                id,
                left,
                top,
                width,
                height,
                right,
                bottom,
            );
            break;
        default:
            throw new Error(
                `[react-nexacro] 알 수 없는 컴포넌트 타입: "${type}"`,
            );
    }

    return comp;
}

/**
 * 넥사크로 컴포넌트에 props를 적용한다.
 * 이벤트 핸들러(on*)와 일반 프로퍼티를 구분하여 처리한다.
 *
 * @param {nexacro.Component} comp
 * @param {Object}            props
 * @access private
 */
function wrapWithBatchedUpdates(fn) {
    return function () {
        var args = arguments;
        var self = this;
        NexacroReconciler.batchedUpdates(function () {
            fn.apply(self, args);
        });
    };
}

function applyProps(comp, props) {
    const SKIP_KEYS = new Set(["id", "children"]);

    // 이벤트 핸들러 매핑 (React prop → 넥사크로 이벤트명)
    const EVENT_MAP = {
        onClick: "onclick",
        onDblClick: "ondblclick",
        onChanged: "onchanged",
        onCanChange: "canchange",
        onDropDown: "ondropdown",
        onCloseUp: "oncloseup",
        onItemChanged: "onitemchanged",
        onKeyDown: "onkeydown",
        onKeyUp: "onkeyup",
        onSetFocus: "onsetfocus",
        onKillFocus: "onkillfocus",
        onMouseOver: "onmouseover",
        onMouseOut: "onmouseout",
        onHScroll: "onhscroll",
        onVScroll: "onvscroll",
        onLoad: "onload",
        onMove: "onmove",
        onSize: "onsize",
    };

    // 래핑된 핸들러 저장소 초기화
    if (!comp._nxWrappedHandlers) comp._nxWrappedHandlers = {};
    if (!comp.__reactLayoutProps) comp.__reactLayoutProps = {};
    if (!comp.__reactLayoutProps) comp.__reactLayoutProps = {};

    Object.keys(props).forEach((key) => {
        if (SKIP_KEYS.has(key)) return;

        const val = props[key];

        if (EVENT_MAP[key]) {
            if (typeof val === "function") {
                const wrapped = wrapWithBatchedUpdates(val);
                comp._nxWrappedHandlers[key] = wrapped;
                comp.addEventHandler(EVENT_MAP[key], wrapped);
            }
            return;
        }

        

        // ref 처리
        if (key === "ref") {
            if (val && typeof val === "object" && "current" in val) {
                val.current = comp;
            }
            return;
        }

        // 일반 프로퍼티 적용
        try {
            const LAYOUT_KEYS = new Set(["top", "left", "right", "bottom"]);
            if (LAYOUT_KEYS.has(key)) {
                comp.__reactLayoutProps[key] = val;
                return;
            }
            if (key === "layouttype") {
                if (typeof comp.set_type === "function") comp.set_type(val);
                else comp["type"] = val;
            } else if (key === "_forceLayout") {
                // 무시
            } else {
                const setter = "set_" + key;
                if (typeof comp[setter] === "function") {
                    comp[setter](val);
                } else {
                    comp[key] = val;
                }
            }
        } catch (e) {
            // 읽기 전용 프로퍼티 등 무시
        }
    });

    applyLayoutProps(comp, comp.__reactLayoutProps);
}

/**
 * 이전 props와 새 props를 비교하여 변경된 항목만 업데이트한다.
 *
 * @param {nexacro.Component} comp
 * @param {Object}            oldProps
 * @param {Object}            newProps
 * @access private
 */
function updateProps(comp, oldProps, newProps) {
    const EVENT_MAP = {
        onClick: "onclick",
        onDblClick: "ondblclick",
        onChanged: "onchanged",
        onCanChange: "canchange",
        onDropDown: "ondropdown",
        onCloseUp: "oncloseup",
        onItemChanged: "onitemchanged",
        onKeyDown: "onkeydown",
        onKeyUp: "onkeyup",
        onSetFocus: "onsetfocus",
        onKillFocus: "onkillfocus",
        onMouseOver: "onmouseover",
        onMouseOut: "onmouseout",
        onHScroll: "onhscroll",
        onVScroll: "onvscroll",
        onLoad: "onload",
        onMove: "onmove",
        onSize: "onsize",
    };

    const SKIP_KEYS = new Set(["id", "children"]);

    // 래핑된 핸들러 저장소 초기화
    if (!comp._nxWrappedHandlers) comp._nxWrappedHandlers = {};
    if (!comp.__reactLayoutProps) comp.__reactLayoutProps = {};

    // 이전 이벤트 핸들러 제거 (저장된 래핑 함수로 정확히 제거)
    Object.keys(oldProps).forEach((key) => {
        if (SKIP_KEYS.has(key)) return;
        if (EVENT_MAP[key] && typeof oldProps[key] === "function") {
            try {
                const prevWrapped = comp._nxWrappedHandlers[key];
                if (prevWrapped) {
                    comp.removeEventHandler(EVENT_MAP[key], prevWrapped);
                    delete comp._nxWrappedHandlers[key];
                }
            } catch (e) {}
        }
    });

    // 새 props 적용
    Object.keys(newProps).forEach((key) => {
        if (SKIP_KEYS.has(key)) return;
        if (key === "ref") return;

        const val = newProps[key];
        
        const LAYOUT_KEYS = new Set(["top", "left", "right", "bottom"]);

        if (EVENT_MAP[key]) {
            if (typeof val === "function") {
                const wrapped = wrapWithBatchedUpdates(val);
                comp._nxWrappedHandlers[key] = wrapped;
                comp.addEventHandler(EVENT_MAP[key], wrapped);
            }
            return;
        }

        if (key === "_forceLayout") {
            if (oldProps[key] !== val) {
                pendingLayoutRestoreComps.add(comp);
            }
            return;
        }

        if (oldProps[key] !== val) {
            try {
                if (LAYOUT_KEYS.has(key)) {
                    comp.__reactLayoutProps[key] = val;
                    pendingLayoutRestoreComps.add(comp);
                    return;
                }
                if (key === "layouttype") {
                    if (typeof comp.set_type === "function") comp.set_type(val);
                    else comp["type"] = val;
                } else {
                    const setter = "set_" + key;
                    if (typeof comp[setter] === "function") {
                        comp[setter](val);
                    } else {
                        comp[key] = val;
                    }
                }
            } catch (e) {
            }
        }
    });

    applyLayoutProps(comp, comp.__reactLayoutProps);
}

/**
 * 컨테이너(Div 또는 Form)에서 자식을 추가할 수 있는 form을 반환한다.
 *
 * @param {nexacro.Div | nexacro.Form} container
 * @returns {nexacro.Form}
 * @access private
 */
function getForm(container) {
    if (container instanceof nexacro.Div) {
        return container.form;
    }
    if (container instanceof nexacro.Form) {
        return container;
    }
    // Tabpage 등
    if (container && container.form) {
        return container.form;
    }
    return container;
}

/**
 * 변경이 발생한 컴포넌트부터 최상위 폼까지, 그리고 하위 자손 폼들까지 모두 스크롤 초기화 대기열에 추가한다.
 *
 * @param {*} startNode  스크롤 갱신을 시작할 자식 컴포넌트 또는 부모 컨테이너
 * @access private
 */
function markScrollForAncestors(startNode) {
    // startNode 자체의 form (자신이 Div 등 컨테이너일 경우)
    if (startNode) {
        try {
            const selfForm = getForm(startNode);
            if (selfForm && typeof selfForm.resetScroll === "function") {
                pendingScrollForms.add(selfForm);
            }
        } catch(e) {}
    }

    // 상위(Ancestors) 탐색만 수행하여 O(1) ~ O(Depth) 수준으로 최적화 (하위 탐색 제거)
    let current = startNode;
    while (current) {
        const form = getForm(current.parent || current);
        if (form && typeof form.resetScroll === "function") {
            pendingScrollForms.add(form);
        }
        current = current.parent;
    }
}

/**
 * Panel 여부를 반환한다.
 *
 * @param {*} instance
 * @returns {boolean}
 * @access private
 */
function recordChildInitial(parent, child) {
    if (!parent) return;
    if (!parent._reactChildren) parent._reactChildren = [];
    const idx = parent._reactChildren.indexOf(child);
    if (idx === -1) {
        parent._reactChildren.push(child);
    }
}

function recordChildAppend(parent, child) {
    if (!parent) return;
    if (!parent._reactChildren) parent._reactChildren = [];
    const idx = parent._reactChildren.indexOf(child);
    if (idx > -1) {
        parent._reactChildren.splice(idx, 1);
    }
    parent._reactChildren.push(child);
    pendingOrderRestoreParents.add(parent);
}

function recordChildInsertBefore(parent, child, beforeChild) {
    if (!parent) return;
    if (!parent._reactChildren) parent._reactChildren = [];
    const idx = parent._reactChildren.indexOf(child);
    if (idx > -1) {
        parent._reactChildren.splice(idx, 1);
    }
    const beforeIdx = parent._reactChildren.indexOf(beforeChild);
    if (beforeIdx > -1) {
        parent._reactChildren.splice(beforeIdx, 0, child);
    } else {
        parent._reactChildren.push(child);
    }
    pendingOrderRestoreParents.add(parent);
}

function recordChildRemove(parent, child) {
    if (!parent || !parent._reactChildren) return;
    const idx = parent._reactChildren.indexOf(child);
    if (idx > -1) {
        parent._reactChildren.splice(idx, 1);
    }
    pendingOrderRestoreParents.add(parent);
}

function isPanel(instance) {
    return instance instanceof nexacro.Panel;
}

/**
 * 부모 컨테이너에 자식을 추가한다.
 * - Panel: 부모 form에 addChild 후 panel.addItem(id, id) 로 등록
 * - Div/Form: getForm(parent).addChild.
 *
 * @param {*} parentInstance  부모 넥사크로 컴포넌트 또는 Form.
 * @param {*} child           자식 넥사크로 컴포넌트
 * @access private
 */
function fixComponentLayoutChains(comp, form) {
    if (!form || !form.components || !comp) return;
    const targetPrefix = comp.id + ":";
    const layoutKeys = ["top", "left", "right", "bottom"];

    Object.values(form.components).forEach(sibling => {
        if (sibling === comp) return;
        layoutKeys.forEach(key => {
            try {
                const lpVal = sibling.__reactLayoutProps && sibling.__reactLayoutProps[key];
                const rawVal = sibling[key];
                const matchLp = typeof lpVal === 'string' && lpVal.startsWith(targetPrefix);
                const matchRaw = typeof rawVal === 'string' && rawVal.startsWith(targetPrefix);
                if (matchLp || matchRaw) {
                    const saved = matchLp ? lpVal : rawVal;
                    const setter = "set_" + key;
                    if (typeof sibling[setter] === "function") {
                        sibling[setter](null);
                        sibling[setter](saved);
                    }
                }
            } catch (e) {}
        });
    });
}

function relinkDependents(newComp, form) {
    if (!form || !form.components) return;
    try {
        fixComponentLayoutChains(newComp, form);
    } catch(e) {}
}

function flushPendingChildren(comp, parentForm) {
    if (isPanel(comp) && comp._pendingChildren) {
        const children = comp._pendingChildren;
        comp._pendingChildren = null;
        children.forEach((child) => {
            if (parentForm && typeof parentForm.addChild === "function") {
                try {
                    parentForm.addChild(child.id, child);
                    child.show?.();
                    if (child.__reactLayoutProps) {
                        pendingLayoutRestoreComps.add(child);
                    }
                    comp.addItem(child.id, child.id);
                    markScrollForAncestors(comp);
                    relinkDependents(child, parentForm);
                } catch (e) {
                    console.error(
                        "[react-nexacro] flushPendingChildren error:",
                        e,
                    );
                }
            }
            flushPendingChildren(child, parentForm);
        });
    }
}

function addChildToParent(parentInstance, child) {
    if (isPanel(parentInstance)) {
        if (!parentInstance.parent) {
            // 아직 Panel이 부모 Form에 추가되지 않았으므로 자식 추가를 보류한다.
            if (!parentInstance._pendingChildren)
                parentInstance._pendingChildren = [];
            parentInstance._pendingChildren.push(child);
        } else {
            // 이미 부모 Form에 추가된 상태라면 바로 추가
            const parentForm = getForm(parentInstance.parent);
            if (parentForm && typeof parentForm.addChild === "function") {
                try {
                    const existing = parentForm.components[child.id];
                    let shouldAdd = true;
                    if (existing) {
                        if (existing === child) {
                            shouldAdd = false;
                        } else {
                            try {
                                console.warn(`[Reconciler] COLLISION! Destroying old ${child.id} before adding new one.`);
                                parentForm.removeChild(child.id);
                                existing.destroy?.();
                                pendingLayoutRestores.add(parentForm);
                            } catch (ex) {}
                        }
                    }

                    if (shouldAdd) {
                        parentForm.addChild(child.id, child);
                        child.show?.();
                        if (child.__reactLayoutProps) {
                            pendingLayoutRestoreComps.add(child);
                        }
                    }
                    parentInstance.addItem(child.id, child.id);
                    markScrollForAncestors(parentInstance);
                    flushPendingChildren(child, parentForm);
                    relinkDependents(child, parentForm);
                } catch (e) {
                    console.error(
                        "[react-nexacro] Error adding child to Panel's parent:",
                        e,
                    );
                }
            }
        }
    } else {
        const form = getForm(parentInstance);
        if (form && typeof form.addChild === "function") {
            try {
                const existing = form.components[child.id];
                let shouldAdd = true;
                if (existing) {
                    if (existing === child) {
                        shouldAdd = false;
                    } else {
                        try {
                            console.warn(`[Reconciler] COLLISION! Destroying old ${child.id} before adding new one.`);
                            form.removeChild(child.id);
                            existing.destroy?.();
                            pendingLayoutRestores.add(form);
                        } catch (ex) {}
                    }
                }

                if (shouldAdd) {
                    form.addChild(child.id, child);
                    child.show?.();
                    // 새 컴포넌트도 200ms 후 setTimeout에서 레이아웃(top/left 등)을 재평가받도록 큐에 추가
                    if (child.__reactLayoutProps) {
                        pendingLayoutRestoreComps.add(child);
                    }
                }
                markScrollForAncestors(parentInstance);
                flushPendingChildren(child, form);
                relinkDependents(child, form);
            } catch (e) {
                console.error(
                    "[react-nexacro] Error adding child to normal container:",
                    e,
                );
            }
        }
    }
}

/**
 * 부모 컨테이너에서 자식을 제거한다.
 * - Panel: panel.removeItem 후 부모 form에서 removeChild - Div/Form:
 * getForm(parent).removeChild.
 *
 * @param {*} parentInstance  부모 넥사크로 컴포넌트 또는 Form.
 * @param {*} child           자식 넥사크로 컴포넌트
 * @access private
 */
function removeChildFromParent(parentInstance, child) {
    if (isPanel(parentInstance)) {
        if (parentInstance._pendingChildren) {
            const idx = parentInstance._pendingChildren.indexOf(child);
            if (idx > -1) {
                parentInstance._pendingChildren.splice(idx, 1);
                child.destroy?.();
                return;
            }
        }
        try {
            parentInstance.removeItem(child.id);
        } catch (e) {}
        const parentForm = getForm(parentInstance.parent);
        if (parentForm && typeof parentForm.removeChild === "function") {
            try {
                parentForm.removeChild(child.id);
                markScrollForAncestors(parentInstance);
            } catch (e) {}
        }
    } else {
        const form = getForm(parentInstance);
        if (form && typeof form.removeChild === "function") {
            try {
                form.removeChild(child.id);
                markScrollForAncestors(parentInstance);
            } catch (e) {}
        }
    }
    child.destroy?.();
}

// ─── react-reconciler 호스트 설정 ────────────────────────────────────────────

const hostConfig = {
    // 지원 기능 플래그
    supportsMutation: true,
    supportsPersistence: false,
    supportsHydration: false,
    isPrimaryRenderer: true,
    noTimeout: -1,

    // 현재 시간
    now: Date.now,

    // 스케줄링
    scheduleTimeout: function (...args) {
        return globalObj.setTimeout(...args);
    },
    cancelTimeout: function (...args) {
        return globalObj.clearTimeout(...args);
    },

    // 루트 컨테이너 준비
    prepareForCommit(containerInfo) {
        try {
            const form = getForm(containerInfo);
            if (form && typeof form.set_enableredraw === "function") {
                form.set_enableredraw(false);
                pendingRedrawForms.add(form);
            }
        } catch (e) {}
        return null;
    },
    resetAfterCommit(containerInfo) {
        // 1. Z-Order/Child list order 복원 및 레이아웃 복구 작업을 "동기적"으로 먼저 수행한다.
        const parentsToReorder = Array.from(pendingOrderRestoreParents);
        pendingOrderRestoreParents.clear();

        const compsToRestore = Array.from(pendingLayoutRestoreComps);
        pendingLayoutRestoreComps.clear();

        if (compsToRestore.length > 0 || parentsToReorder.length > 0) {
            
            // Z-Order/Child list order 복원
            parentsToReorder.forEach(parent => {
                try {
                    if (parent && parent._reactChildren && parent._reactChildren.length > 0) {
                        if (isPanel(parent)) {
                            parent._reactChildren.forEach(child => {
                                if (child && typeof child.bringToFront === "function") {
                                    child.bringToFront();
                                }
                            });
                        } else {
                            const form = getForm(parent);
                            if (form && form.components) {
                                
                                // 리드로우 일시 중지
                                const hasRedraw = typeof form.set_enableredraw === "function";
                                if (hasRedraw) {
                                    form.set_enableredraw(false);
                                }

                                // 모든 자식을 form에서 일시 분리
                                parent._reactChildren.forEach(child => {
                                    try {
                                        if (child && form.components[child.id]) {
                                            form.removeChild(child.id);
                                        }
                                    } catch (e) {}
                                });

                                // 올바른 순서대로 다시 추가하여 인덱스 정렬
                                parent._reactChildren.forEach(child => {
                                    try {
                                        if (child) {
                                            form.addChild(child.id, child);
                                            child.show?.();
                                        }
                                    } catch (e) {}
                                });

                                // 리드로우 재개
                                if (hasRedraw) {
                                    form.set_enableredraw(true);
                                }
                            }
                        }
                    }
                } catch (e) {
                }
            });

            // 레이아웃 복구 및 체인 재연결
            const formsToReset = new Set();
            compsToRestore.forEach((comp) => {
                try {
                    if (comp && comp.__reactLayoutProps && Object.keys(comp.__reactLayoutProps).length > 0) {
                        const lp = comp.__reactLayoutProps;
                        const form = getForm(comp.parent);

                        applyLayoutProps(comp, lp);

                        if (form) {
                            formsToReset.add(form);
                            // 이 컴포넌트를 바라보는 sibling들의 체인 강제 재연결 (모든 방향)
                            fixComponentLayoutChains(comp, form);
                        }
                    }
                } catch (e) {
                }
            });

            // restore 및 chain fix가 완료된 후, 해당 form들의 resetScroll을 호출하여 fittocontents 갱신
            formsToReset.forEach(form => {
                try {
                    if (typeof form.resetScroll === "function") {
                        form.resetScroll();
                    }
                } catch(e) {}
            });

        } else {
        }

        // 손상된 레이아웃 복원 (일반 패스 백업용)
        pendingLayoutRestores.forEach((form) => {
            if (form.components) {
                Object.values(form.components).forEach(comp => {
                    try {
                        if (comp && comp.__reactLayoutProps) {
                            const lp = comp.__reactLayoutProps;
                            
                            if (lp.top !== undefined && typeof comp.set_top === "function") {
                                comp.set_top(null);
                                comp.set_top(lp.top);
                            } else if (lp.bottom !== undefined && typeof comp.set_top === "function") {
                                comp.set_top(null);
                            }

                            if (lp.bottom !== undefined && typeof comp.set_bottom === "function") {
                                comp.set_bottom(null);
                                comp.set_bottom(lp.bottom);
                            } else if (lp.top !== undefined && typeof comp.set_bottom === "function") {
                                comp.set_bottom(null);
                            }

                            if (lp.left !== undefined && typeof comp.set_left === "function") {
                                comp.set_left(null);
                                comp.set_left(lp.left);
                            } else if (lp.right !== undefined && typeof comp.set_left === "function") {
                                comp.set_left(null);
                            }

                            if (lp.right !== undefined && typeof comp.set_right === "function") {
                                comp.set_right(null);
                                comp.set_right(lp.right);
                            } else if (lp.left !== undefined && typeof comp.set_right === "function") {
                                comp.set_right(null);
                            }
                        }
                    } catch (e) {}
                });
            }
        });
        pendingLayoutRestores.clear();

        // 일괄 스크롤 초기화
        pendingScrollForms.forEach((form) => {
            try {
                if (typeof form.resetScroll === "function") {
                    form.resetScroll();
                }
            } catch (e) {}
        });
        pendingScrollForms.clear();

        // 2. 최종적으로 리드로우 재개
        try {
            const form = getForm(containerInfo);
            if (form && typeof form.set_enableredraw === "function") {
                form.set_enableredraw(true);
                pendingRedrawForms.delete(form);
            }
        } catch (e) {}

        pendingRedrawForms.forEach((form) => {
            try {
                if (typeof form.set_enableredraw === "function") {
                    form.set_enableredraw(true);
                }
            } catch (e) {}
        });
        pendingRedrawForms.clear();
    },

    preparePortalMount() {},

    // 이벤트 우선순위
    getCurrentEventPriority() {
        return DefaultEventPriority;
    },

    // React 19 / react-reconciler 0.33 필수 메서드
    setCurrentUpdatePriority(priority) {},
    getCurrentUpdatePriority() {
        return DefaultEventPriority;
    },
    resolveUpdatePriority() {
        return DefaultEventPriority;
    },
    trackSchedulerEvent() {},
    resolveEventType() {
        return null;
    },
    resolveEventTimeStamp() {
        return -1;
    },
    shouldAttemptEagerTransition() {
        return false;
    },
    maySuspendCommit() {
        return false;
    },
    maySuspendCommitOnUpdate() {
        return false;
    },
    maySuspendCommitInSyncRender() {
        return false;
    },
    preloadInstance() {
        return true;
    },
    startSuspendingCommit() {},
    suspendInstance() {},
    waitForCommitToBeReady() {
        return null;
    },
    getSuspendedCommitReason() {
        return null;
    },
    resetFormInstance() {},
    bindToConsole(methodName, args, isConcurrent) {
        return console[methodName].bind(console, ...args);
    },
    supportsMicrotasks: true,
    scheduleMicrotask: (fn) =>
        Promise.resolve()
            .then(fn)
            .catch((e) =>
                globalObj.setTimeout(() => {
                    throw e;
                }),
            ),

    getInstanceFromNode() {
        return null;
    },
    beforeActiveInstanceBlur() {},
    afterActiveInstanceBlur() {},
    prepareScopeUpdate() {},
    getInstanceFromScope() {
        return null;
    },
    detachDeletedInstance() {},

    // 루트 컨테이너 생성
    createContainer(containerInfo) {
        return containerInfo;
    },

    // 컴포넌트 인스턴스 생성
    createInstance(type, props, rootContainer) {
        const comp = createNexacroInstance(type, props, rootContainer);
        applyProps(comp, props);
        return comp;
    },

    // 텍스트 인스턴스 (넥사크로는 텍스트 노드 미지원 → 무시)
    createTextInstance(text) {
        return { __isTextNode: true, text };
    },

    // 자식 추가 (마운트 전 준비 단계)
    appendInitialChild(parentInstance, child) {
        if (child && child.__isTextNode) return;
        recordChildInitial(parentInstance, child);
        addChildToParent(parentInstance, child);
    },

    // 인스턴스 최종화 (자식 추가 완료 후)
    finalizeInitialChildren() {
        return false;
    },

    // 업데이트 준비 (변경 사항 계산)
    prepareUpdate(instance, type, oldProps, newProps) {
        return newProps;
    },

    // 자식 여부
    shouldSetTextContent() {
        return false;
    },

    // 루트 컨테이너 반환
    getRootHostContext() {
        return {};
    },

    // 자식 컨텍스트
    getChildHostContext(parentHostContext) {
        return parentHostContext;
    },

    // 공개 인스턴스 반환 (ref에 노출될 값)
    getPublicInstance(instance) {
        return instance;
    },

    // ── Mutation 메서드 ──────────────────────────────────────────────────────

    // 컨테이너에 자식 추가 (최초 마운트)
    appendChildToContainer(container, child) {
        if (child && child.__isTextNode) return;
        recordChildAppend(container, child);
        // 루트 컨테이너(Div/Form)는 Panel이 아니므로 일반 경로
        const form = getForm(container);
        if (form && typeof form.addChild === "function") {
            form.addChild(child.id, child);
            child.show?.();
            if (child.__reactLayoutProps) {
                pendingLayoutRestoreComps.add(child);
            }
            markScrollForAncestors(container);
            flushPendingChildren(child, form);
            // 새 컴포넌트가 추가된 후, 이 컴포넌트를 top/left로 참조하는 기존 컴포넌트들을 재연결
            relinkDependents(child, form);
        }
    },

    // 부모에 자식 추가
    appendChild(parentInstance, child) {
        if (child && child.__isTextNode) return;
        recordChildAppend(parentInstance, child);
        addChildToParent(parentInstance, child);
    },

    // 특정 위치 앞에 자식 삽입
    insertBefore(parentInstance, child, beforeChild) {
        if (child && child.__isTextNode) return;
        recordChildInsertBefore(parentInstance, child, beforeChild);
        addChildToParent(parentInstance, child);
    },

    // 컨테이너에 특정 위치 앞에 자식 삽입
    insertInContainerBefore(container, child, beforeChild) {
        if (child && child.__isTextNode) return;
        recordChildInsertBefore(container, child, beforeChild);
        const form = getForm(container);
        if (form && typeof form.addChild === "function") {
            form.addChild(child.id, child);
            child.show?.();
            if (child.__reactLayoutProps) {
                pendingLayoutRestoreComps.add(child);
            }
            markScrollForAncestors(container);
            flushPendingChildren(child, form);
        }
    },

    // 자식 제거
    removeChild(parentInstance, child) {
        if (child && child.__isTextNode) return;
        recordChildRemove(parentInstance, child);
        removeChildFromParent(parentInstance, child);
    },

    // 컨테이너에서 자식 제거
    removeChildFromContainer(container, child) {
        if (child && child.__isTextNode) return;
        recordChildRemove(container, child);
        const form = getForm(container);
        if (form && typeof form.removeChild === "function") {
            try {
                form.removeChild(child.id);
            } catch (e) {}
        }
        child.destroy?.();
    },

    // props 업데이트 커밋
    // react-reconciler 0.33: (instance, type, oldProps, newProps) — updatePayload 파라미터 제거됨
    commitUpdate(instance, type, oldProps, newProps) {
        updateProps(instance, oldProps, newProps);
        markScrollForAncestors(instance);
    },

    // 텍스트 업데이트 (무시)
    commitTextUpdate() {},

    // 마운트 후 효과 (commitMount가 필요한 경우)
    commitMount() {},

    // 숨기기/보이기 (Suspense 지원)
    hideInstance(instance) {
        try {
            instance.visible = false;
        } catch (e) {}
    },
    hideTextInstance() {},
    unhideInstance(instance) {
        try {
            instance.visible = true;
        } catch (e) {}
    },
    unhideTextInstance() {},

    // 컨테이너 초기화
    clearContainer(container) {
        const form = getForm(container);
        if (form && form.all) {
            Array.from(form.all).forEach((child) => {
                try {
                    form.removeChild(child.id);
                } catch (e) {}
                try {
                    child.destroy();
                } catch (e) {}
            });
        }
    },
};

export const NexacroReconciler = ReactReconciler(hostConfig);