
window.AScript = new Map();
window.isTouchAvailable = 'ontouchstart' in window ||
    window.DocumentTouch && document instanceof window.DocumentTouch ||
    navigator.maxTouchPoints > 0 ||
    window.navigator.msMaxTouchPoints > 0
window.getPrimaryPointerEvent = function () {
    const isIOS = /iP(ad|hone|od)/.test(navigator.userAgent);
    const supportsPointer = !!window.PointerEvent;

    if (supportsPointer && !isIOS) {
        return "pointerdown"; // chuẩn nhất
    }

    if (
        'ontouchstart' in window ||
        navigator.maxTouchPoints > 0 ||
        window.navigator.msMaxTouchPoints > 0
    ) {
        return "touchstart"; // fallback tốt nhất cho iOS hoặc thiết bị cũ
    }

    return "mousedown"; // fallback cho desktop không cảm ứng
};

window.isValidPointerClick = function (ev) {
    return ev.pointerType === 'mouse' && ev.button !== 0;
}

if (Node.prototype.appendChildren === undefined) {
    Node.prototype.appendChildren = function () {
        let children = [...arguments];

        if (
            children.length == 1 &&
            Object.prototype.toString.call(children[0]) === "[object Array]"
        ) {
            children = children[0];
        }
        var documentFragment = document.createDocumentFragment();
        children.forEach(c => documentFragment.appendChild(c));
        this.appendChild(documentFragment);
    };
}
if (Node.prototype.removeAll === undefined) {
    Node.prototype.removeAll = function () {
        while (this.firstChild) this.removeChild(this.lastChild);
    };
}
if (NodeList.prototype.removeAll === undefined) {
    NodeList.prototype.removeAll = function () {
        for (var i = this.length - 1; i >= 0; i--) {
            this[i].remove();
        }
    };
}

if (Array.prototype.hasItem === undefined) {
    Array.prototype.hasItem = function (o, callback) {
        var f = false;
        this.forEach(e => {
            if (callback(e, o)) {
                f = true;
                return;
            }
        });
        return f;
    }
}

if (Array.prototype.removeItem === undefined) {
    Array.prototype.removeItem = function (o, callback) {
        var f = false;
        this.forEach((e, i) => {
            if (callback(e, o)) {
                delete this[i];
                this.splice(i, 1);
            }
        });
        return f;
    }
}

window.getOS = function () {
    var userAgent = window.navigator.userAgent,
        platform = window.navigator.platform,
        macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'],
        windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'],
        iosPlatforms = ['iPhone', 'iPad', 'iPod'],
        os = null;

    if (macosPlatforms.indexOf(platform) !== -1) {
        os = 'Mac OS';
    } else if (iosPlatforms.indexOf(platform) !== -1) {
        os = 'iOS';
    } else if (windowsPlatforms.indexOf(platform) !== -1) {
        os = 'Windows';
    } else if (/Android/.test(userAgent)) {
        os = 'Android';
    } else if (!os && /Linux/.test(platform)) {
        os = 'Linux';
    }

    return os;
}

window.GetAbsoluteURL = function(relativeURL) {
    if (relativeURL.startsWith(window.location.origin)) {
        return relativeURL;
    }
    return window.location.origin + relativeURL;
}

window.getPathFromUrl = function(url) {
    const parsed = new URL(url);
    return parsed.pathname + parsed.search + parsed.hash;
}

window.GetEventType = function () {
    if (isTouchAvailable) {
        return "touchend";
    } else {
        return "click";
    }
}
window.fireEvent = function (element, event) {
    if (document.createEventObject) {
        // dispatch for IE
        var evt = document.createEventObject();
        return element.fireEvent('on' + event, evt)
    }
    else {
        // dispatch for firefox + others
        var evt = document.createEvent("HTMLEvents");
        evt.initEvent(event, true, true); // event type,bubbling,cancelable
        return !element.dispatchEvent(evt);
    }
}
window.requestTimeout = function (fn, delay, registerCancel = () => { }) {
    const start = new Date().getTime();

    const loop = () => {
        const delta = new Date().getTime() - start;

        if (delta >= delay) {
            fn();
            registerCancel(function () { });
            return;
        }

        const raf = requestAnimationFrame(loop);
        registerCancel(() => cancelAnimationFrame(raf));
    };

    const raf = requestAnimationFrame(loop);
    registerCancel(() => cancelAnimationFrame(raf));
};



