// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { loadTimeData } from '//resources/js/load_time_data.js';
import { assert, assertNotReachedCase } from 'chrome://resources/js/assert.js';
import { getRequiredElement } from 'chrome://resources/js/util.js';
import { HelpCenterTopic, PanelStateKind, PrepareForClientResult, ProfileReadyState, WebUiState } from './glic.mojom-webui.js';
import { WebClientState } from './glic_api_impl/host/glic_api_host.js';
import { isFullWebView } from './shared/web_view_type.js';
import { WebviewController, WebviewPersistentState } from './webview.js';
// Time to wait before showing loading panel.
const kPreHoldLoadingTimeMs = loadTimeData.getInteger('preLoadingTimeMs');
// Minimum time to hold "loading" panel visible.
const kMinHoldLoadingTimeMs = loadTimeData.getInteger('minLoadingTimeMs');
// Maximum time to wait for load before showing error panel.
const kMaxWaitTimeMs = loadTimeData.getInteger('maxLoadingTimeMs');
// Whether to enable the debug button on the error panel. Can be enabled with
// the --enable-features=GlicDebugWebview command-line flag.
const kEnableDebug = loadTimeData.getBoolean('enableDebug');
const kShowErrorAllowed = loadTimeData.getBoolean('showErrorAllowed');
// Whether additional web client unresponsiveness tracking metrics should be
// recorded.
const kEnableUnresponsiveMetrics = loadTimeData.getBoolean('enableWebClientUnresponsiveMetrics');
const $ = new Proxy({}, {
    get(_target, prop) {
        return getRequiredElement(prop);
    },
});
// Web client unresponsiveness state tracking values for metrics reporting.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(WebClientUnresponsiveState)
export var WebClientUnresponsiveState;
(function (WebClientUnresponsiveState) {
    WebClientUnresponsiveState[WebClientUnresponsiveState["ENTERED_FROM_WEBVIEW_EVENT"] = 0] = "ENTERED_FROM_WEBVIEW_EVENT";
    WebClientUnresponsiveState[WebClientUnresponsiveState["ENTERED_FROM_CUSTOM_HEARTBEAT"] = 1] = "ENTERED_FROM_CUSTOM_HEARTBEAT";
    WebClientUnresponsiveState[WebClientUnresponsiveState["ALREADY_ON_FROM_WEBVIEW_EVENT"] = 2] = "ALREADY_ON_FROM_WEBVIEW_EVENT";
    WebClientUnresponsiveState[WebClientUnresponsiveState["ALREADY_ON_FROM_CUSTOM_HEARTBEAT"] = 3] = "ALREADY_ON_FROM_CUSTOM_HEARTBEAT";
    WebClientUnresponsiveState[WebClientUnresponsiveState["EXITED"] = 4] = "EXITED";
    WebClientUnresponsiveState[WebClientUnresponsiveState["MAX_VALUE"] = 4] = "MAX_VALUE";
})(WebClientUnresponsiveState || (WebClientUnresponsiveState = {}));
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:WebClientUnresponsiveState)
// Enum for specific stages of loading the web client, reported if loading times
// out.
// LINT.IfChange(LoadingStage)
export var LoadingStage;
(function (LoadingStage) {
    LoadingStage[LoadingStage["NOT_LOADING"] = 0] = "NOT_LOADING";
    LoadingStage[LoadingStage["AWAITING_PROFILE_READY"] = 1] = "AWAITING_PROFILE_READY";
    LoadingStage[LoadingStage["AWAITING_COOKIE_SYNC"] = 2] = "AWAITING_COOKIE_SYNC";
    LoadingStage[LoadingStage["LOADING_WEB_CLIENT"] = 3] = "LOADING_WEB_CLIENT";
    LoadingStage[LoadingStage["AWAITING_NOTIFY_PANEL_WILL_OPEN"] = 4] = "AWAITING_NOTIFY_PANEL_WILL_OPEN";
    LoadingStage[LoadingStage["MAX_VALUE"] = 4] = "MAX_VALUE";
})(LoadingStage || (LoadingStage = {}));
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:LoadingStage,//tools/metrics/histograms/metadata/glic/histograms.xml:LoadingStage)
// Reasons for entering WebUiState.kError.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(PanelWebUiStateErrorReason)
export var WebUiErrorReason;
(function (WebUiErrorReason) {
    WebUiErrorReason[WebUiErrorReason["WEBVIEW_ERROR"] = 0] = "WEBVIEW_ERROR";
    WebUiErrorReason[WebUiErrorReason["LOAD_ERROR"] = 1] = "LOAD_ERROR";
    WebUiErrorReason[WebUiErrorReason["COOKIE_SYNC_ERROR"] = 2] = "COOKIE_SYNC_ERROR";
    WebUiErrorReason[WebUiErrorReason["TIMEOUT_NOTIFY_PANEL_WILL_OPEN"] = 3] = "TIMEOUT_NOTIFY_PANEL_WILL_OPEN";
    WebUiErrorReason[WebUiErrorReason["TIMEOUT_LOADING_CLIENT"] = 4] = "TIMEOUT_LOADING_CLIENT";
    WebUiErrorReason[WebUiErrorReason["TIMEOUT_WARMED"] = 5] = "TIMEOUT_WARMED";
    WebUiErrorReason[WebUiErrorReason["CLIENT_ERROR"] = 6] = "CLIENT_ERROR";
    WebUiErrorReason[WebUiErrorReason["CLOSE_DEBUG_VIEW"] = 7] = "CLOSE_DEBUG_VIEW";
    WebUiErrorReason[WebUiErrorReason["MAX_VALUE"] = 7] = "MAX_VALUE";
})(WebUiErrorReason || (WebUiErrorReason = {}));
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:PanelWebUiStateErrorReason)
export class GlicAppController {
    loadingTimer;
    isFreCompleted = loadTimeData.getBoolean('completedFre');
    // This is used to simulate no connection for tests.
    simulateNoConnection = loadTimeData.getBoolean('simulateNoConnection');
    guestResizeEnabled = false;
    // Present only when loading or after loading is finished. Removed on error.
    webview;
    webviewPersistentState = new WebviewPersistentState();
    profileReadyState = undefined;
    profileReadyInitialState = Promise.withResolvers();
    enteredUnresponsiveTimestampMs;
    // Loading stage, affects metrics only.
    loadingStage = LoadingStage.NOT_LOADING;
    loadingStageStartTimestampMs;
    panelStateKind = PanelStateKind.kHidden;
    state;
    // When entering loading state, this represents the earliest timestamp at
    // which the UI can transition to the ready state. This ensures that the
    // loading UI isn't just a brief flash on screen.
    earliestLoadingDismissTime;
    browserProxy;
    constructor(browserProxy) {
        this.browserProxy = browserProxy;
        window.addEventListener('online', () => {
            this.online();
        });
        window.addEventListener('offline', () => {
            if (!this.isOnline()) {
                this.offline();
            }
        });
        // Programmatically redirect focus to the embedded guest
        // webview if focus gets trapped on the orchestrator container
        // (document.body) while the guest panel is visible.
        window.addEventListener('focus', () => {
            const isGuestVisible = !$.guestPanel.hidden;
            const isFocusTrapped = document.activeElement === document.body || !document.activeElement;
            if (isGuestVisible && isFocusTrapped) {
                this.webview?.focus();
            }
        });
        if (this.isOnline()) {
            this.setState(WebUiState.kBeginLoad);
        }
        else {
            this.setState(WebUiState.kOffline);
        }
        $.profilePickerButton.addEventListener('click', () => {
            this.openProfilePicker();
        });
        $.reload.addEventListener('click', () => {
            this.reload();
        });
        $.disabledByAdminCloseButton.addEventListener('click', () => {
            this.browserProxy.pageHandler.closePanel();
        });
        $.disabledByAdminPanel.querySelector('a')?.addEventListener('click', () => {
            this.openDisabledByAdminLink();
        });
        $.locationMismatchHelpButton.addEventListener('click', () => {
            this.browserProxy.pageHandler.openHelpCenterTopicAndClosePanel(HelpCenterTopic.kLocationMismatch);
        });
        $.ineligibleAccountHelpButton.addEventListener('click', () => {
            this.browserProxy.pageHandler.openHelpCenterTopicAndClosePanel(HelpCenterTopic.kIneligibleAccount);
        });
        $.signInButton.addEventListener('click', () => {
            this.signIn();
        });
        $.showError.addEventListener('click', () => {
            this.showPanel('guestPanel');
        });
        if (kShowErrorAllowed) {
            $.showError.hidden = false;
        }
        document.addEventListener('keydown', ev => {
            if (this.state !== WebUiState.kReady) {
                if (ev.code === 'Escape') {
                    ev.stopPropagation();
                    ev.preventDefault();
                    this.browserProxy.pageHandler.closePanel();
                }
            }
        });
        if (kEnableDebug) {
            window.addEventListener('load', () => {
                this.installDebugButton();
            });
        }
    }
    // WebviewDelegate implementation.
    webviewUnresponsive() {
        console.warn('webview unresponsive');
        this.trackUnresponsiveState(this.state === WebUiState.kUnresponsive ?
            WebClientUnresponsiveState.ALREADY_ON_FROM_WEBVIEW_EVENT :
            WebClientUnresponsiveState.ENTERED_FROM_WEBVIEW_EVENT);
        this.setState(WebUiState.kUnresponsive);
    }
    trackUnresponsiveState(newState) {
        if (!kEnableUnresponsiveMetrics) {
            return;
        }
        // Track and record unresponsive state duration.
        if (newState === WebClientUnresponsiveState.ENTERED_FROM_WEBVIEW_EVENT ||
            newState === WebClientUnresponsiveState.ENTERED_FROM_CUSTOM_HEARTBEAT) {
            // Entering an unresponsive state.
            this.enteredUnresponsiveTimestampMs = Date.now();
        }
        else if (newState === WebClientUnresponsiveState.EXITED) {
            // Existing an unresponsive state.
            if (this.enteredUnresponsiveTimestampMs !== undefined) {
                const unresponsiveDuration = Date.now() - this.enteredUnresponsiveTimestampMs;
                chrome.histograms.recordMediumTime('Glic.Host.WebClientUnresponsiveState.Duration', unresponsiveDuration);
                this.enteredUnresponsiveTimestampMs = undefined;
            }
            else {
                console.error('Unresponsive state exited without an entering timestamp');
            }
        }
        // Record unresponsive state detections and transitions.
        chrome.histograms.recordEnumerationValue('Glic.Host.WebClientUnresponsiveState', newState, WebClientUnresponsiveState.MAX_VALUE + 1);
    }
    webviewError(reason) {
        console.warn(`webview exit. reason: ${reason}`);
        this.setErrorState(WebUiErrorReason.WEBVIEW_ERROR);
    }
    webviewPageCommit(type) {
        switch (type) {
            case 'login':
                this.cancelTimeout();
                $.guestPanel.classList.toggle('show-header', true);
                this.showPanel('guestPanel');
                break;
            case 'guestError':
            case 'guestCaaError':
                this.setState(WebUiState.kGuestError);
                break;
            case 'regular':
                $.guestPanel.classList.toggle('show-header', false);
                if (this.state === WebUiState.kReady ||
                    this.state === WebUiState.kWarmed ||
                    this.state === WebUiState.kGuestError) {
                    this.setState(WebUiState.kBeginLoad);
                }
                break;
            case 'loadError':
                this.setErrorState(WebUiErrorReason.LOAD_ERROR);
                break;
            default:
                assertNotReachedCase(type);
        }
    }
    webviewDeniedByAdmin() {
        $.disabledByAdminPanel.classList.toggle('show-disabled-by-admin-link', true);
        this.setState(WebUiState.kDisabledByAdmin);
    }
    setState(newState) {
        if (this.state === newState) {
            return;
        }
        if (this.state) {
            this.states.get(this.state).onExit?.call(this);
        }
        this.state = newState;
        this.states.get(this.state).onEnter?.call(this);
        this.browserProxy.pageHandler.webUiStateChanged(this.state);
        this.browserProxy.pageHandler.enableDragResize(this.state === WebUiState.kReady && this.guestResizeEnabled);
    }
    setErrorState(reason) {
        // Only record the histogram if not already in the error state.
        if (this.state === WebUiState.kError) {
            return;
        }
        chrome.histograms.recordEnumerationValue('Glic.PanelWebUiState.Error', reason, WebUiErrorReason.MAX_VALUE + 1);
        if (!this.isFreCompleted) {
            chrome.histograms.recordEnumerationValue('Glic.Fre.PanelWebUiState.Error', reason, WebUiErrorReason.MAX_VALUE + 1);
        }
        this.setState(WebUiState.kError);
    }
    stateDescriptor() {
        return this.state !== undefined ? this.states.get(this.state) : undefined;
    }
    states = new Map([
        [
            WebUiState.kBeginLoad,
            { onEnter: this.beginLoad, onExit: this.cancelTimeout },
        ],
        [
            WebUiState.kShowLoading,
            { onEnter: this.showLoading, onExit: this.cancelTimeout },
        ],
        [
            WebUiState.kHoldLoading,
            { onEnter: this.holdLoading, onExit: this.cancelTimeout },
        ],
        [
            WebUiState.kFinishLoading,
            { onEnter: this.finishLoading, onExit: this.cancelTimeout },
        ],
        [
            WebUiState.kError,
            {
                reloadOnOpen: true,
                onEnter: () => {
                    // Keep the webview alive for debugging purposes.
                    this.setWebviewDormant();
                    this.showPanel('errorPanel');
                },
            },
        ],
        [
            WebUiState.kOffline,
            {
                onEnter: () => {
                    this.destroyWebview();
                    this.showPanel('offlinePanel');
                },
            },
        ],
        [
            WebUiState.kUnavailable,
            {
                reloadOnOpen: true,
                onEnter: () => {
                    this.destroyWebview();
                    this.showPanel('unavailablePanel');
                },
            },
        ],
        [
            WebUiState.kIneligibleAccount,
            {
                reloadOnOpen: true,
                onEnter: () => {
                    this.destroyWebview();
                    this.showPanel('ineligibleAccountPanel');
                },
            },
        ],
        [
            WebUiState.kDisabledByAdmin,
            {
                reloadOnOpen: true,
                onEnter: () => {
                    this.destroyWebview();
                    this.showPanel('disabledByAdminPanel');
                },
            },
        ],
        [
            WebUiState.kLocationMismatch,
            {
                reloadOnOpen: true,
                onEnter: () => {
                    this.destroyWebview();
                    this.showPanel('locationMismatchPanel');
                },
            },
        ],
        [
            WebUiState.kWarmed,
            {
                onEnter: () => {
                    $.guestPanel.classList.toggle('show-header', false);
                    this.showPanel('guestPanel');
                },
            },
        ],
        [
            WebUiState.kReady,
            {
                onEnter: () => {
                    this.trackLoadingStageEnd();
                    $.guestPanel.classList.toggle('show-header', false);
                    this.showPanel('guestPanel');
                },
            },
        ],
        [
            WebUiState.kUnresponsive,
            {
                reloadOnOpen: true,
                onEnter: () => {
                    $.unresponsiveOverlay.classList.toggle('hidden', false);
                },
                onExit: () => {
                    this.trackUnresponsiveState(WebClientUnresponsiveState.EXITED);
                    $.unresponsiveOverlay.classList.toggle('hidden', true);
                },
            },
        ],
        [
            WebUiState.kSignIn,
            {
                reloadOnOpen: true,
                onEnter: () => {
                    this.destroyWebview();
                    this.showPanel('signInPanel');
                },
            },
        ],
        [
            WebUiState.kGuestError,
            {
                reloadOnOpen: true,
                onEnter: () => {
                    $.guestPanel.classList.toggle('show-header', true);
                    this.showPanel('guestPanel');
                },
            },
        ],
    ]);
    cancelTimeout() {
        if (this.loadingTimer) {
            clearTimeout(this.loadingTimer);
            this.loadingTimer = undefined;
        }
    }
    beginLoad() {
        // Wait a moment before showing the loading panel.
        if (!loadTimeData.getBoolean('noLoader')) {
            this.loadingTimer = setTimeout(() => {
                this.setState(WebUiState.kShowLoading);
            }, kPreHoldLoadingTimeMs);
        }
        this.load();
    }
    trackLoadingStageStart(newStage) {
        this.loadingStage = newStage;
        this.loadingStageStartTimestampMs = performance.now();
    }
    trackLoadingStageEnd() {
        if (this.loadingStage === LoadingStage.NOT_LOADING) {
            return;
        }
        chrome.histograms.recordMediumTime('Glic.Host.LoadingStageDuration.' +
            LoadingStage[this.getLoadingStage()], Math.floor(performance.now() - this.loadingStageStartTimestampMs));
        this.loadingStage = LoadingStage.NOT_LOADING;
    }
    getLoadingStage() {
        if (this.loadingStage === LoadingStage.LOADING_WEB_CLIENT &&
            this.webview?.waitingOnPanelWillOpen()) {
            return LoadingStage.AWAITING_NOTIFY_PANEL_WILL_OPEN;
        }
        return this.loadingStage;
    }
    async load() {
        // profileReadyState isn't available right away. Wait until it's ready.
        this.trackLoadingStageStart(LoadingStage.AWAITING_PROFILE_READY);
        await this.profileReadyInitialState.promise;
        this.trackLoadingStageEnd();
        const readyState = this.profileReadyState;
        assert(readyState !== undefined);
        switch (readyState) {
            case ProfileReadyState.kIneligible:
            case ProfileReadyState.kUnknownError:
                this.setState(WebUiState.kUnavailable);
                return;
            case ProfileReadyState.kIneligibleAccount:
                this.setState(WebUiState.kIneligibleAccount);
                return;
            case ProfileReadyState.kLocationMismatch:
                this.setState(WebUiState.kLocationMismatch);
                return;
            case ProfileReadyState.kDisabledByAdmin:
                $.disabledByAdminPanel.classList.toggle('show-disabled-by-admin-link', false);
                this.setState(WebUiState.kDisabledByAdmin);
                return;
            case ProfileReadyState.kSignInRequired:
                this.setState(WebUiState.kSignIn);
                return;
            case ProfileReadyState.kReady:
                break;
            default:
                assertNotReachedCase(readyState);
        }
        // Blocking on cookie syncing here introduces latency, we should consider
        // ways to avoid it.
        this.trackLoadingStageStart(LoadingStage.AWAITING_COOKIE_SYNC);
        const { result } = this.browserProxy.glicPreloadHandler ?
            await this.browserProxy.glicPreloadHandler.prepareForClient() :
            await this.browserProxy.pageHandler.prepareForClient();
        this.trackLoadingStageEnd();
        switch (result) {
            case PrepareForClientResult.kSuccess:
                break;
            case PrepareForClientResult.kErrorResyncingCookies:
                console.warn('prepareForClient in beginLoad() failed.');
                this.setErrorState(WebUiErrorReason.COOKIE_SYNC_ERROR);
                return;
            case PrepareForClientResult.kRequiresSignIn:
                this.setState(WebUiState.kSignIn);
                return;
            default:
                assertNotReachedCase(result);
        }
        // Load the web client only after cookie sync is complete.
        this.trackLoadingStageStart(LoadingStage.LOADING_WEB_CLIENT);
        this.destroyWebview();
        this.webview = new WebviewController($.webviewContainer, this.browserProxy, this, this, this.webviewPersistentState);
        this.webview.getWebClientState().subscribe(this.webClientStateChanged.bind(this));
        if (loadTimeData.getBoolean('noLoader')) {
            this.showPanel('guestPanel');
        }
        // Browser is expected to call client's notifyPanelWillOpen(), and then we
        // expect a call to webClientReady() when that finishes.
    }
    showLoading() {
        this.showPanel('loadingPanel');
        // After kMinHoldLoadingTimeMs, transition to finish-loading or ready. Note
        // that we do not transition from show-loading to ready before the timeout.
        this.earliestLoadingDismissTime = performance.now() + kMinHoldLoadingTimeMs;
        this.loadingTimer = setTimeout(() => {
            this.setState(WebUiState.kFinishLoading);
        }, kMinHoldLoadingTimeMs);
    }
    holdLoading() {
        // The web client is ready, but we still wait for the remainder of
        // `kMinHoldLoadingTimeMs` before showing it, to allow the loading animation
        // to complete once.
        this.loadingTimer = setTimeout(() => {
            this.setState(WebUiState.kReady);
        }, Math.max(0, this.earliestLoadingDismissTime - performance.now()));
    }
    finishLoading() {
        // The web client is not yet ready, so wait for the remainder of
        // `kMaxWaitTimeMs`. Switch to error state at that time unless interrupted
        // by `webClientReady`.
        this.loadingTimer = setTimeout(() => {
            if (this.webview?.waitingOnPanelWillOpen()) {
                console.warn('Exceeded timeout waiting for notifyPanelWillOpen');
                this.setErrorState(WebUiErrorReason.TIMEOUT_NOTIFY_PANEL_WILL_OPEN);
            }
            else if (this.webview?.getWebClientState().getCurrentValue() ===
                WebClientState.RESPONSIVE) {
                this.setState(WebUiState.kReady);
            }
            else {
                console.warn('Exceeded timeout waiting for client to load');
                this.setErrorState(WebUiErrorReason.TIMEOUT_LOADING_CLIENT);
            }
            if (this.state !== WebUiState.kReady) {
                chrome.histograms.recordEnumerationValue('Glic.Host.LoadingTimedOut', this.getLoadingStage(), LoadingStage.MAX_VALUE + 1);
                this.webview?.onLoadTimeOut();
            }
            this.trackLoadingStageEnd();
        }, kMaxWaitTimeMs - kMinHoldLoadingTimeMs);
    }
    // Show one webUI panel and hide the others. The widget is resized to fit the
    // newly-visible content. If the guest panel is now visible, then its size
    // will be determined by the most recent resize request.
    showPanel(id) {
        for (const panel of document.querySelectorAll('.panel')) {
            panel.hidden = panel.id !== id;
        }
        const panelStateKindSection = getRequiredElement('localPanels');
        panelStateKindSection.classList.toggle('hidden', id === 'guestPanel');
        // Focus the webview when the guest panel is shown.
        // b/475260887: webview.focus() won't focus the client page if the
        // <webview> element is invisible (due to an ancestor element having
        // display: none or HTML hidden attribute).
        if (id === 'guestPanel') {
            this.webview?.focus();
        }
    }
    // Destroy the current webview if it exists. This is necessary because
    // webview does not support unloading content by setting src=""
    destroyWebview() {
        if (!this.webview) {
            return;
        }
        this.webview.destroy();
        this.webview = undefined;
    }
    setWebviewDormant() {
        // Never allow dormant state when the panel is hidden.
        if (this.panelStateKind === PanelStateKind.kHidden) {
            this.destroyWebview();
            return;
        }
        this.webview?.setDormant();
    }
    online() {
        if (this.simulateNoConnection) {
            return;
        }
        if (this.state !== WebUiState.kOffline) {
            return;
        }
        this.setState(WebUiState.kBeginLoad);
    }
    offline() {
        const allowedStates = [
            WebUiState.kBeginLoad,
            WebUiState.kShowLoading,
            WebUiState.kFinishLoading,
        ];
        if (allowedStates.includes(this.state)) {
            this.setState(WebUiState.kOffline);
        }
    }
    installDebugButton() {
        const button = document.createElement('cr-icon-button');
        button.id = 'debug';
        button.classList.add('tonal-button');
        button.setAttribute('iron-icon', 'cr:search');
        document.querySelector('#errorPanel .notice').appendChild(button);
        button.addEventListener('click', () => {
            this.showDebug();
        });
    }
    // ApiHostEmbedder implementation.
    // Called when the web client requests to enable manual drag resize.
    enableDragResize(enabled) {
        this.guestResizeEnabled = enabled;
        if (this.state === WebUiState.kReady) {
            this.browserProxy.pageHandler.enableDragResize(this.guestResizeEnabled);
        }
    }
    // Called when the notifyPanelWillOpen promise resolves to open the panel
    // when triggered from the browser.
    webClientReady() {
        if (this.state === WebUiState.kBeginLoad ||
            this.state === WebUiState.kFinishLoading ||
            this.state === WebUiState.kWarmed) {
            this.cancelTimeout();
            this.trackLoadingStageEnd();
            this.setState(WebUiState.kReady);
        }
        else if (this.state === WebUiState.kShowLoading) {
            this.cancelTimeout();
            this.setState(WebUiState.kHoldLoading);
        }
    }
    getZoom() {
        return new Promise((resolve) => {
            if (!this.webview || !isFullWebView(this.webview.webview)) {
                resolve(1.0);
                return;
            }
            this.webview.webview.getZoom((currentZoom) => {
                resolve(currentZoom);
            });
        });
    }
    onboardingCompleted() {
        this.isFreCompleted = true;
    }
    webClientWarmed() {
        if (this.state === WebUiState.kBeginLoad ||
            this.state === WebUiState.kFinishLoading ||
            this.state === WebUiState.kShowLoading) {
            this.cancelTimeout();
            this.trackLoadingStageEnd();
            this.setState(WebUiState.kWarmed);
            if (this.panelStateKind !== PanelStateKind.kHidden) {
                this.startWarmedTimeout();
            }
        }
    }
    startWarmedTimeout() {
        if (this.loadingTimer) {
            return;
        }
        this.loadingTimer = setTimeout(() => {
            if (this.state === WebUiState.kWarmed) {
                this.setErrorState(WebUiErrorReason.TIMEOUT_WARMED);
            }
        }, kMaxWaitTimeMs);
    }
    webClientStateChanged(state) {
        switch (state) {
            case WebClientState.RESPONSIVE:
                // If we're still in a loading state, let it transition naturally
                // through the loading process.
                switch (this.state) {
                    case WebUiState.kBeginLoad:
                    case WebUiState.kShowLoading:
                    case WebUiState.kHoldLoading:
                        return;
                    default:
                        this.setState(WebUiState.kReady);
                }
                break;
            case WebClientState.UNRESPONSIVE:
                this.trackUnresponsiveState(this.state === WebUiState.kUnresponsive ?
                    WebClientUnresponsiveState.ALREADY_ON_FROM_CUSTOM_HEARTBEAT :
                    WebClientUnresponsiveState.ENTERED_FROM_CUSTOM_HEARTBEAT);
                this.setState(WebUiState.kUnresponsive);
                break;
            case WebClientState.ERROR:
                this.guestResizeEnabled = false;
                this.setErrorState(WebUiErrorReason.CLIENT_ERROR);
                break;
            case WebClientState.UNINITIALIZED:
                break;
            default:
                assertNotReachedCase(state);
        }
    }
    // External entry points.
    // TODO: Make this a proper state.
    showDebug() {
        this.setState(WebUiState.kReady);
        $.guestPanel.classList.toggle('show-header', true);
        $.guestPanel.classList.toggle('debug', true);
    }
    close() {
        // If we're in the debug view, switch back to error. Otherwise close the
        // window.
        if (this.state === WebUiState.kReady &&
            $.guestPanel.classList.contains('debug')) {
            $.guestPanel.classList.toggle('debug', false);
            this.setErrorState(WebUiErrorReason.CLOSE_DEBUG_VIEW);
        }
        else if (this.state === WebUiState.kReady) {
            this.browserProxy.pageHandler.closePanel();
        }
        else {
            // Reload in the background if user closes window while web client is not
            // ready. This is an escape hatch for situation where we're stuck in a
            // loading state caused by an error.
            this.browserProxy.pageHandler.closePanel().then(() => {
                this.reload();
            });
        }
    }
    // Called when the reload button is clicked.
    reload() {
        this.destroyWebview();
        // TODO: Allow the timeout on this load to be longer than the initial load.
        this.setState(WebUiState.kBeginLoad);
    }
    openProfilePicker() {
        this.browserProxy.pageHandler.openProfilePickerAndClosePanel();
    }
    signIn() {
        this.browserProxy.pageHandler.signInAndClosePanel();
    }
    // PageInterface implementation.
    updatePageState(panelStateKind) {
        if (this.panelStateKind === panelStateKind) {
            return;
        }
        this.panelStateKind = panelStateKind;
        if (this.panelStateKind !== PanelStateKind.kHidden &&
            this.state === WebUiState.kWarmed) {
            this.startWarmedTimeout();
        }
        const panelStateKindSection = getRequiredElement('localPanels');
        panelStateKindSection.classList.toggle('sidePanel', this.panelStateKind === PanelStateKind.kAttached);
        panelStateKindSection.classList.toggle('floating', this.panelStateKind === PanelStateKind.kDetached);
    }
    zoom(zoomAction) {
        this.webview?.zoom(zoomAction);
    }
    // Called before the WebUI is shown. If we're in an error state, automatically
    // try to reload.
    intentToShow() {
        if (this.stateDescriptor()?.reloadOnOpen) {
            this.reload();
        }
    }
    setProfileReadyState(state) {
        if (this.profileReadyState === state) {
            return;
        }
        const initialCall = this.profileReadyState === undefined;
        this.profileReadyState = state;
        if (initialCall) {
            // The initial state is handled in `beginLoad()`.
            this.profileReadyInitialState.resolve();
        }
        else {
            switch (this.profileReadyState) {
                case ProfileReadyState.kUnknownError:
                case ProfileReadyState.kIneligible:
                    this.setState(WebUiState.kUnavailable);
                    break;
                case ProfileReadyState.kIneligibleAccount:
                    this.setState(WebUiState.kIneligibleAccount);
                    break;
                case ProfileReadyState.kLocationMismatch:
                    this.setState(WebUiState.kLocationMismatch);
                    break;
                case ProfileReadyState.kDisabledByAdmin:
                    $.disabledByAdminPanel.classList.toggle('show-disabled-by-admin-link', false);
                    this.setState(WebUiState.kDisabledByAdmin);
                    break;
                case ProfileReadyState.kSignInRequired:
                    this.setState(WebUiState.kSignIn);
                    break;
                case ProfileReadyState.kReady:
                    if (this.stateDescriptor()?.reloadOnOpen) {
                        this.setState(WebUiState.kBeginLoad);
                    }
                    break;
                default:
                    assertNotReachedCase(this.profileReadyState);
            }
        }
    }
    openDisabledByAdminLink() {
        this.browserProxy.pageHandler.openDisabledByAdminLinkAndClosePanel();
    }
    isOnline() {
        return loadTimeData.getBoolean('ignoreOfflineState') ?
            true :
            navigator.onLine && !this.simulateNoConnection;
    }
}
