import { __decorate } from "tslib";
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable class-methods-use-this */
import { html } from 'lit';
import { Events as CoreEvents } from '@api-client/core/build/browser.js';
import { RenderableMixin } from '../mixins/RenderableMixin.js';
import { RouteMixin } from '../mixins/RouteMixin.js';
import { reactive } from '../lib/decorators.js';
import { Events } from '../events/Events.js';
import { EventTypes } from '../events/EventTypes.js';
import { ConfigBroadcastChannel } from '../lib/config/Config.js';
import supportedPlatform from '../lib/SupportedPlatform.js';
import { ButtonType } from '../ui/button/UiButton.js';
import { SnackNotifications } from '../ui/notification/SnackNotifications.js';
import '../define/ui/ui-button.js';
/**
 * A base class for pages build outside the LitElement. It uses `lit-html`
 * as the template renderer.
 *
 * The implementation (extending this class) should override the `pageTemplate()`
 * function that returns the `TemplateResult` from the `lit-html` library.
 *
 * To reflect the changed state call the `render()` function. The function schedules
 * a micro task (through `requestAnimationFrame`) to call the render function on the template.
 *
 * Use the `@reactive()` decorator from `src/lib/decorators.js` to mark a property as reactive,
 * meaning, when the property change it calls the `render()` function.
 */