window.formatDateToString = function (date) {
    var dd = (date.getDay() < 10 ? '0' : '') + date.getDay();
    var MM = ((date.getMonth() + 1) < 10 ? '0' : '') + (date.getMonth() + 1);
    var yyyy = date.getFullYear();
    var hh = (date.getHours() < 10 ? '0' : '') + date.getHours();
    var mm = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
    var ss = (date.getSeconds() < 10 ? '0' : '') + date.getSeconds();
    return (dd + "-" + MM + "-" + yyyy + " " + hh + ":" + mm + ":" + ss);
}
window.padLeadingZeros = function (num, size) {
    var s = num + "";
    while (s.length < size) s = "0" + s;
    return s;
}
window.AddFormData = function (frm, name, val) {
    if (frm.has(name)) {
        frm.set(name, val); v
    } else {
        frm.append(name, val);
    }
}

window.checkViewHeight = function () {
    let vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty('--vh', `${vh}px`);
}
window.checkViewHeight();
window.addEventListener('resize', () => {
    window.checkViewHeight();
});
window.LongIdGenerator = class {
    constructor(start = 1n) {
        this.counter = BigInt(start);
    }

    next() {
        return this.counter++;
    }

    reset(start = 1n) {
        this.counter = BigInt(start);
    }
}
window.AObject = class {
    constructor() {
        this.listeners = new Map(); // label -> Map<id, callback>
        this.onceListeners = new Map();
        this.triggerdLabels = new Map();
        this.listenersById = new Map(); // id -> { label, callback }
        this.systemEvents = new Map();
        this.customEvents = new Map();
        this.parentEventMap = new Map();
        this.listAObject = new Set();
        this.eventName = window.getPrimaryPointerEvent();
        this.isScrolling = false;
    }

    _fCheckPast(label, callback) {
        if (this.triggerdLabels.has(label)) {
            callback(this.triggerdLabels.get(label));
            return true;
        } else {
            return false;
        }
    }

    on(label, callback, checkPast = false) {
        const id = crypto.randomUUID();
        const cb = (...args) => callback(...args);

        if (!this.listeners.has(label)) {
            this.listeners.set(label, new Map());
        }
        this.listeners.get(label).set(id, cb);
        this.listenersById.set(id, { label, callback: cb });

        if (checkPast) this._fCheckPast(label, callback);

        return id;
    }

    onReady(label, callback) {
        return this.on(label, callback, true);
    }

    once(label, callback, checkPast = false) {
        const id = crypto.randomUUID();
        const cb = (...args) => callback(...args);

        if (checkPast && this._fCheckPast(label, callback)) return;

        if (!this.onceListeners.has(label)) {
            this.onceListeners.set(label, new Map());
        }
        this.onceListeners.get(label).set(id, cb);
        this.listenersById.set(id, { label, callback: cb });

        return id;
    }

    onceReady(label, callback) {
        return this.once(label, callback, true);
    }

    off(label, callback = true) {
        if (callback === true) {
            this.listeners.delete(label);
            this.onceListeners.delete(label);
        } else {
            const removeMatching = (map) => {
                const innerMap = map.get(label);
                if (innerMap) {
                    for (let [id, cb] of innerMap.entries()) {
                        if (cb === callback) {
                            innerMap.delete(id);
                            this.listenersById.delete(id);
                        }
                    }
                    if (innerMap.size === 0) map.delete(label);
                }
            };
            removeMatching(this.listeners);
            removeMatching(this.onceListeners);
        }
    }

    offById(id) {
        const info = this.listenersById.get(id);
        if (info) {
            const { label, callback } = info;
            const removeFrom = (map) => {
                const innerMap = map.get(label);
                if (innerMap && innerMap.has(id)) {
                    innerMap.delete(id);
                    if (innerMap.size === 0) map.delete(label);
                }
            };
            removeFrom(this.listeners);
            removeFrom(this.onceListeners);
            this.listenersById.delete(id);
        }
    }

    trigger(label, ...args) {
        let res = false;
        this.triggerdLabels.set(label, ...args);

        const triggerFrom = (map) => {
            const innerMap = map.get(label);
            if (innerMap) {
                for (let cb of innerMap.values()) {
                    cb(...args);
                    res = true;
                }
            }
        };

        triggerFrom(this.onceListeners);
        triggerFrom(this.listeners);
        this.onceListeners.delete(label);

        return res;
    }

    dispose() {
        for (const [element, eventMap] of this.systemEvents.entries()) {
            for (const [event, { callback }] of eventMap.entries()) {
                element.removeEventListener(event, callback, false);
            }
        }
        for (const [id, obj] of this.customEvents.entries()) {
            obj.ele.offById(id);
        }
        for (const id of this.listAObject) {
            id.dispose();
        }
        this.listAObject.clear();
        this.systemEvents.clear();
        this.parentEventMap.clear();

        // Hủy các custom event listeners
        this.listeners.clear();
        this.onceListeners.clear();
        this.listenersById.clear();
        this.triggerdLabels.clear();
    }
    removeSystemEvent(element) {
        const eventMap = this.systemEvents.get(element);
        if (!eventMap) return;

        for (const [event, { callback, parent }] of eventMap.entries()) {
            element.removeEventListener(event, callback, false);

            // Nếu có parent, thì xóa tham chiếu khỏi parentEventMap
            if (parent && this.parentEventMap.has(parent)) {
                const entrySet = this.parentEventMap.get(parent);
                for (let item of entrySet) {
                    if (item.element === element && item.event === event) {
                        entrySet.delete(item);
                        break;
                    }
                }
                if (entrySet.size === 0) {
                    this.parentEventMap.delete(parent);
                }
            }
        }

        this.systemEvents.delete(element);
    }

    removeSytemEventParent(parent) {
        const entries = this.parentEventMap.get(parent);
        if (!entries) return;
        let iC = 0;
        for (const { element, eventName } of entries) {
            const eventMap = this.systemEvents.get(element);
            if (eventMap) {
                const data = eventMap.get(eventName);
           
                if (data && typeof data.callback === 'function') {
                    element.removeEventListener(eventName   , data.callback, false);
                    eventMap.delete(eventName);
                    // Nếu element không còn event nào, xóa khỏi systemEvents
                    if (eventMap.size === 0) {
                        this.systemEvents.delete(element);
                    }
                }
            }
            
        }
        this.parentEventMap.delete(parent);
    }
    removeCustomEventParent(parent) {
        const entries = this.parentEventMap.get("Custom" + parent);
        if (!entries) return;
        for (const { id, element } of entries) {
            const eventMap = this.customEvents.get(id);
            if (eventMap) {
                eventMap.ele.offById(eventMap.id);
                this.customEvents.delete(id);
            }

        }
        this.parentEventMap.delete(parent);
    }
    addCustomEvent(id, element, groups = null) {
        if (!this.customEvents.has(id)) {
            this.customEvents.set(id, { "ele":element, "groups":groups });
        }
        if (groups) {
            if (!this.parentEventMap.has("Custom" + parent)) {
                this.parentEventMap.set("Custom" + parent, new Map());
            }
            this.parentEventMap.get("Custom" + parent).set(id, element);
        }

    }
    addSystemEvent(eventName, element, callback, parent = null, capture = false) {
        if (!this.systemEvents.has(element)) {
            this.systemEvents.set(element, new Map());
        }
        const eventMap = this.systemEvents.get(element);
        eventMap.set(eventName, { callback, parent });
        element.addEventListener(eventName, callback, capture);
        if (parent) {
            if (!this.parentEventMap.has(parent)) {
                this.parentEventMap.set(parent, new Set());
            }
            this.parentEventMap.get(parent).add({ element, eventName });
        }

    }

    cloneNodes(source, destination) {
        const children = source.childNodes;
        for (let i = 0; i < children.length; i++) {
            destination.appendChild(children[i].cloneNode(true));
        }
    }
};
class LoadScriptAsync extends window.AObject {
    constructor(type) {
        super();
        this.id = 0;
        this.type = type;
        this.listCurrent = new Map();
        this.stackScript = [];
        this.jsLib = document.querySelector("section[app-js-lib]");
        this.jsPage = document.querySelector("section[app-js-page]");
        this.count = 0;
    }
    getScripts() {
        document.querySelectorAll("section [js-lib]").forEach(el => {

            if (!el.hasAttribute("checked")) {
                this.listCurrent.set(el.src.split(/(\\|\/)/g).pop(), { "el": el.src, "status": "0" });
                el.setAttribute("checked", "");
            }
        });
    }
    processScript(doc) {
        if (typeof doc.childNodes === "undefined") {
            return;
        }
        this.stackScript = [];
        this.listCurrent = new Map();
        this.getScripts();
        for (var i = 0; i < doc.childNodes.length; i++) {
            var n = doc.childNodes[i];
            if (n.getAttribute("js-page") != null) {
                this.stackScript.push({ "el": n });
            }
            if (n.getAttribute("js-lib") != null) {
                if (this.checkExist(n)) {
                    continue;
                }
                var src = n.getAttribute("src");
                this.jsLib.appendChild(this.createScriptTag(n));
                this.listCurrent.set(src.split(/(\\|\/)/g).pop(), { "el": src, "status": "0" });
            }
        }

        window.requestTimeout(this.checkLoaded.bind(this), 10, window.registerCancel);
    }
    checkLoaded() {
        this.listCurrent.forEach((v, k, m) => {
            var tn = k.substring(0, k.length - 3);
            if (window.AScript.has(tn)) {
                this.listCurrent.delete(k);
            }
        });
        if (this.listCurrent.size == 0) {
            this.addJsPage();
            this.trigger("Loaded", null);
        } else {
            window.requestTimeout(this.checkLoaded.bind(this), 10, window.registerCancel);
        }
    }
    checkExist(elm) {
        if (this.listCurrent.has(elm.src.split(/(\\|\/)/g).pop())) {
            return true;
        } else {
            return false;
        }
    }
    createScriptTag(el) {
        var newScript = document.createElement("script");
        newScript.setAttribute("async", "");
        for (var i = 0; i < el.attributes.length; i++) {
            newScript.setAttribute(el.attributes[i].name, el.attributes[i].value);
        }
        if (!el.hasChildNodes()) {
            newScript.src = el.src;
        } else {
            var tmp = document.createTextNode(el.innerHTML);
            newScript.appendChild(tmp);
        }
        return newScript;
    }
    addJsPage() {
        this.jsPage.innerHTML = "";
        if (window.Destroy != undefined) window.Destroy();
        this.stackScript.forEach(el => {
            this.jsPage.appendChild(this.createScriptTag(el.el));

        });
    }
}
class AApp extends window.AObject {
    constructor(container = '[app-content]') {
        super();
        this.cachePage = new CacheManager();
        this.isLoadedLayout = false;
        window.Destroy = undefined;
        window.ALayout = new Map();
        this.isRedirectPage = false;
        this.metaPage = document.head.querySelector("meta[name=idPage]");
        this.pageName = this.metaPage.getAttribute("pageName");
        this.lName = this.metaPage.getAttribute("layName");
        var tmp = document.querySelector(container);
        this.mainApp = tmp.querySelector("[main-content]") ? tmp.querySelector("[main-content]") : tmp;
        var f = function (ev) {
            if (ev.state) {
                    this.cachePage.get(ev.state.url, ((result) => {
                    if (result == null) {

                    } else {
                        this.loadContentPage(result.html);
                        this.metaPage.content = result.idPage;
                        this.metaPage.setAttribute("isLayout", result.isLayout);
                        this.trigger("redirect_page", ev.state);
                        this.checkLayout(result.lName);
                        var l = new LoadScriptAsync("Page");
                        l.processScript(result.doc);
                        l.on("Loaded", (() => {
                            this.loadedPage();
                        }).bind(this));
                    }

                }).bind(this));
            }
        }.bind(this);
        window.addEventListener("popstate", f);
    }
    checkLayout(lName) {
        if (this.lName !== lName) {
            if (this.lName !== "None") {
                const l = window.ALayout.get(this.lName);
                l.dispose();
            }
            this.callLoadLayout(lName);
            this.lName = lName;
        }
    }
    render() {
        if (this.lName !== "None") {
            this.callLoadLayout(this.lName);
        } else {
            this.isLoadedLayout = true;
        }
        this.renderNoLayout();
    }
    addAttributeURL(url, str) {
        if (url.indexOf("?") != -1) {
            return url + "&" + str.replace("?", "");
        } else {
            return url + str;
        }
    }
    renderNoLayout() {
        (function () {
            this.callLoadPage(window.location.href);
        }).bind(this)();
    }

