import { CoreTypes } from '../core-types';
import { CSSUtils } from '../css/system-classes';
import { Device, Screen } from '../platform';
import { profile } from '../profiling';
import { Trace } from '../trace';
import { clearResolverCache, prepareAppForModuleResolver, _setResolver } from '../module-name-resolver/helpers';
import { Builder } from '../ui/builder';
import * as bindableResources from '../ui/core/bindable/bindable-resources';
import { readyInitAccessibilityCssHelper, readyInitFontScale } from '../accessibility/accessibility-common';
import { getAppMainEntry, isAppInBackground, setAppInBackground, setAppMainEntry } from './helpers-common';
import { getNativeScriptGlobals } from '../globals/global-utils';
import { SDK_VERSION } from '../utils/constants';
// prettier-ignore
const ORIENTATION_CSS_CLASSES = [
    `${CSSUtils.CLASS_PREFIX}${CoreTypes.DeviceOrientation.portrait}`,
    `${CSSUtils.CLASS_PREFIX}${CoreTypes.DeviceOrientation.landscape}`,
    `${CSSUtils.CLASS_PREFIX}${CoreTypes.DeviceOrientation.unknown}`,
];
// prettier-ignore
const SYSTEM_APPEARANCE_CSS_CLASSES = [
    `${CSSUtils.CLASS_PREFIX}${CoreTypes.SystemAppearance.light}`,
    `${CSSUtils.CLASS_PREFIX}${CoreTypes.SystemAppearance.dark}`,
];
// prettier-ignore
const LAYOUT_DIRECTION_CSS_CLASSES = [
    `${CSSUtils.CLASS_PREFIX}${CoreTypes.LayoutDirection.ltr}`,
    `${CSSUtils.CLASS_PREFIX}${CoreTypes.LayoutDirection.rtl}`,
];
// SDK Version CSS classes
let sdkVersionClasses = [];
export function initializeSdkVersionClass(rootView) {
    const majorVersion = Math.floor(SDK_VERSION);
    sdkVersionClasses = [];
    let platformPrefix = '';
    if (__APPLE__) {
        platformPrefix = __VISIONOS__ ? 'ns-visionos' : 'ns-ios';
    }
    else if (__ANDROID__) {
        platformPrefix = 'ns-android';
    }
    if (platformPrefix) {
        // Add exact version class (e.g., .ns-ios-26 or .ns-android-36)
        // this acts like 'gte' for that major version range
        // e.g., if user wants iOS 27, they can add .ns-ios-27 specifiers
        sdkVersionClasses.push(`${platformPrefix}-${majorVersion}`);
    }
    // Apply the SDK version classes to root views
    applySdkVersionClass(rootView);
}
function applySdkVersionClass(rootView) {
    if (!sdkVersionClasses.length) {
        return;
    }
    if (!rootView) {
        return;
    }
    // Batch apply all SDK version classes to root view for better performance
    const classesToAdd = sdkVersionClasses.filter((className) => !rootView.cssClasses.has(className));
    classesToAdd.forEach((className) => rootView.cssClasses.add(className));
    // Apply to modal views only if there are any
    const rootModalViews = rootView._getRootModalViews();
    if (rootModalViews.length > 0) {
        rootModalViews.forEach((rootModalView) => {
            const modalClassesToAdd = sdkVersionClasses.filter((className) => !rootModalView.cssClasses.has(className));
            modalClassesToAdd.forEach((className) => rootModalView.cssClasses.add(className));
        });
    }
}
const globalEvents = getNativeScriptGlobals().events;
// Scene lifecycle event names
export const SceneEvents = {
    sceneWillConnect: 'sceneWillConnect',
    sceneDidActivate: 'sceneDidActivate',
    sceneWillResignActive: 'sceneWillResignActive',
    sceneWillEnterForeground: 'sceneWillEnterForeground',
    sceneDidEnterBackground: 'sceneDidEnterBackground',
    sceneDidDisconnect: 'sceneDidDisconnect',
    sceneContentSetup: 'sceneContentSetup',
};
export class ApplicationCommon {
    /**
     * @internal - should not be constructed by the user.
     */
    constructor() {
        this.launchEvent = 'launch';
        this.suspendEvent = 'suspend';
        this.displayedEvent = 'displayed';
        this.backgroundEvent = 'background';
        this.foregroundEvent = 'foreground';
        this.resumeEvent = 'resume';
        this.exitEvent = 'exit';
        this.lowMemoryEvent = 'lowMemory';
        this.uncaughtErrorEvent = 'uncaughtError';
        this.discardedErrorEvent = 'discardedError';
        this.orientationChangedEvent = 'orientationChanged';
        this.systemAppearanceChangedEvent = 'systemAppearanceChanged';
        this.layoutDirectionChangedEvent = 'layoutDirectionChanged';
        this.fontScaleChangedEvent = 'fontScaleChanged';
        this.livesyncEvent = 'livesync';
        this.loadAppCssEvent = 'loadAppCss';
        this.cssChangedEvent = 'cssChanged';
        this.initRootViewEvent = 'initRootView';
        // Application events go through the global events.
        this.on = globalEvents.on.bind(globalEvents);
        this.once = globalEvents.once.bind(globalEvents);
        this.off = globalEvents.off.bind(globalEvents);
        this.notify = globalEvents.notify.bind(globalEvents);
        this.hasListeners = globalEvents.hasListeners.bind(globalEvents);
        this._inBackground = false;
        this._suspended = false;
        this._cssFile = './app.css';
        this.started = false;
        /**
         * Boolean to enable/disable systemAppearanceChanged
         */
        this.autoSystemAppearanceChanged = true;
        getNativeScriptGlobals().appInstanceReady = true;
        global.__onUncaughtError = (error) => {
            this.notify({
                eventName: this.uncaughtErrorEvent,
                object: this,
                android: error,
                ios: error,
                error: error,
            });
        };
        global.__onDiscardedError = (error) => {
            this.notify({
                eventName: this.discardedErrorEvent,
                object: this,
                error: error,
            });
        };
        global.__onLiveSync = (context) => {
            if (this.suspended) {
                return;
            }
            const rootView = this.getRootView();
            this.livesync(rootView, context);
        };
    }
    /**
     * @internal
     */
    livesync(rootView, context) {
        this.notify({ eventName: this.livesyncEvent, object: this });
        const liveSyncCore = global.__onLiveSyncCore;
        let reapplyAppStyles = false;
        // ModuleContext is available only for Hot Module Replacement
        if (context && context.path) {
            const styleExtensions = ['css', 'scss'];
            const appStylesFullFileName = this.getCssFileName();
            const appStylesFileName = appStylesFullFileName.substring(0, appStylesFullFileName.lastIndexOf('.') + 1);
            reapplyAppStyles = styleExtensions.some((ext) => context.path === appStylesFileName.concat(ext));
        }
        // Handle application styles
        if (rootView && reapplyAppStyles) {
            rootView._onCssStateChange();
        }
        else if (liveSyncCore) {
            liveSyncCore(context);
        }
    }
    /**
     * Applies the the `newCssClass` to the `rootView` and removes all other css classes from `cssClasses`
     * previously applied to the `rootView`.
     * @param rootView
     * @param cssClasses
     * @param newCssClass
     * @param skipCssUpdate
     */
    applyCssClass(rootView, cssClasses, newCssClass, skipCssUpdate = false) {
        if (!rootView.cssClasses.has(newCssClass)) {
            cssClasses.forEach((cssClass) => this.removeCssClass(rootView, cssClass));
            this.addCssClass(rootView, newCssClass);
            this.increaseStyleScopeApplicationCssSelectorVersion(rootView);
            if (!skipCssUpdate) {
                rootView._onCssStateChange();
            }
            if (Trace.isEnabled()) {
                const rootCssClasses = Array.from(rootView.cssClasses);
                Trace.write(`Applying root css class: ${newCssClass}. rootView css classes: ${rootCssClasses.join(' ')}`, Trace.categories.Style);
            }
        }
    }
    addCssClass(rootView, cssClass) {
        CSSUtils.pushToSystemCssClasses(cssClass);
        rootView.cssClasses.add(cssClass);
    }
    removeCssClass(rootView, cssClass) {
        CSSUtils.removeSystemCssClass(cssClass);
        rootView.cssClasses.delete(cssClass);
    }
    increaseStyleScopeApplicationCssSelectorVersion(rootView) {
        const styleScope = rootView._styleScope ?? rootView?.currentPage?._styleScope;
        if (styleScope) {
            styleScope._increaseApplicationCssSelectorVersion();
        }
    }
    setRootViewCSSClasses(rootView) {
        const platform = Device.os.toLowerCase();
        const deviceType = Device.deviceType.toLowerCase();
        const orientation = this.orientation();
        const systemAppearance = this.systemAppearance();
        const layoutDirection = this.layoutDirection();
        if (platform) {
            CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${platform}`);
        }
        if (deviceType) {
            CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${deviceType}`);
        }
        if (orientation) {
            CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${orientation}`);
        }
        if (systemAppearance) {
            CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${systemAppearance}`);
        }
        if (layoutDirection) {
            CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${layoutDirection}`);
        }
        rootView.cssClasses.add(CSSUtils.ROOT_VIEW_CSS_CLASS);
        const rootViewCssClasses = CSSUtils.getSystemCssClasses();
        rootViewCssClasses.forEach((c) => rootView.cssClasses.add(c));
        this.increaseStyleScopeApplicationCssSelectorVersion(rootView);
        rootView._onCssStateChange();
        if (Trace.isEnabled()) {
            const rootCssClasses = Array.from(rootView.cssClasses);
            Trace.write(`Setting root css classes: ${rootCssClasses.join(' ')}`, Trace.categories.Style);
        }
    }
    /**
     * iOS Only
     * Dynamically change the preferred frame rate
     * For devices (iOS 15+) which support min/max/preferred frame rate you can specify ranges
     * For devices (iOS < 15), you can specify the max frame rate
     * see: https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro
     * To use, ensure your Info.plist has:
     * ```xml
     *   <key>CADisableMinimumFrameDurationOnPhone</key>
     *   <true/>
     * ```
     * @param options { min?: number; max?: number; preferred?: number }
     */
    setMaxRefreshRate(options) {
        // implement in platform specific files (iOS only for now)
    }
    /**
     * @returns The main entry of the application
     */
    getMainEntry() {
        return getAppMainEntry();
    }
    notifyLaunch(additionalLanchEventData) {
        const launchArgs = {
            eventName: this.launchEvent,
            object: this,
            ios: this.ios,
            android: this.android,
            ...additionalLanchEventData,
        };
        this.notify(launchArgs);
        this.loadAppCss();
        return launchArgs.root;
    }
    createRootView(view, fireLaunchEvent = false, additionalLanchEventData) {
        let rootView = view;
        if (!rootView) {
            if (fireLaunchEvent) {
                rootView = this.notifyLaunch(additionalLanchEventData);
                // useful for integrations that would like to set rootView asynchronously after app launch
                if (rootView === null) {
                    return null;
                }
            }
            if (!rootView) {
                // try to navigate to the mainEntry (if specified)
                if (!getAppMainEntry()) {
                    throw new Error('Main entry is missing. App cannot be started. Verify app bootstrap.');
                }
                rootView = Builder.createViewFromEntry(getAppMainEntry());
            }
        }
        return rootView;
    }
    getRootView() {
        throw new Error('getRootView() Not implemented.');
    }
    resetRootView(entry) {
        setAppMainEntry(typeof entry === 'string' ? { moduleName: entry } : entry);
        // rest of implementation is platform specific
    }
    initRootView(rootView) {
        this.setRootViewCSSClasses(rootView);
        readyInitAccessibilityCssHelper();
        readyInitFontScale();
        this.notify({ eventName: this.initRootViewEvent, rootView });
    }
    /**
     * Get application level static resources.
     */
    getResources() {
        return bindableResources.get();
    }
    /**
     * Set application level static resources.
     */
    setResources(res) {
        bindableResources.set(res);
    }
    /**
     * Sets css file name for the application.
     */
    setCssFileName(cssFileName) {
        this._cssFile = cssFileName;
        this.notify({
            eventName: this.cssChangedEvent,
            object: this,
            cssFile: cssFileName,
        });
    }
    /**
     * Gets css file name for the application.
     */
    getCssFileName() {
        return this._cssFile;
    }
    /**
     * Loads immediately the app.css.
     * By default the app.css file is loaded shortly after "loaded".
     * For the Android snapshot the CSS can be parsed during the snapshot generation,
     * as the CSS does not depend on runtime APIs, and loadAppCss will be called explicitly.
     */
    loadAppCss() {
        try {
            this.notify({
                eventName: this.loadAppCssEvent,
                object: this,
                ios: this.ios,
                android: this.android,
                cssFile: this.getCssFileName(),
            });
        }
        catch (e) {
            if (Trace.isEnabled()) {
                Trace.write(`The app CSS file ${this.getCssFileName()} couldn't be loaded!`, Trace.categories.Style, Trace.messageType.warn);
            }
        }
    }
    addCss(cssText, attributeScoped) {
        this.notify({
            eventName: this.cssChangedEvent,
            object: this,
            cssText: cssText,
        });
        if (!attributeScoped) {
            const rootView = this.getRootView();
            if (rootView) {
                rootView._onCssStateChange();
            }
        }
    }
    run(entry) {
        throw new Error('run() Not implemented.');
    }
    getOrientation() {
        // override in platform specific Application class
        throw new Error('getOrientation() not implemented');
    }
    setOrientation(value) {
        if (this._orientation === value) {
            return;
        }
        this._orientation = value;
        // Update metrics early enough regardless of the existence of root view
        // Also, CSS will use the correct size values during update trigger
        Screen.mainScreen._updateMetrics();
        this.orientationChanged(this.getRootView(), value);
        this.notify({
            eventName: this.orientationChangedEvent,
            android: this.android,
            ios: this.ios,
            newValue: value,
            object: this,
        });
    }
    orientation() {
        return (this._orientation ?? (this._orientation = this.getOrientation()));
    }
    orientationChanged(rootView, newOrientation) {
        if (!rootView) {
            return;
        }
        const newOrientationCssClass = `${CSSUtils.CLASS_PREFIX}${newOrientation}`;
        this.applyCssClass(rootView, ORIENTATION_CSS_CLASSES, newOrientationCssClass, true);
        const rootModalViews = rootView._getRootModalViews();
        rootModalViews.forEach((rootModalView) => {
            this.applyCssClass(rootModalView, ORIENTATION_CSS_CLASSES, newOrientationCssClass, true);
            // Trigger state change for root modal view classes and media queries
            rootModalView._onCssStateChange();
        });
        // Trigger state change for root view classes and media queries
        rootView._onCssStateChange();
    }
    getNativeApplication() {
        // override in platform specific Application class
        throw new Error('getNativeApplication() not implemented');
    }
    hasLaunched() {
        return getNativeScriptGlobals().launched;
    }
    getSystemAppearance() {
        // override in platform specific Application class
        throw new Error('getSystemAppearance() not implemented');
    }
    setSystemAppearance(value) {
        if (this._systemAppearance === value) {
            return;
        }
        this._systemAppearance = value;
        this.systemAppearanceChanged(this.getRootView(), value);
        this.notify({
            eventName: this.systemAppearanceChangedEvent,
            android: this.android,
            ios: this.ios,
            newValue: value,
            object: this,
        });
    }
    systemAppearance() {
        // return cached value, or get it from the platform specific override
        return (this._systemAppearance ?? (this._systemAppearance = this.getSystemAppearance()));
    }
    /**
     * enable/disable systemAppearanceChanged
     */
    setAutoSystemAppearanceChanged(value) {
        this.autoSystemAppearanceChanged = value;
    }
    /**
     * Updates root view classes including those of modals
     * @param rootView the root view
     * @param newSystemAppearance the new appearance change
     */
    systemAppearanceChanged(rootView, newSystemAppearance) {
        if (!rootView || !this.autoSystemAppearanceChanged) {
            return;
        }
        const newSystemAppearanceCssClass = `${CSSUtils.CLASS_PREFIX}${newSystemAppearance}`;
        this.applyCssClass(rootView, SYSTEM_APPEARANCE_CSS_CLASSES, newSystemAppearanceCssClass, true);
        const rootModalViews = rootView._getRootModalViews();
        rootModalViews.forEach((rootModalView) => {
            this.applyCssClass(rootModalView, SYSTEM_APPEARANCE_CSS_CLASSES, newSystemAppearanceCssClass, true);
            // Trigger state change for root modal view classes and media queries
            rootModalView._onCssStateChange();
        });
        // Trigger state change for root view classes and media queries
        rootView._onCssStateChange();
    }
    getLayoutDirection() {
        // override in platform specific Application class
        throw new Error('getLayoutDirection() not implemented');
    }
    setLayoutDirection(value) {
        if (this._layoutDirection === value) {
            return;
        }
        this._layoutDirection = value;
        this.layoutDirectionChanged(this.getRootView(), value);
        this.notify({
            eventName: this.layoutDirectionChangedEvent,
            android: this.android,
            ios: this.ios,
            newValue: value,
            object: this,
        });
    }
    layoutDirection() {
        // return cached value, or get it from the platform specific override
        return (this._layoutDirection ?? (this._layoutDirection = this.getLayoutDirection()));
    }
    /**
     * Updates root view classes including those of modals
     * @param rootView the root view
     * @param newLayoutDirection the new layout direction change
     */
    layoutDirectionChanged(rootView, newLayoutDirection) {
        if (!rootView) {
            return;
        }
        const newLayoutDirectionCssClass = `${CSSUtils.CLASS_PREFIX}${newLayoutDirection}`;
        this.applyCssClass(rootView, LAYOUT_DIRECTION_CSS_CLASSES, newLayoutDirectionCssClass, true);
        const rootModalViews = rootView._getRootModalViews();
        rootModalViews.forEach((rootModalView) => {
            this.applyCssClass(rootModalView, LAYOUT_DIRECTION_CSS_CLASSES, newLayoutDirectionCssClass, true);
            // Trigger state change for root modal view classes and media queries
            rootModalView._onCssStateChange();
        });
        // Trigger state change for root view classes and media queries
        rootView._onCssStateChange();
    }
    get inBackground() {
        return isAppInBackground();
    }
    setInBackground(value, additonalData) {
        setAppInBackground(value);
        this.notify({
            eventName: value ? this.backgroundEvent : this.foregroundEvent,
            object: this,
            ios: this.ios,
            ...additonalData,
        });
    }
    get suspended() {
        return this._suspended;
    }
    setSuspended(value, additonalData) {
        this._suspended = value;
        this.notify({
            eventName: value ? this.suspendEvent : this.resumeEvent,
            object: this,
            ios: this.ios,
            android: this.android,
            ...additonalData,
        });
    }
    get android() {
        return undefined;
    }
    get ios() {
        return undefined;
    }
    get AndroidApplication() {
        return this.android;
    }
    get iOSApplication() {
        return this.ios;
    }
}
// Expose statically for backwards compat on AndroidApplication.on etc.
/**
 * @deprecated Use `Application.android.on()` instead.
 */
ApplicationCommon.on = globalEvents.on.bind(globalEvents);
/**
 * @deprecated Use `Application.android.once()` instead.
 */
ApplicationCommon.once = globalEvents.once.bind(globalEvents);
/**
 * @deprecated Use `Application.android.off()` instead.
 */
ApplicationCommon.off = globalEvents.off.bind(globalEvents);
/**
 * @deprecated Use `Application.android.notify()` instead.
 */
ApplicationCommon.notify = globalEvents.notify.bind(globalEvents);
/**
 * @deprecated Use `Application.android.hasListeners()` instead.
 */
ApplicationCommon.hasListeners = globalEvents.hasListeners.bind(globalEvents);
__decorate([
    profile,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Function)
], ApplicationCommon.prototype, "notifyLaunch", null);
__decorate([
    profile,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Function, Object, Object]),
    __metadata("design:returntype", void 0)
], ApplicationCommon.prototype, "createRootView", null);
prepareAppForModuleResolver(() => {
    ApplicationCommon.on('livesync', (args) => clearResolverCache());
    ApplicationCommon.on('orientationChanged', (args) => {
        _setResolver(undefined);
    });
});
//# sourceMappingURL=application-common.js.map