export class ApplicationScreen extends RouteMixin(RenderableMixin(EventTarget)) {
    /**
     * Checks whether the store is in the single user mode.
     * It also returns true when the store info is not set (before the backend was initialized).
     */
    get isSingleUser() {
        const { storeInfo } = this;
        if (!storeInfo) {
            return true;
        }
        return storeInfo.mode === 'single-user';
    }
    constructor(init) {
        super();
        this.eventTarget = window;
        /**
         * True when the app should render mobile friendly view.
         */
        this.isMobile = false;
        /**
         * The loading state information.
         */
        this.loadingStatus = 'Initializing the application...';
        /**
         * A flag telling the application screen that the logic is initialized.
         *
         * The page can request different initialization logics. When the logic is
         * loaded the flag is set to true.
         */
        this.initialized = false;
        /**
         * True when the user meta is being loaded.
         */
        this.loadingUser = false;
        this.authenticated = false;
        /**
         * Detected system theme.
         */
        this.systemTheme = 'light';
        /**
         * The theme to use as selected by the user.
         * Default to system default.
         */
        this.userTheme = 'default';
        /**
         * Application configuration broadcast channel.
         */
        this.configBroadcast = new BroadcastChannel(ConfigBroadcastChannel);
        this.bindings = {
            config: init.config,
            store: init.store,
        };
        window.onunhandledrejection = this.unhandledRejectionHandler.bind(this);
        this.initMediaQueries();
        this.initStoreChange();
        this.configBroadcast.addEventListener('message', this.handleConfigBroadcast.bind(this));
        // this will use the system-default theme until config is read.
        this.themeActiveCallback();
    }
    /**
     * Initializes configuration that is relevant to all application windows.
     */
    async initializeConfig() {
        const { config } = this.bindings;
        const theme = await config.local.get('theme');
        if (theme) {
            this.userTheme = theme;
        }
        this.themeActiveCallback();
    }
    /**
     * Initializes media queries and observers.
     */
    initMediaQueries() {
        const mql = window.matchMedia('(max-width: 600px)');
        this.isMobile = mql.matches;
        mql.addEventListener('change', (e) => {
            this.isMobile = e.matches;
        });
        const dark = window.matchMedia('(prefers-color-scheme: dark)');
        if (dark.matches) {
            this.systemTheme = 'dark';
        }
        dark.addEventListener('change', (e) => {
            this.systemTheme = e.matches ? 'dark' : 'light';
            this.themeActiveCallback();
        });
    }
    themeActiveCallback() {
        const { systemTheme, userTheme } = this;
        let theme;
        if (userTheme === 'default') {
            theme = systemTheme;
        }
        else {
            theme = userTheme;
        }
        this.activateTheme(theme);
    }
    activateTheme(type) {
        const root = document.querySelector('html');
        if (!root) {
            return;
        }
        const { classList } = root;
        // clear all themes
        classList.forEach((value) => {
            if (value.startsWith('theme-')) {
                classList.remove(value);
            }
        });
        const name = `theme-${type}`;
        classList.add(name);
    }
    async isPlatformSupported() {
        const result = await supportedPlatform();
        if (!result) {
            this.page = 'unsupported-platform';
        }
        return result;
    }
    initStoreChange() {
        window.addEventListener(EventTypes.Config.Environment.State.defaultChange, (e) => {
            const event = e;
            const id = event.detail.id;
            this.storeChanged(id);
        });
    }
    /**
     * Creates a modal dialog with the error details.
     * @param message The message to render
     */
    reportCriticalError(message) {
        try {
            const dialog = document.createElement('dialog');
            const form = document.createElement('form');
            const title = document.createElement('h2');
            const content = document.createElement('div');
            const messageContainer = document.createElement('p');
            const buttons = document.createElement('div');
            const button = document.createElement('ui-button');
            dialog.classList.add('error-dialog');
            form.method = 'method';
            title.innerText = `An error ocurred`;
            messageContainer.classList.add('body-medium');
            messageContainer.textContent = message;
            content.appendChild(messageContainer);
            button.textContent = 'Dismiss';
            button.type = ButtonType.filled;
            button.value = 'submit';
            button.submit = true;
            buttons.classList.add('dialog-buttons');
            buttons.appendChild(button);
            form.appendChild(title);
            form.appendChild(content);
            form.appendChild(buttons);
            dialog.appendChild(form);
            document.body.appendChild(dialog);
            dialog.showModal();
            form.addEventListener('submit', (e) => {
                e.preventDefault();
                dialog.close();
            });
            dialog.addEventListener('close', () => {
                dialog.parentElement?.removeChild(dialog);
            });
        }
        catch (e) {
            /* eslint-disable-next-line no-console */
            console.error(e);
        }
    }
    unhandledRejectionHandler(e) {
        /* eslint-disable-next-line no-console */
        console.error(e);
        this.reportCriticalError(e.reason);
    }
    handleConfigBroadcast(e) {
        const event = e.data;
        const { path } = event;
        if (path === 'local.set') {
            this.handleLocalConfigUpdated(event.key, event.value);
        }
        else if (path === 'environment.setDefault') {
            this.handleDefaultEnvironmentSet(event.env);
        }
        else if (path === 'environment.add') {
            this.handleEnvironmentAdd(event.env, event.init);
        }
    }
    handleLocalConfigUpdated(key, value) {
        if (key === 'theme') {
            this.userTheme = value;
            this.themeActiveCallback();
        }
    }
    handleDefaultEnvironmentSet(env) {
        const { initialized, bindings } = this;
        this.env = env;
        bindings.store.global.set(env);
        this.resolvePending();
        if (initialized) {
            this.storeChanged(env.key);
        }
    }
    handleEnvironmentAdd(env, init = {}) {
        if (!init.asDefault) {
            return;
        }
        this.handleDefaultEnvironmentSet(env);
    }
    /**
     * Called from the initialize store init flow when the user is not authenticated
     * and the auth screen is rendered.
     */
    async handleAuthenticate() {
        const { store } = this.bindings;
        await store.auth.authenticate({ updateEnvironment: true });
        this.authenticated = true;
        this.resolvePending();
    }
    handleOpenStoreConfig() {
        Events.Navigation.Store.config();
    }
    /**
     * Initializes the flow where an environment is required.
     * This checks for the default environment.
     * When it doesn't exists it asks the user to pick existing environment
     * or to create one.
     * When a default environment exists, it performs authentication
     * when necessary.
     *
     * Eventually it returns the HTTP store with an authenticated environment.
     *
     * @param defaultFlows When set to `true` is renders default pages for env selector
     * or when to authenticate. When false it throws errors instead. Default to `true`.
     */
    async initializeStore(defaultFlows = true) {
        const { store, config } = this.bindings;
        let env;
        try {
            env = await config.environment.read();
        }
        catch (e) {
            if (!defaultFlows) {
                throw e;
            }
        }
        if (!env || !env.location) {
            await this.enterEnvironmentMissingFlow();
        }
        if (!env || !env.location) {
            return;
        }
        this.env = env;
        await store.global.set(env);
        try {
            this.storeInfo = await store.environment.readStoreInfo(env.location);
        }
        catch (e) {
            if (!defaultFlows) {
                throw e;
            }
            await this.enterStoreUnavailableFlow();
        }
        const authStatus = await store.auth.isAuthenticated();
        if (!authStatus) {
            if (!defaultFlows) {
                throw new Error(`Not authenticated.`);
            }
            await this.enterAuthenticationRequiredFlow();
        }
        this.authenticated = true;
    }
    async loadUser() {
        this.loadingUser = true;
        try {
            this.user = await this.bindings.store.users.me();
        }
        catch (e) {
            CoreEvents.Telemetry.exception(`Loading user: ${e.message}`, undefined, this.eventTarget || window);
        }
        finally {
            this.loadingUser = false;
        }
    }
    enterEnvironmentMissingFlow() {
        return new Promise((resolve) => {
            this.pendingResolver = resolve;
            this.page = 'env-required';
        });
    }
    enterAuthenticationRequiredFlow() {
        return new Promise((resolve) => {
            this.pendingResolver = resolve;
            this.page = 'auth-required';
        });
    }
    /**
     * Enters into the flow where the application is unable to connect to the data store.
     * It asks user to retry or re-configure.
     *
     * Note, this is called from withing the `initializeStore()` method and this is expected to
     * eventually finish.
     */
    enterStoreUnavailableFlow() {
        return new Promise((resolve) => {
            this.pendingResolver = resolve;
            this.page = 'store-unavailable';
        });
    }
    async handleRetryStoreInfo() {
        const { bindings, env } = this;
        const { store } = bindings;
        if (!env) {
            // this should not happen as we enter this flow after the env is set.
            return;
        }
        try {
            this.storeInfo = await store.environment.readStoreInfo(env.location);
            this.resolvePending();
        }
        catch (_) {
            SnackNotifications.notify(`Unable to connect.`);
        }
    }
    resolvePending() {
        const { pendingResolver } = this;
        if (pendingResolver) {
            this.pendingResolver = undefined;
            pendingResolver();
        }
    }
    /**
     * @returns A template for the page loader
     */
    loaderTemplate() {
        return html `
    <div class="app-loader body-large">
      <p class="message">Preparing something spectacular</p>
      <p class="sub-message">${this.loadingStatus}</p>
    </div>
    `;
    }
    pageTemplate() {
        const { initialized } = this;
        if (!initialized) {
            if (this.page) {
                return html `
        ${this.renderInitFlowPage()}
        `;
            }
            return this.loaderTemplate();
        }
        return html ``;
    }
    headerTemplate() {
        return html `
    <header>Welcome</header>
    `;
    }
    mainTemplate() {
        return html `<main></main>`;
    }
    footerTemplate() {
        return html `<footer>Credits: Pawel Uchida-Psztyc</footer>`;
    }
    navigationTemplate() {
        return html `<nav></nav>`;
    }
    renderInitFlowPage() {
        switch (this.page) {
            case 'auth-required': return this.renderAuthRequired();
            case 'env-required': return this.renderEnvRequired();
            case 'unsupported-platform': return this.renderPlatformNotSupported();
            case 'store-unavailable': return this.renderStoreUnavailable();
            default: return html `<p class="general-error">Unknown state. Did you set the <i>initialized</i> flag?</p>`;
        }
    }
    /**
     * Renders a full page overlay with the authentication required message.
     */
    renderAuthRequired() {
        return html `
    <div class="auth-required-screen">
      <h1 class="display-large">Authentication required</h1>
      <p class="body-large">You are not authenticated. To continue, please, authenticate your account.</p>
      <ui-button type="tonal" @click="${this.handleAuthenticate}">Authenticate</ui-button>
    </div>
    `;
    }
    renderEnvRequired() {
        return html `
    <div class="auth-required-screen">
      <h1 class="display-large">Environment configuration missing</h1>
      <p class="body-large">
        The application is unable to determine which environment to use.
        Open the <b>store configuration</b> screen to configure the connection.
      </p>
      <ui-button type="tonal" @click="${this.handleOpenStoreConfig}">Configure store</ui-button>
    </div>
    `;
    }
    renderPlatformNotSupported() {
        return html `
    <div class="auth-required-screen">
      <h1 class="display-large">Unsupported platform</h1>
      <p class="body-large">
        It looks like your browser does not support one of the crucial features needed to run
        this application. Accept our apologies, we work hard to support as many browsers as possible. 
      </p>
    </div>
    `;
    }
    renderStoreUnavailable() {
        return html `
    <div class="auth-required-screen">
      <h1 class="display-large">Unable to connect</h1>
      <p class="body-large">
        It looks like you or the data store are offline. The application is unable to continue
        without a connection to the backend.
      </p>
      <div class="actions">
        <ui-button type="tonal" @click="${this.handleRetryStoreInfo}">Retry</ui-button>
        <ui-button type="outlined" @click="${this.handleOpenStoreConfig}">Configure store</ui-button>
      </div>
    </div>
    `;
    }
}
__decorate([
    reactive()
], ApplicationScreen.prototype, "eventTarget", void 0);
__decorate([
    reactive()
], ApplicationScreen.prototype, "isMobile", void 0);
__decorate([
    reactive()
], ApplicationScreen.prototype, "loadingStatus", void 0);
__decorate([
    reactive()
], ApplicationScreen.prototype, "initialized", void 0);
__decorate([
    reactive()
], ApplicationScreen.prototype, "page", void 0);
__decorate([
    reactive()
], ApplicationScreen.prototype, "user", void 0);
__decorate([
    reactive()
], ApplicationScreen.prototype, "loadingUser", void 0);
__decorate([
    reactive()
], ApplicationScreen.prototype, "authenticated", void 0);
//# sourceMappingURL=ApplicationScreen.js.map