    scrollTop() {
        return window.scrollY || window.smScroll.scrollTop;
    }
    checkVisible(elm) {
        var rect = elm.getBoundingClientRect();
        var viewHeight = window.innerHeight;
        return !(rect.bottom < 0 || rect.top - viewHeight >= 0);
    }
    initScrollBar() {
        if (window.getOS() == "iOS") {
            document.querySelector(".main-scrollbar[data-scrollbar]").classList.add("iOS");
            let scrollY = 0;
            let ticking = false;

            window.addEventListener('scroll', ((event) => {
                scrollY = window.scrollY;

                if (!ticking) {
                    window.requestAnimationFrame(() => {
                        this.trigger("App_Scrolling", scrollY);
                        ticking = false;
                    });
                    ticking = true;
                }

            }).bind(this));
        } else {
            window.Scrollbar.use(window.OverscrollPlugin);
            var sOption = {
                damping: (window.getOS() == "Android") ? .08 : .04,
                thumbMinSize: 25,
                renderByPixel: true,
                alwaysShowTracks: true,
                continuousScrolling: true,
                plugins: {
                    overscroll: {
                        effect: 'bounce',
                        damping: .15,//0.15
                        maxOverscroll: 250
                    }
                }
            };
            window.smScroll = window.Scrollbar.init(document.querySelector('.main-scrollbar[data-scrollbar]'), sOption);
            window.smScroll.addListener((status => {
                this.trigger("App_Scrolling", status.offset.y);
            }).bind(this));
        }
    }
    initNavs(groups = "Defaul") {
        document.querySelectorAll("[app-nav]").forEach(((el) => {
            if (el.getAttribute("app-nav") !== 'true') {
                const f1 = function (ev) {
                    ev.preventDefault();
                };
                this.addSystemEvent("click", el, f1, groups)
                const f2 = (function (ev) {
                    if (window.isValidPointerClick(ev)) return;
                 
                    this.initNavApp(ev.currentTarget.getAttribute("href"), ev.currentTarget.hasAttribute("isflexpage"));
                }).bind(this);
                this.addSystemEvent(this.eventName, el, f2, groups);
                el.setAttribute("app-nav", true);
            }
        }).bind(this));
    }
    initNavApp(t, flex = false) {
        if (window.getOS() == "iOS") {
            window.scrollTo({ top: 0, behavior: 'smooth' });
        } else {
            window.smScroll.scrollTo(0, 0, 500);
        }
        this.isRedirectPage = true;
        this.callLoadPage(window.GetAbsoluteURL(t), flex);
    }
    callLoadPage(url, flex = false) {
        url = window.getPathFromUrl(url);
        this.cachePage.searchFlexPage(url, ((result) => {
            if (result) {
                this.cachePage.get(result.layout.linkID, ((result1) => {
                    if (result1) {
                        const a = result.layout.info;
                        const doc2 = new DOMParser().parseFromString(result1.doc, "text/html");
                        const doc = doc2.firstChild.querySelector("head");
                        const obj = {
                            html: result1.html,
                            title: a.title,
                            idPage: a.idPage,
                            lName: a.lName,
                            doc: doc,
                            dynamicF: a.dynamicF
                        };
                        this.setContentPage(obj, url); 
                    } else {
                        this.getPage(url, result)
                    }
                }).bind(this), "flex");
            } else {
                this.getPage(url);
            }
        }).bind(this));
        //if (flex) {
           
        //} else {
        //    this.cachePage.get(url, ((result) => {
        //        if (result) {
        //            // Set content page từ cache
        //        } else {
        //            this.getPage(url);  // Load mới nếu chưa có trong cache
        //        }
        //    }).bind(this));
        //}
    }
    loadContentPage(content) {
        const tpl = document.createElement('template');
        tpl.innerHTML = content;
        for (var i = this.mainApp.childNodes.length - 1; i >= 0; i--) {
            this.mainApp.childNodes[i].remove();
        }
        this.mainApp.appendChild(tpl.content);
    }
    contentPage(page, url) {
        document.title = page.title + " - " + this.pageName;
        var meta = document.head.querySelector("meta[name=idPage]");
        document.body.setAttribute("page", page.idPage);
        meta.content = page.idPage;
        meta.setAttribute("layName", page.lName);       
        this.loadContentPage(page.html);
        window.history.pushState({"url":url}, page.title, url);
    }
    setContentPage(page, url) {
        this.contentPage(page, url);
        var l = new LoadScriptAsync("Page");
        if (this.isRedirectPage) {
            this.trigger("redirect_page", page);
            this.checkLayout(page.lName);
            this.isRedirectPage = false;
        }
        l.on("Loaded", (() => {
            this.loadedPage();
        }).bind(this));
        l.processScript(page.doc);
    }
    getPage(url, rs = null) {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", this.addAttributeURL(url, "?vr=cAsync"));
        xhr.send();
        var f = function (evt) {
            if (evt.currentTarget.readyState == 4 && evt.currentTarget.status == 200) {
                if (evt.currentTarget.responseText) {
                    var jP = JSON.parse(evt.currentTarget.responseText);
                    this.loadPage(jP, window.GetAbsoluteURL(url), rs);
                }
            }
        }.bind(this);
        xhr.addEventListener("readystatechange", f, false);
    }
    loadPage(o, url, rs) {
        const title = o.Title;
        const idPage = o.PageId;
        const doc2 = new DOMParser().parseFromString(o.Scripts, "text/html");
        const doc = doc2.firstChild.querySelector("head");
        const dF = doc.querySelector("script[dynamic]");
        const obj = {
            html: o.Content,
            title: title,
            idPage: idPage,
            lName: o.LayoutName,
            flexPageID: o.FlexPageId,
            doc: doc,
            dynamicF: dF ? dF.getAttribute("dynamic") : null
        };
        this.cachePage.set(url, obj);
        this.setContentPage(obj, url);
    }
    loadedPage() {
        this.metaPage = document.head.querySelector("meta[name=idPage]");
        if (this.metaPage != null && this.isLoadedLayout) {
            this.cachePage.getInf(window.location.href, ((tmp) => {
                if (tmp != null && tmp.dynamicF != null) {

                    if (window[tmp.dynamicF] != null) window[tmp.dynamicF]();
                } else {
                    if (window["L" + this.metaPage.content] != null) window["L" + this.metaPage.content]();
                }
                this.initNavs(this.metaPage.content);
                this.trigger("pageLoaded", null);
            }).bind(this));
           
        } else {
            window.requestTimeout(this.loadedPage.bind(this), 10, window.registerCancel);
        }
    }
    callLoadLayout(lName) {
        this.cachePage.get("layout|" + lName, ((result) => {
            if (result) {
                this.setLayout(result);  // Set content page từ cache
            } else {
                console.log("connect new Layout");
                this.loadLayout();  // Load mới nếu chưa có trong cache
            }
            
        }).bind(this), "layout");
     
    }
    loadLayout() {
        (function () {
            var xhr = new XMLHttpRequest();
            xhr.open("GET", this.addAttributeURL(window.location.href, "?vr=lAsync"));
            xhr.send();
            var f = function (evt) {
                if (evt.currentTarget.readyState == 4 && evt.currentTarget.status == 200) {
                    if (evt.currentTarget.responseText) {
                        var jP = JSON.parse(evt.currentTarget.responseText);
                        this.setLayout(jP);
                        this.cachePage.set(this.lName, jP, "layout");
                    }
                }
            }.bind(this);
            xhr.addEventListener("readystatechange", f, false);
        }).bind(this)();
    }
    loadedLayout() {
        if (this.lName !== "None") {
            const l = window.ALayout.get(this.lName);
            if (!l.isLoaded) {
                l.renderMenu();
                this.isLoadedLayout = true;
                this.trigger("layoutLoaded", null);
            }
        }
    }
    setLayout(o) {
       
        var oP = new DOMParser();
        var pHtml = oP.parseFromString(o.Content, 'text/html');
        (function () {
            pHtml.body.childNodes.forEach(function (item) {
                var t = document.getElementById(item.getAttribute("id"));
                if (t) {
                    item.classList.forEach(el => {
                        t.classList.add(el);
                    });
                    item.childNodes.forEach(el => {
                        t.appendChild(el.cloneNode(true));
                    });
                }
            });
        }).bind(this)();
        (function () {
            var doc = oP.parseFromString(o.Scripts, 'text/html').head;
            var l = new LoadScriptAsync("Layout");
            l.on("Loaded", (() => {
                this.loadedLayout();
            }).bind(this));
            l.processScript(doc);
        }).bind(this)();
    }
    loadCSS(arr) {
        for (var i = 0; i < arr.length; i++) {
            var link = document.createElement('link');
            link.setAttribute('rel', 'stylesheet');
            link.setAttribute('href', arr[i]);
            document.head.appendChild(link);
        }
    }
}
class CacheManager {
    constructor() {
        this.storage = new CacheStorage("appCache");
        this.pageMap = new Map();
        this.layoutMap = new Map();
        this.readyCallbacks = [];
        this.isReady = false;
        this.flexPages = new FlexPageTrie();
        this.countFP = 0;
        this._loadIndex();
        window.addEventListener("beforeunload", (function (e) {
            this.storage.set("treePage", this.flexPages.toJSON(), function () { });
        }).bind(this));
    }


