import node_events from "node:events";
function util_define(object, properties) {
    for (const name of Object.getOwnPropertyNames(properties)){
        const propDesc = Object.getOwnPropertyDescriptor(properties, name);
        Object.defineProperty(object, name, propDesc);
    }
}
function installOwnProperties(globalThis1) {
    util_define(globalThis1, {
        get globalThis () {
            return globalThis1._globalProxy;
        }
    });
}
const createGlobalThis = ()=>{
    const globalThis1 = {};
    globalThis1._globalObject = globalThis1._globalProxy = globalThis1;
    installOwnProperties(globalThis1);
    return globalThis1;
};
function _define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
const initElementTree = ()=>{
    let uiSignNext = 0;
    return new class {
        countElement(element, parentComponentUniqueId) {
            element.$$uiSign = uiSignNext++;
            this.uniqueId2Element.set(element.$$uiSign, element);
            element.parentComponentUniqueId = parentComponentUniqueId;
        }
        __CreatePage(_tag, parentComponentUniqueId) {
            const page = this.__CreateElement('page', parentComponentUniqueId);
            this.root = page;
            document.body.innerHTML = '';
            lynxTestingEnv.env.window.document.body.appendChild(page);
            return page;
        }
        __CreateRawText(text) {
            const element = lynxTestingEnv.env.window.document.createTextNode(text);
            this.countElement(element, 0);
            return element;
        }
        __GetElementUniqueID(e) {
            return e.$$uiSign;
        }
        __SetClasses(e, cls) {
            e.className = cls;
        }
        __CreateElement(tag, parentComponentUniqueId) {
            if ('raw-text' === tag) return this.__CreateRawText('');
            const element = lynxTestingEnv.env.window.document.createElement(tag);
            this.countElement(element, parentComponentUniqueId);
            return element;
        }
        __CreateView(parentComponentUniqueId) {
            return this.__CreateElement('view', parentComponentUniqueId);
        }
        __CreateScrollView(parentComponentUniqueId) {
            return this.__CreateElement('scroll-view', parentComponentUniqueId);
        }
        __FirstElement(e) {
            return e.firstChild;
        }
        __CreateText(parentComponentUniqueId) {
            return this.__CreateElement('text', parentComponentUniqueId);
        }
        __CreateImage(parentComponentUniqueId) {
            return this.__CreateElement('image', parentComponentUniqueId);
        }
        __CreateWrapperElement(parentComponentUniqueId) {
            return this.__CreateElement('wrapper', parentComponentUniqueId);
        }
        __AddInlineStyle(e, key, value) {
            e.style[key] = value;
        }
        __AppendElement(parent, child) {
            parent.appendChild(child);
        }
        __SetCSSId(e, id, entryName) {
            const cssId = `${entryName ?? '__Card__'}:${id}`;
            if (Array.isArray(e)) e.forEach((item)=>{
                item.cssId = cssId;
            });
            else e.cssId = cssId;
        }
        __SetAttribute(e, key, value) {
            if ('style' === key || 'class' === key || 'className' === key || 'key' === key || 'id' === key || 'ref' === key || /^data-/.exec(key) || /^(bind|catch|global-bind|capture-bind|capture-catch)[A-Za-z]/.exec(key)) throw new Error(`Cannot use __SetAttribute for "${key}"`);
            if ('update-list-info' === key) {
                let listInfoStr = e.getAttribute(key);
                let listInfo = listInfoStr ? JSON.parse(listInfoStr) : [];
                listInfo.push(value);
                e.setAttribute(key, JSON.stringify(listInfo));
                return;
            }
            if ('text' === key) {
                e.textContent = value;
                return;
            }
            if (null === value) return void e.removeAttribute(key);
            if ('string' == typeof value) return void e.setAttribute(key, value);
            e.setAttribute(key, JSON.stringify(value));
        }
        __AddEvent(e, eventType, eventName, eventHandler) {
            if (e.eventMap?.[`${eventType}:${eventName}`]) {
                e.removeEventListener(`${eventType}:${eventName}`, e.eventMap[`${eventType}:${eventName}`]);
                delete e.eventMap[`${eventType}:${eventName}`];
            }
            if (void 0 === eventHandler) return;
            if ('string' != typeof eventHandler && void 0 === eventHandler['type']) throw new Error(`event must be string, but got ${typeof eventHandler}`);
            const listener = (evt)=>{
                if ('object' == typeof eventHandler && 'worklet' === eventHandler['type']) {
                    const isBackground = !__MAIN_THREAD__;
                    globalThis.lynxTestingEnv.switchToMainThread();
                    runWorklet(eventHandler.value, [
                        Object.assign({}, evt)
                    ]);
                    if (isBackground) globalThis.lynxTestingEnv.switchToBackgroundThread();
                } else {
                    if ('catchEvent' === eventType || 'capture-catch' === eventType) evt.stopPropagation();
                    globalThis.lynxCoreInject.tt.publishEvent(eventHandler, evt);
                }
            };
            e.eventMap = e.eventMap ?? {};
            e.eventMap[`${eventType}:${eventName}`] = listener;
            e.addEventListener(`${eventType}:${eventName}`, listener, {
                capture: 'capture-bind' === eventType || 'capture-catch' === eventType
            });
        }
        __GetEvent(e, eventType, eventName) {
            const jsFunction = e.eventMap?.[`${eventType}:${eventName}`];
            if (void 0 !== jsFunction) return {
                type: eventType,
                name: eventName,
                jsFunction
            };
        }
        __SetID(e, id) {
            e.id = id;
        }
        __SetInlineStyles(e, styles) {
            if ('string' == typeof styles) e.setAttributeNS(null, 'style', styles);
            else Object.assign(e.style, styles);
        }
        __AddDataset(e, key, value) {
            e.dataset[key] = value;
        }
        __SetDataset(e, dataset) {
            Object.assign(e.dataset, dataset);
        }
        __SetGestureDetector(e, id, type, config, relationMap) {
            e.gesture = {
                id,
                type,
                config,
                relationMap
            };
        }
        __RemoveGestureDetector(e, id) {
            if (e.gesture?.['id'] === id) delete e.gesture;
        }
        __GetDataset(e) {
            return e.dataset;
        }
        __RemoveElement(parent, child) {
            let ch = parent.firstChild;
            while(ch){
                if (ch === child) {
                    parent.removeChild(ch);
                    break;
                }
                ch = ch.nextSibling;
            }
        }
        __InsertElementBefore(parent, child, ref) {
            if (void 0 === ref) parent.appendChild(child);
            else parent.insertBefore(child, ref);
        }
        __ReplaceElement(newElement, oldElement) {
            const parent = oldElement.parentNode;
            if (!parent) throw new Error('unreachable');
            parent.replaceChild(newElement, oldElement);
        }
        __FlushElementTree() {}
        __UpdateListComponents(_list, _components) {}
        __UpdateListCallbacks(list, componentAtIndex, enqueueComponent) {
            Object.defineProperties(list, {
                componentAtIndex: {
                    enumerable: false,
                    configurable: true,
                    value: componentAtIndex
                },
                enqueueComponent: {
                    enumerable: false,
                    configurable: true,
                    value: enqueueComponent
                }
            });
        }
        __CreateList(parentComponentUniqueId, componentAtIndex, enqueueComponent) {
            const e = this.__CreateElement('list', parentComponentUniqueId);
            Object.defineProperties(e, {
                componentAtIndex: {
                    enumerable: false,
                    configurable: true,
                    value: componentAtIndex
                },
                enqueueComponent: {
                    enumerable: false,
                    configurable: true,
                    value: enqueueComponent
                }
            });
            return e;
        }
        __GetTag(ele) {
            return ele.nodeName;
        }
        __GetAttributeByName(ele, name) {
            return ele.getAttribute(name);
        }
        __ElementAnimate(element, args) {
            const [operation, name] = args;
            switch(operation){
                case 0:
                    {
                        const keyframes = args[2];
                        const options = args[3];
                        this.animationMap.set(name, {
                            element,
                            state: 'running',
                            keyframes,
                            options
                        });
                        break;
                    }
                case 1:
                    {
                        const anim = this.animationMap.get(name);
                        if (anim) anim.state = 'running';
                        break;
                    }
                case 2:
                    {
                        const anim = this.animationMap.get(name);
                        if (anim) anim.state = 'paused';
                        break;
                    }
                case 3:
                    this.animationMap.delete(name);
                    break;
                case 4:
                    {
                        const anim = this.animationMap.get(name);
                        if (anim) anim.state = 'finished';
                        break;
                    }
            }
        }
        clear() {
            this.root = void 0;
        }
        toTree() {
            return this.root;
        }
        enterListItemAtIndex(e, index, ...args) {
            const isBackground = !__MAIN_THREAD__;
            globalThis.lynxTestingEnv.switchToMainThread();
            try {
                const { componentAtIndex, $$uiSign } = e;
                return componentAtIndex(e, $$uiSign, index, ...args);
            } finally{
                if (isBackground) globalThis.lynxTestingEnv.switchToBackgroundThread();
            }
        }
        leaveListItem(e, uiSign) {
            const isBackground = !__MAIN_THREAD__;
            globalThis.lynxTestingEnv.switchToMainThread();
            try {
                const { enqueueComponent, $$uiSign } = e;
                enqueueComponent(e, $$uiSign, uiSign);
            } finally{
                if (isBackground) globalThis.lynxTestingEnv.switchToBackgroundThread();
            }
        }
        toJSON() {
            return this.toTree();
        }
        __GetElementByUniqueId(uniqueId) {
            return this.uniqueId2Element.get(uniqueId);
        }
        __GetPageElement() {
            return this.root;
        }
        __QuerySelector(e, cssSelector, _params) {
            return e.querySelector(cssSelector) ?? void 0;
        }
        constructor(){
            _define_property(this, "uniqueId2Element", new Map());
            _define_property(this, "root", void 0);
            _define_property(this, "animationMap", new Map());
        }
    }();
};
function GlobalEventEmitter_define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
class GlobalEventEmitter {
    addListener(eventName, listener) {
        this.listeners[eventName] ??= [];
        this.listeners[eventName].push(listener);
    }
    removeListener(eventName, listener) {
        if (!this.listeners[eventName]) return;
        this.listeners[eventName] = this.listeners[eventName].filter((l)=>l !== listener);
    }
    emit(eventName, args) {
        if (!this.listeners[eventName]) return;
        this.listeners[eventName].forEach((listener)=>args ? listener(...args) : listener());
    }
    clear() {
        this.listeners = {};
    }
    removeAllListeners(eventName) {
        if (eventName) delete this.listeners[eventName];
        else this.clear();
    }
    trigger(eventName, params) {
        this.emit(eventName, [
            params
        ]);
    }
    toggle(eventName, ...data) {
        this.emit(eventName, data);
    }
    constructor(){
        GlobalEventEmitter_define_property(this, "listeners", {});
    }
}
function dist_define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
function installLynxTestingEnv(target, env) {
    target.lynxEnv = env;
    target.lynxTestingEnv = new LynxTestingEnv(env);
    target.Node = env.window.Node;
}
function uninstallLynxTestingEnv(target) {
    delete target.lynxTestingEnv;
    delete target.lynxEnv;
    delete target.Node;
}
function __injectElementApi(target) {
    const elementTree1 = initElementTree();
    target.elementTree = elementTree1;
    if (void 0 === target) target = globalThis;
    for (const k of Object.getOwnPropertyNames(elementTree1.constructor.prototype))if (k.startsWith('__')) target[k] = elementTree1[k].bind(elementTree1);
    target.$kTemplateAssembler = {};
    target.registerDataProcessor = ()=>{
        console.error('registerDataProcessor is not implemented');
    };
    target.__OnLifecycleEvent = (...args)=>{
        const isMainThread = __MAIN_THREAD__;
        globalThis.lynxTestingEnv.switchToBackgroundThread();
        globalThis.lynxCoreInject.tt.OnLifecycleEvent(...args);
        if (isMainThread) globalThis.lynxTestingEnv.switchToMainThread();
    };
    target._ReportError = ()=>{};
}
function createPolyfills() {
    const app = {
        callLepusMethod: (...rLynxChange)=>{
            const isBackground = !__MAIN_THREAD__;
            globalThis.lynxTestingEnv.switchToMainThread();
            globalThis[rLynxChange[0]](rLynxChange[1]);
            globalThis.lynxTestingEnv.switchToBackgroundThread();
            rLynxChange[2]?.();
            globalThis.lynxTestingEnv.switchToMainThread();
            if (isBackground) globalThis.lynxTestingEnv.switchToBackgroundThread();
        },
        markTiming: ()=>{},
        createJSObjectDestructionObserver: ()=>({})
    };
    const performance = {
        __functionCallHistory: [],
        _generatePipelineOptions: ()=>{
            performance.__functionCallHistory.push([
                '_generatePipelineOptions'
            ]);
            return {
                pipelineID: 'pipelineID',
                needTimestamps: false
            };
        },
        _onPipelineStart: (id)=>{
            performance.__functionCallHistory.push([
                '_onPipelineStart',
                id
            ]);
        },
        _markTiming: (id, key)=>{
            performance.__functionCallHistory.push([
                '_markTiming',
                id,
                key
            ]);
        },
        _bindPipelineIdWithTimingFlag: (id, flag)=>{
            performance.__functionCallHistory.push([
                '_bindPipelineIdWithTimingFlag',
                id,
                flag
            ]);
        }
    };
    const ee = new node_events();
    ee.dispatchEvent = ({ type, data })=>{
        if (!globalThis.lynxTestingEnv) return;
        const origin = __MAIN_THREAD__ ? 'CoreContext' : 'JSContext';
        if ('CoreContext' === origin) lynxTestingEnv.switchToBackgroundThread();
        else lynxTestingEnv.switchToMainThread();
        ee.emit(type, {
            data: data,
            origin
        });
        if ('CoreContext' === origin) lynxTestingEnv.switchToMainThread();
        else lynxTestingEnv.switchToBackgroundThread();
    };
    ee.addEventListener = ee.addListener;
    ee.removeEventListener = ee.removeListener;
    const CoreContext = ee;
    const JsContext = ee;
    function __LoadLepusChunk(chunkName, _options) {
        const isBackground = !__MAIN_THREAD__;
        globalThis.lynxTestingEnv.switchToMainThread();
        let ans;
        if ('worklet-runtime' === chunkName) ans = globalThis.onInitWorkletRuntime?.();
        else throw new Error(`__LoadLepusChunk: Unknown chunk name: ${chunkName}`);
        if (isBackground) globalThis.lynxTestingEnv.switchToBackgroundThread();
        return ans;
    }
    return {
        app,
        performance,
        CoreContext,
        JsContext,
        __LoadLepusChunk
    };
}
function injectMainThreadGlobals(target, polyfills) {
    __injectElementApi(target);
    const { performance, CoreContext, JsContext, __LoadLepusChunk } = polyfills || {};
    if (void 0 === target) target = globalThis;
    target.__DEV__ = true;
    target.__PROFILE__ = true;
    target.__ALOG__ = true;
    target.__ALOG_ELEMENT_API__ = true;
    target.__JS__ = false;
    target.__LEPUS__ = true;
    target.__BACKGROUND__ = false;
    target.__MAIN_THREAD__ = true;
    target.__REF_FIRE_IMMEDIATELY__ = false;
    target.__FIRST_SCREEN_SYNC_TIMING__ = 'immediately';
    target.__TESTING_FORCE_RENDER_TO_OPCODE__ = false;
    target.__ENABLE_SSR__ = false;
    target.globDynamicComponentEntry = '__Card__';
    const native = {
        _listeners: {},
        onTriggerEvent: void 0,
        postMessage: (_message)=>{},
        addEventListener: (type, listener)=>{
            if (!native._listeners[type]) native._listeners[type] = [];
            native._listeners[type].push(listener);
        },
        removeEventListener: (type, listener)=>{
            if (native._listeners[type]) native._listeners[type] = native._listeners[type].filter((l)=>l !== listener);
        },
        dispatchEvent: (event)=>{
            const listeners = native._listeners[event.type];
            if (listeners) listeners.forEach((listener)=>listener(event));
            return {
                canceled: false
            };
        }
    };
    target.lynx = {
        performance,
        getNative: ()=>native,
        getCoreContext: ()=>CoreContext,
        getJSContext: ()=>JsContext,
        reportError: (e)=>{
            throw e;
        }
    };
    target.requestAnimationFrame = setTimeout;
    target.cancelAnimationFrame = clearTimeout;
    target.console.profile = console.profile = ()=>{};
    target.console.profileEnd = console.profileEnd = ()=>{};
    target.console.alog = console.alog = ()=>{};
    target.__LoadLepusChunk = __LoadLepusChunk;
    globalThis.onInjectMainThreadGlobals?.(target);
}
const IGNORE_LIST_GLOBALS = [
    'globalThis',
    'global'
];
class NodesRef {
    invoke() {
        throw new Error('not implemented');
    }
    path() {
        throw new Error('not implemented');
    }
    fields() {
        throw new Error('not implemented');
    }
    setNativeProps(props) {
        return {
            exec: ()=>{
                const element = 0 === this._nodeSelectToken.type ? lynxTestingEnv.env.window.document.querySelector(this._nodeSelectToken.identifier) : elementTree.uniqueId2Element.get(Number(this._nodeSelectToken.identifier));
                if (!element) throw new Error(`[NodesRef.setNativeProps] Element not found for identifier=${this._nodeSelectToken.identifier}`);
                for(const key in props)element.setAttributeNS(null, key, props[key]);
            }
        };
    }
    constructor(selectorQuery, nodeSelectToken){
        dist_define_property(this, "_nodeSelectToken", void 0);
        dist_define_property(this, "_selectorQuery", void 0);
        this._nodeSelectToken = nodeSelectToken;
        this._selectorQuery = selectorQuery;
    }
}
function injectBackgroundThreadGlobals(target, polyfills) {
    const { app, performance, CoreContext, JsContext, __LoadLepusChunk } = polyfills || {};
    if (void 0 === target) target = globalThis;
    target.__DEV__ = true;
    target.__PROFILE__ = true;
    target.__ALOG__ = true;
    target.__ALOG_ELEMENT_API__ = true;
    target.__JS__ = true;
    target.__LEPUS__ = false;
    target.__BACKGROUND__ = true;
    target.__MAIN_THREAD__ = false;
    target.__ENABLE_SSR__ = false;
    target.globDynamicComponentEntry = '__Card__';
    target.lynxCoreInject = {};
    target.lynxCoreInject.tt = {
        _params: {
            initData: {},
            updateData: {}
        }
    };
    const globalEventEmitter = new GlobalEventEmitter();
    target.lynx = {
        getNativeApp: ()=>app,
        performance,
        createSelectorQuery: ()=>({
                selectUniqueID: function(uniqueId) {
                    return new NodesRef({}, {
                        type: 2,
                        identifier: uniqueId.toString()
                    });
                },
                select: function(selector) {
                    const el = lynxTestingEnv.env.window.document.querySelector(selector);
                    if (!el) throw new Error(`[createSelectorQuery.select] No element matches selector: ${selector}`);
                    return new NodesRef({}, {
                        type: 0,
                        identifier: selector
                    });
                }
            }),
        getCoreContext: ()=>CoreContext,
        getJSContext: ()=>JsContext,
        getJSModule: (moduleName)=>{
            if ('GlobalEventEmitter' === moduleName) return globalEventEmitter;
            throw new Error(`getJSModule(${moduleName}) not implemented`);
        },
        reportError: (e)=>{
            throw e;
        }
    };
    target.requestAnimationFrame = setTimeout;
    target.cancelAnimationFrame = clearTimeout;
    target.console.profile = console.profile = ()=>{};
    target.console.profileEnd = console.profileEnd = ()=>{};
    target.console.alog = console.alog = ()=>{};
    target.SystemInfo = {
        platform: 'iOS',
        pixelRatio: 3,
        pixelWidth: 1170,
        pixelHeight: 2532,
        osVersion: '17.0.2',
        enableKrypton: true,
        runtimeType: 'quickjs',
        lynxSdkVersion: '3.0'
    };
    target.__LoadLepusChunk = __LoadLepusChunk;
    globalThis.onInjectBackgroundThreadGlobals?.(target);
}
class LynxTestingEnv {
    injectGlobals() {
        const polyfills = createPolyfills();
        injectBackgroundThreadGlobals(this.backgroundThread.globalThis, polyfills);
        injectMainThreadGlobals(this.mainThread.globalThis, polyfills);
    }
    switchToBackgroundThread() {
        this.originals = new Map();
        Object.getOwnPropertyNames(this.backgroundThread.globalThis).forEach((key)=>{
            if (IGNORE_LIST_GLOBALS.includes(key)) return;
            this.originals.set(key, global[key]);
            global[key] = this.backgroundThread.globalThis[key];
        });
        globalThis?.onSwitchedToBackgroundThread?.();
    }
    switchToMainThread() {
        this.originals = new Map();
        Object.getOwnPropertyNames(this.mainThread.globalThis).forEach((key)=>{
            if (IGNORE_LIST_GLOBALS.includes(key)) return;
            this.originals.set(key, global[key]);
            global[key] = this.mainThread.globalThis[key];
        });
        globalThis?.onSwitchedToMainThread?.();
    }
    clearGlobal() {
        this.originals?.forEach((v, k)=>{
            global[k] = v;
        });
        this.originals?.clear();
    }
    reset() {
        this.injectGlobals();
        this.switchToMainThread();
        this.switchToBackgroundThread();
        globalThis.onResetLynxTestingEnv?.();
    }
    constructor(env){
        dist_define_property(this, "originals", new Map());
        dist_define_property(this, "backgroundThread", void 0);
        dist_define_property(this, "mainThread", void 0);
        dist_define_property(this, "env", void 0);
        this.env = env ?? global.lynxEnv;
        if (!this.env) throw new Error("LynxTestingEnv requires an object with a jsdom-like `window`. Pass `{ window }` to the constructor, or ensure your test runner sets global.lynxEnv to that shape (e.g., via a setup file).");
        this.backgroundThread = createGlobalThis();
        this.mainThread = createGlobalThis();
        const globalPolyfills = {
            console: this.env.window['console'],
            Event: this.env.window.Event,
            window: this.env.window,
            document: this.env.window.document
        };
        Object.assign(this.mainThread.globalThis, globalPolyfills);
        Object.assign(this.backgroundThread.globalThis, globalPolyfills);
        this.injectGlobals();
        this.switchToBackgroundThread();
    }
}
export { LynxTestingEnv, installLynxTestingEnv, uninstallLynxTestingEnv };
