const tslib = require('tslib');
import { Observable } from '../data/observable';
import { trace as profilingTrace, time, uptime, level as profilingLevel } from '../profiling';
function registerOnGlobalContext(moduleName, exportName) {
    Object.defineProperty(global, exportName, {
        get: function () {
            // We do not need to cache require() call since it is already cached in the runtime.
            const m = global.loadModule(moduleName);
            // Redefine the property to make sure the above code is executed only once.
            const resolvedValue = m[exportName];
            Object.defineProperty(global, exportName, {
                value: resolvedValue,
                configurable: true,
                writable: true,
            });
            return resolvedValue;
        },
        configurable: true,
    });
}
/**
 * Manages internal framework global state
 */
export class NativeScriptGlobalState {
    constructor() {
        this.launched = false;
        this._appInstanceReady = false;
        // console.log('creating NativeScriptGlobals...')
        this.events = new Observable();
        this._setLaunched = this._setLaunchedFn.bind(this);
        this.events.on('launch', this._setLaunched);
        if (profilingLevel() > 0) {
            this.events.on('displayed', () => {
                const duration = uptime();
                const end = time();
                const start = end - duration;
                profilingTrace(`Displayed in ${duration.toFixed(2)}ms`, start, end);
            });
        }
    }
    get appInstanceReady() {
        return this._appInstanceReady;
    }
    set appInstanceReady(value) {
        this._appInstanceReady = value;
        // app instance ready, wire up any app events waiting in startup queue
        if (this.appEventWiring && this.appEventWiring.length) {
            for (const callback of this.appEventWiring) {
                callback();
            }
            // cleanup
            this.appEventWiring = null;
        }
    }
    /**
     * Ability for classes to initialize app event handling early even before the app instance is ready during boot cycle avoiding boot race conditions
     * @param callback wire up any global event handling inside the callback
     */
    addEventWiring(callback) {
        if (this._appInstanceReady) {
            callback();
        }
        else {
            if (!this.appEventWiring) {
                this.appEventWiring = [];
            }
            this.appEventWiring.push(callback);
        }
    }
    _setLaunchedFn() {
        // console.log('NativeScriptGlobals launch fired!');
        this.launched = true;
        this.events.off('launch', this._setLaunched);
        this._setLaunched = null;
    }
}
export function installPolyfills(moduleName, exportNames) {
    if (global.__snapshot) {
        const loadedModule = global.loadModule(moduleName);
        exportNames.forEach((exportName) => (global[exportName] = loadedModule[exportName]));
    }
    else {
        exportNames.forEach((exportName) => registerOnGlobalContext(moduleName, exportName));
    }
}
export function initGlobal() {
    if (!global.NativeScriptHasInitGlobal) {
        global.NativeScriptHasInitGlobal = true;
        // init global state handler
        global.NativeScriptGlobals = new NativeScriptGlobalState();
        // ts-helpers
        // Required by V8 snapshot generator
        if (!global.__extends) {
            global.__extends = function (d, b) {
                for (const p in b) {
                    if (b.hasOwnProperty(p)) {
                        d[p] = b[p];
                    }
                }
                function __() {
                    this.constructor = d;
                }
                d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __());
            };
        }
        // Bind the tslib helpers to global scope.
        // This is needed when we don't use importHelpers, which
        // breaks extending native-classes
        for (const fnName of Object.getOwnPropertyNames(tslib)) {
            if (typeof tslib[fnName] !== 'function') {
                continue;
            }
            if (fnName in global) {
                // Don't override globals that are already defined (ex. __extends)
                continue;
            }
            global[fnName] = tslib[fnName];
        }
        // module helpers
        const modules = new Map();
        const modulesLoadedForUI = new Set();
        const defaultExtensionMap = {
            '.js': '.js',
            '.ts': '.js',
            '.kt': '.js',
            '.css': '.css',
            '.scss': '.css',
            '.less': '.css',
            '.sass': '.css',
            '.xml': '.xml',
        };
        // Cast to <any> because moduleResolvers is read-only in definitions
        global.moduleResolvers = [global.require];
        global.registerModule = function (name, loader) {
            modules.set(name, { loader, moduleId: name });
        };
        global._unregisterModule = function _unregisterModule(name) {
            modules.delete(name);
        };
        global._isModuleLoadedForUI = function _isModuleLoadedForUI(moduleName) {
            return modulesLoadedForUI.has(moduleName);
        };
        global.registerWebpackModules = function registerWebpackModules(context, extensionMap = {}) {
            context.keys().forEach((moduleId) => {
                const extDotIndex = moduleId.lastIndexOf('.');
                const base = moduleId.substr(0, extDotIndex);
                const originalExt = moduleId.substr(extDotIndex);
                const registerExt = extensionMap[originalExt] || defaultExtensionMap[originalExt] || originalExt;
                // We prefer source files for webpack scenarios before compilation leftovers,
                // e. g. if we get a .js and .ts for the same module, the .js is probably the compiled version of the .ts file,
                // so we register the .ts with higher priority, similar is the case with us preferring the .scss to .css
                const isSourceFile = originalExt !== registerExt;
                const registerName = base + registerExt;
                const registerWithName = (nickName) => {
                    modules.set(nickName, {
                        moduleId,
                        loader: () => {
                            return context(moduleId);
                        },
                    });
                };
                if (registerName.startsWith('./') && registerName.endsWith('.js')) {
                    const jsNickNames = [
                        // This is extremely short version like "main-page" that was promoted to be used with global.registerModule("module-name", loaderFunc);
                        registerName.substr(2, registerName.length - 5),
                        // This is for supporting module names like "./main/main-page"
                        registerName.substr(0, registerName.length - 3),
                        // This is for supporting module names like "main/main-page.js"
                        registerName.substr(2),
                    ];
                    jsNickNames.forEach((jsNickName) => {
                        if (isSourceFile || !global.moduleExists(jsNickName)) {
                            registerWithName(jsNickName);
                        }
                    });
                }
                else if (registerName.startsWith('./')) {
                    const moduleNickNames = [
                        // This is for supporting module names like "main/main-page.xml"
                        registerName.substr(2),
                    ];
                    moduleNickNames.forEach((moduleNickName) => {
                        if (!global.moduleExists(moduleNickName)) {
                            registerWithName(moduleNickName);
                        }
                    });
                }
                if (isSourceFile || !global.moduleExists(registerName)) {
                    registerWithName(registerName);
                }
            });
        };
        global.moduleExists = function moduleExists(name) {
            return modules.has(name);
        };
        global.loadModule = function loadModule(name, isUIModule = false) {
            const moduleInfo = modules.get(name);
            if (moduleInfo) {
                if (isUIModule) {
                    modulesLoadedForUI.add(moduleInfo.moduleId);
                }
                const result = moduleInfo.loader(name);
                if (result.enableAutoAccept) {
                    result.enableAutoAccept();
                }
                return result;
            }
            for (const resolver of global.moduleResolvers) {
                const result = resolver(name);
                if (result) {
                    modules.set(name, { moduleId: name, loader: () => result });
                    return result;
                }
            }
        };
        global.getRegisteredModules = function getRegisteredModules() {
            return Array.from(modules.keys());
        };
        /**
         * Polyfills
         */
        // This method iterates all the keys in the source exports object and copies them to the destination exports one.
        // Note: the method will not check for naming collisions and will override any already existing entries in the destination exports.
        global.moduleMerge = function (sourceExports, destExports) {
            for (const key in sourceExports) {
                destExports[key] = sourceExports[key];
            }
        };
        global.zonedCallback = function (callback) {
            if (global.zone) {
                // Zone v0.5.* style callback wrapping
                return global.zone.bind(callback);
            }
            if (global.Zone) {
                // Zone v0.6.* style callback wrapping
                return global.Zone.current.wrap(callback);
            }
            else {
                return callback;
            }
        };
        global.System = {
            import(path) {
                return new Promise((resolve, reject) => {
                    try {
                        resolve(global.require(path));
                    }
                    catch (e) {
                        reject(e);
                    }
                });
            },
        };
        // DOM api polyfills
        global.registerModule('timer', () => require('../timer'));
        installPolyfills('timer', ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval']);
        global.registerModule('animation', () => require('../animation-frame'));
        installPolyfills('animation', ['requestAnimationFrame', 'cancelAnimationFrame']);
        global.registerModule('media-query-list', () => require('../media-query-list'));
        installPolyfills('media-query-list', ['matchMedia', 'MediaQueryList']);
        global.registerModule('ui-dialogs', () => require('../ui/dialogs'));
        installPolyfills('ui-dialogs', ['alert', 'confirm', 'prompt', 'login', 'action']);
        global.registerModule('text', () => require('../text'));
        installPolyfills('text', ['TextDecoder', 'TextEncoder']);
        global.registerModule('xhr', () => require('../xhr'));
        installPolyfills('xhr', ['XMLHttpRequest', 'FormData', 'Blob', 'File', 'FileReader']);
        global.registerModule('fetch', () => require('../fetch'));
        installPolyfills('fetch', ['fetch', 'Headers', 'Request', 'Response']);
        // global.registerModule('abortcontroller', () => require('../abortcontroller'));
        // installPolyfills('abortcontroller', ['AbortController', 'AbortSignal']);
        // Custom decorators
        global.Deprecated = function (target, key, descriptor) {
            if (descriptor) {
                const originalMethod = descriptor.value;
                descriptor.value = function (...args) {
                    console.log(`${key.toString()} is deprecated`);
                    return originalMethod.apply(this, args);
                };
                return descriptor;
            }
            else {
                console.log(`${(target && target.name) || target} is deprecated`);
                return target;
            }
        };
        global.Experimental = function (target, key, descriptor) {
            if (descriptor) {
                const originalMethod = descriptor.value;
                descriptor.value = function (...args) {
                    console.log(`${key.toString()} is experimental`);
                    return originalMethod.apply(this, args);
                };
                return descriptor;
            }
            else {
                console.log(`${(target && target.name) || target} is experimental`);
                return target;
            }
        };
    }
}
function isTestingEnv() {
    return typeof jest !== 'undefined';
}
if (!global.NativeScriptHasInitGlobal && !isTestingEnv()) {
    initGlobal();
}
// ensure the Application instance is initialized before any other module imports it.
require('@nativescript/core/application');
//# sourceMappingURL=index.js.map