    _key(id, type = "page") {
        return `${type}|${id}`;
    }

    _onReady(callback) {
        if (this.isReady) {
            callback();
        } else {
            this.readyCallbacks.push(callback);
        }
    }

    _loadIndex() {
        this.storage.getAllKeys(((keys) => {
            let pending = 0;
            keys.forEach(((key) => {
                if (!key.startsWith(`${this.storage.prefix}|`)) return;

                const shortKey = key.replace(`${this.storage.prefix}|`, "");
                const [type, id] = shortKey.split("|");

                if (type === "treePage") {
                    pending++;
                    this.storage.get(key, (data) => {
                        if (data) this.flexPages.fromJSON(data);
                        if (--pending === 0) this._finishInit();
                    }, true);
                } else if (type === "layout") {
                    this.layoutMap.set(id, true);
                }
            }).bind(this));
            if (pending === 0) this._finishInit(); // Trường hợp không có key nào là "pI"
        }).bind(this));
    }

    _finishInit() {
        this.isReady = true;
        this.readyCallbacks.forEach(cb => cb());
        this.readyCallbacks = [];
    }

    // Lấy thông tin trang hoặc layout
    getInf(id, callback, type = "page") {
        this._onReady(() => {
            (type === "page") ? callback(this.pageMap.get(id)) : callback(this.layoutMap.get(id));
        });
    }
    
    // Lấy dữ liệu từ cache (localStorage hoặc IndexedDB nếu cần)
    get(id, callback, type = "cahce") {
        this._onReady(() => {
            this.storage.get(id, (data) => {
                if (!data) {
                    callback(null);
                    return;
                }
                callback(data);
            }, false, type);
        });
    }
    searchFlexPage(url, callback) {
        this._onReady(() => {
            callback(this.flexPages.search(url));
        });
    }
    genShortID() {
        return Date.now().toString(36) + Math.random().toString(36).substring(2, 5);
    }
    // Lưu dữ liệu page hoặc layout vào cache
    set(id, obj, type = "page", callback) {
        this._onReady(() => {
            const key = this._key(id, type);

            if (type === "page") {
                const htmlData = {
                    html: obj.html,
                    doc: obj.doc.innerHTML
                };
                let info = {
                    title: obj.title,
                    idPage: obj.idPage,
                    lName: obj.lName,
                    dynamicF: obj.dynamicF
                };
                // Lưu dữ liệu htmlData vào storage
                const idN = this.genShortID();
                let url = window.getPathFromUrl(id);
                if (obj.flexPageID !== "None" && obj.flexPageID !== "") {
                    if (obj.dynamicF !== null && obj.dynamicF !== undefined && obj.dynamicF != "") {
                        url = obj.flexPageID;
                        info = {
                            lName: obj.lName,
                            dynamicF: obj.dynamicF
                        }
                    }
                }
               // this.flexPages.insert(url, { "linkID": idN, "info": info });
                //this.storage.set(idN, htmlData, function () { }, type = "flex");
                if (this.countFP > 5) {
                    this.countFP = 0;
                    //this.storage.set("treePage", this.flexPages.toJSON(), function () { });
                } else {
                    this.countFP++;
                }
                this.storage.set("treePage", this.flexPages.toJSON(), function () { });
            } else { 
                const layout = {
                    Content: obj.Content,
                    Scripts: obj.Scripts
                };

                // Lưu dữ liệu layout vào storage
                this.layoutMap.set(id, true);
                this.storage.set(key, layout, function () {
                    //  callback(null);  // Gọi callback khi lưu thành công
                });
            }
        });
    }
}
class CacheStorage {
    constructor(prefix = "cache", maxAgeMs = 3 * 24 * 60 * 60 * 1000) {
        this.prefix = prefix;
        this.maxAgeMs = maxAgeMs;
        this.db = null;
        this.useIndexedDB = false;
        this.queue = [];
        this.ready = false;
        this._initDB();
        this.cleanupExpired();
    }

    _key(key) {
        return `${this.prefix}|${key}`;
    }

    _initDB() {

        const openDB = () => {
            console.log("Renew Database");
            const req = indexedDB.open(`${this.prefix}_db`, 1);
            req.onupgradeneeded = (e) => {
                const db = e.target.result;
                if (!db.objectStoreNames.contains("cache")) {
                    db.createObjectStore("cache", { keyPath: "key" });
                }
                if (!db.objectStoreNames.contains("fpCache")) {
                    db.createObjectStore("fpCache", { keyPath: "key" });
                }
            };
            req.onsuccess = () => {
                this.db = req.result;
                this.ready = true;
                this._flushQueue();
            };
            req.onerror = () => {
                console.warn("IndexedDB open failed, fallback to localStorage");
                this.db = null;
                this.ready = true;
                this._flushQueue();
            };
        };


        const del = indexedDB.deleteDatabase(`${this.prefix}_db`);
        del.onsuccess = () => openDB();
        del.onerror = () => {
            console.warn("deleteDatabase failed, continue opening fresh");
            openDB();
        };
        del.onblocked = () => {
            console.warn("deleteDatabase blocked (tab khác đang giữ kết nối)");
        };
      
    }
    _flushQueue() {
        while (this.queue.length) {
            const fn = this.queue.shift();
            fn();
        }
    }

    _onReady(fn) {
        if (this.ready) fn();
        else this.queue.push(fn);
    }

    get(key, callback, f = false, type = "cache") {
        this._onReady(() => {
            if (this.db) {
                let tx, store, req;
                if (type === "flex") {
                    tx = this.db.transaction("fpCache", "readonly");
                    store = tx.objectStore("fpCache");
                } else {
                    tx = this.db.transaction("cache", "readonly");
                    store = tx.objectStore("cache");
                }
                req = store.get(f ? key : this._key(key));
                req.onsuccess = () => callback(req.result ? req.result.value : null);
            } else {
                try {
                    const raw = localStorage.getItem(this._key(key));
                    const parsed = raw ? JSON.parse(raw) : null;
                    callback(parsed.data);
                } catch {
                    callback(null);
                }
            }
        });
    }
    getAllKeys(callback) {
        this._onReady(() => {
            if (this.db) {
                let tx = this.db.transaction("cache", "readonly");
                let store = tx.objectStore("cache");
                let req = store.getAllKeys();
                req.onsuccess = (evt) => {
                    callback(evt.target.result);
                };
            }
        });
    }
    set(key, data, callback = () => { }, type="cache") {
        this._onReady(() => {
            if (this.db) {
                let tx, store, req;
                if (type === "flex") {
                    tx = this.db.transaction("fpCache", "readwrite");
                    store = tx.objectStore("fpCache");
                    store.put({ key: this._key(key), value: data, ts: Date.now() });
                } else {
                    tx = this.db.transaction("cache", "readwrite");
                    store = tx.objectStore("cache");
                    store.put({ key: this._key(key), value: data, ts: Date.now() });
                }
     
                tx.oncomplete = () => callback(null);
                tx.onerror = (e) => callback(e);
            } else {
                try {
                    console.log("Run Local");
                    localStorage.setItem(this._key(key), JSON.stringify({ data, ts: Date.now() }));
                    callback(null);
                } catch (e) {
                    
                    callback(e);
                }
            }
        });
    }

    remove(key, type = "cache") {
        const fullKey = this._key(key);
        if (this.db) {
            try {
                const tx = this.db.transaction("cache", "readwrite");
                const store = tx.objectStore("cache");
                store.delete(fullKey);
            } catch { }
        }
    }

    cleanupExpired() {
        const now = Date.now();
        for (let i = localStorage.length - 1; i >= 0; i--) {
            const key = localStorage.key(i);
            if (!key.startsWith(this.prefix)) continue;
            try {
                const raw = JSON.parse(localStorage.getItem(key));
                if (raw && raw.ts && now - raw.ts > this.maxAgeMs) {
                    localStorage.removeItem(key);
                }
            } catch {
                localStorage.removeItem(key);
            }
        }
    }
}
class TrieNode {
    constructor() {
        this.children = new Map(); // segment -> TrieNode
        this.paramChild = null;    // node chứa ':param'
        this.wildcardChild = null; // node chứa '*'
        this.paramName = null;     // tên param nếu là param node
        this.layout = null;
    }
}

class FlexPageTrie {
    constructor() {
        this.root = new TrieNode();
    }

    insert(path, layout) {
        const segments = path.split('/').filter(Boolean);
        let node = this.root;

        for (const seg of segments) {
            if (seg === '*') {
                if (!node.wildcardChild) node.wildcardChild = new TrieNode();
                node = node.wildcardChild;
            } else if (seg.startsWith(':')) {
                if (!node.paramChild) node.paramChild = new TrieNode();
                node.paramChild.paramName = seg.slice(1);
                node = node.paramChild;
            } else {
                if (!node.children.has(seg)) node.children.set(seg, new TrieNode());
                node = node.children.get(seg);
            }
        }

        node.layout = layout;
    }

    search(url) {
        const segments = url.split('/').filter(Boolean);
        const result = this._searchRecursive(this.root, segments, 0, {});
        return result || null;
    }

    _searchRecursive(node, segments, index, params) {
        if (!node) return null;
        if (index === segments.length) {
            if (node.layout) {
                return { layout: node.layout, params };
            }
            return null;
        }

        const seg = segments[index];

        // Ưu tiên match chính xác
        if (node.children.has(seg)) {
            const res = this._searchRecursive(node.children.get(seg), segments, index + 1, { ...params });
            if (res) return res;
        }

        // Match dynamic :param
        if (node.paramChild) {
            const newParams = { ...params };
            newParams[node.paramChild.paramName] = seg;
            const res = this._searchRecursive(node.paramChild, segments, index + 1, newParams);
            if (res) return res;
        }

        // Match wildcard *
        if (node.wildcardChild) {
            const res = this._searchRecursive(node.wildcardChild, segments, index + 1, { ...params });
            if (res) return res;
        }

        return null;
    }
    _toJSON(node) {
        const obj = {
            layout: node.layout,
            paramName: node.paramName,
            children: {},
            paramChild: node.paramChild ? this._toJSON(node.paramChild) : null,
            wildcardChild: node.wildcardChild ? this._toJSON(node.wildcardChild) : null
        };

        for (const [key, child] of node.children.entries()) {
            obj.children[key] = this._toJSON(child);
        }

        return obj;
    }

    toJSON() {
        return this._toJSON(this.root);
    }
    _fromJSON(data) {
        const node = new TrieNode();
        node.layout = data.layout;
        node.paramName = data.paramName;

        for (const key in data.children) {
            node.children.set(key, this._fromJSON(data.children[key]));
        }

        if (data.paramChild) {
            node.paramChild = this._fromJSON(data.paramChild);
        }

        if (data.wildcardChild) {
            node.wildcardChild = this._fromJSON(data.wildcardChild);
        }

        return node;
    }

    fromJSON(data) {
        this.root = this._fromJSON(data);
    }
}

window.removeStopCollapsed = function () {
    if (window.dropdown != null && window.dropdown.currentE != null) {
        if (window.dropdown.currentE.item.hasAttribute("stopCollapsed")) {
            window.dropdown.currentE.item.removeAttribute("stopCollapsed");
            window.dropdown.currentE = null;
        }
    }
}
window.scroll_options = {
    damping: 0.1,
    thumbMinSize: 25,
    renderByPixel: true,
    alwaysShowTracks: true,
    continuousScrolling: true,
    plugins: {
        overscroll: {
            effect: 'bounce',
            damping: 0.15,
            maxOverscroll: 150
        }
    }
};