// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { GlowAnimationState } from '//resources/cr_components/search/constants.js';
import { getInstance as getAnnouncerInstance } from '//resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
import { I18nMixinLit } from '//resources/cr_elements/i18n_mixin_lit.js';
import { assert, assertNotReached } from '//resources/js/assert.js';
import { EventTracker } from '//resources/js/event_tracker.js';
import { loadTimeData } from '//resources/js/load_time_data.js';
import { hasKeyModifiers } from '//resources/js/util.js';
import { DriveDisclaimerStatus, DriveUploadError, InputMethod, SuggestInventory } from '//resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js';
import { ComposeboxFile, ComposeboxFileValidationError, ContextType, ContextualSearchInputStateDeletionType, FILE_VALIDATION_ERRORS_MAP, getLoadTimeBoolean, isContextUploadStatusTerminal, ProcessFilesError, recordBoolean, recordContextAdditionMethod, recordContextualElementClickedMetric, recordEnumerationValue, recordInputTypeShown, recordModelModeSelection, recordModelModeShown, recordToolModeSelection, recordToolModeShown, recordUserAction, TabSuggestionsState, TabUploadOrigin } from './common.js';
// 
import { ComposeboxProxyImpl } from './composebox_proxy.js';
// 
import { ContextUploadStatus, InputType, ModelMode, ToolMode } from './composebox_query.mojom-webui.js';
import { ContextualEntrypointAndMenuElement } from './contextual_entrypoint_and_menu.js';
import { WindowProxy } from './window_proxy.js';
export var VoiceSearchAction;
(function (VoiceSearchAction) {
    VoiceSearchAction[VoiceSearchAction["ACTIVATE"] = 0] = "ACTIVATE";
    VoiceSearchAction[VoiceSearchAction["QUERY_SUBMITTED"] = 1] = "QUERY_SUBMITTED";
})(VoiceSearchAction || (VoiceSearchAction = {}));
export var SubmitButtonIconType;
(function (SubmitButtonIconType) {
    SubmitButtonIconType["FORWARD"] = "forward";
    SubmitButtonIconType["UPWARD"] = "upward";
})(SubmitButtonIconType || (SubmitButtonIconType = {}));
const PERMISSION_PROMPT_CSS_CLASS = 'permission-prompt-showing';
function dedupeTabs(restoredTabs, recentTabs) {
    const restoredUrlMap = new Map(restoredTabs.map(t => [t.tabId, t.url]));
    return recentTabs.filter(t => {
        const restoredUrl = restoredUrlMap.get(t.tabId);
        return restoredUrl !== t.url;
    });
}
export const ComposeboxEmbedderMixin = (superClass) => {
    class ComposeboxEmbedderMixin extends I18nMixinLit(superClass) {
        static get properties() {
            return {
                addedTabsIds: { type: Object },
                animationState: {
                    reflect: true,
                    type: String,
                },
                disableCaretColorAnimation: {
                    type: Boolean,
                    reflect: true,
                },
                disableVoiceSearchAnimation: { type: Boolean },
                isDraggingFile: {
                    reflect: true,
                    type: Boolean,
                },
                enableImageContextualSuggestions: {
                    reflect: true,
                    type: Boolean,
                },
                smartComposeEnabled: {
                    reflect: true,
                    type: Boolean,
                },
                submitting: { type: Boolean },
                showContextMenuDescription: { type: Boolean },
                smartTabSharingActive: { type: Boolean },
                smartTabSharingVisible: { type: Boolean },
                contextManagementInComposeboxEnabled: { type: Boolean },
                shouldShowGhostFiles: { type: Boolean },
                showMenuOnClick: { type: Boolean },
                submitButtonIconType: { type: String },
                isCanvasQuerySubmitted: { type: Boolean },
                canSubmitFilesAndInput: {
                    type: Boolean,
                    reflect: true,
                },
                contextMenuEnabled: { type: Boolean },
                errorMessage: { type: String },
                files: { type: Object },
                fileUploadsComplete: {
                    type: Boolean,
                    reflect: true,
                },
                hasAllowedInputs: {
                    type: Boolean,
                    reflect: true,
                },
                input: { type: String },
                inputPlaceholder: { type: String, reflect: true },
                inputState: { type: Object },
                inToolMode: {
                    type: Boolean,
                    reflect: true,
                },
                inVoiceSearchMode: {
                    type: Boolean,
                    reflect: true,
                },
                maxSuggestions: { type: Number },
                receivedSpeech: { type: Boolean },
                result: { type: Object },
                shareTabsFlyoutOpen: { type: Boolean },
                searchboxLayoutMode: {
                    type: String,
                    reflect: true,
                },
                /**
                 * Generic flag indicating a "Next" searchbox (Realbox Next, Omnibox
                 * Next, etc.). Used for all styling and behavior shared across
                 * 'Next' searchbox implementations.
                 */
                searchboxNextEnabled: {
                    type: Boolean,
                    reflect: true,
                },
                selectedMatchIndex: { type: Number },
                showDropdown: {
                    reflect: true,
                    type: Boolean,
                },
                dropdownNeeded: { type: Boolean },
                clearAllInputsWhenSubmittingQuery: { type: Boolean },
                closeOnEscape: { type: Boolean },
                composeboxNoFlickerSuggestionsFix: { type: Boolean },
                queryZpsOnLoad: { type: Boolean },
                showFileCarousel: {
                    reflect: true,
                    type: Boolean,
                },
                showTypedSuggestWithContext: { type: Boolean },
                showVoiceSearch: { type: Boolean },
                suggestionActivityEnabled: { type: Boolean },
                usePecApi: {
                    type: Boolean,
                    reflect: true,
                },
                isOblongShape: { type: Boolean },
                webuiOmniboxSimplificationEnabled: { type: Boolean },
                smartComposeInlineHint: { type: String },
                smartComposeStats: { type: Object },
                recentTabId: { type: Number },
                state: { type: Object },
                submitEnabled: {
                    reflect: true,
                    type: Boolean,
                },
                tabSuggestions: { type: Array },
                tabSuggestionsState: { type: Number },
                aimThreadRestoredTabs: { type: Array },
                tabDeselectionEnabled: { type: Boolean },
                transcript: { type: String },
                uploadButtonDisabled: {
                    type: Boolean,
                    reflect: true,
                },
                hasVoiceSearchError: { type: Boolean },
                voiceSearchCoherenceEnabled: { type: Boolean },
                isListening: { type: Boolean, reflect: true },
                tabFaviconChipsToCoinsEnabled: { type: Boolean },
                energyEffectEnabled: {
                    reflect: true,
                    type: Boolean,
                },
                energyEffectAnimationEnabled: {
                    reflect: true,
                    type: Boolean,
                },
            };
        }
        #animationState_accessor_storage = GlowAnimationState.NONE;
        get animationState() { return this.#animationState_accessor_storage; }
        set animationState(value) { this.#animationState_accessor_storage = value; }
        #disableCaretColorAnimation_accessor_storage = false;
        get disableCaretColorAnimation() { return this.#disableCaretColorAnimation_accessor_storage; }
        set disableCaretColorAnimation(value) { this.#disableCaretColorAnimation_accessor_storage = value; }
        #disableVoiceSearchAnimation_accessor_storage = false;
        get disableVoiceSearchAnimation() { return this.#disableVoiceSearchAnimation_accessor_storage; }
        set disableVoiceSearchAnimation(value) { this.#disableVoiceSearchAnimation_accessor_storage = value; }
        #submitting_accessor_storage = false;
        // Blocks autocomplete updates after a query has been submitted or
        // clicked. This prevents the dropdown from briefly flashing open due to
        // late-arriving autocomplete updates or automatic focus restoration
        // before navigation completes.
        get submitting() { return this.#submitting_accessor_storage; }
        set submitting(value) { this.#submitting_accessor_storage = value; }
        #addedTabsIds_accessor_storage = new Map();
        // Current tabs attached to composebox for this turn (query).
        get addedTabsIds() { return this.#addedTabsIds_accessor_storage; }
        set addedTabsIds(value) { this.#addedTabsIds_accessor_storage = value; }
        #aimThreadRestoredTabs_accessor_storage = [];
        // Persistent tabs from previous queries/turns in this conversation
        // (that AIM has confirmed that it has received), OR tabs that were part
        // of a past historical conversation that was loaded.
        get aimThreadRestoredTabs() { return this.#aimThreadRestoredTabs_accessor_storage; }
        set aimThreadRestoredTabs(value) { this.#aimThreadRestoredTabs_accessor_storage = value; }
        #isDraggingFile_accessor_storage = false;
        get isDraggingFile() { return this.#isDraggingFile_accessor_storage; }
        set isDraggingFile(value) { this.#isDraggingFile_accessor_storage = value; }
        #enableImageContextualSuggestions_accessor_storage = loadTimeData.getBoolean('composeboxShowImageSuggest');
        get enableImageContextualSuggestions() { return this.#enableImageContextualSuggestions_accessor_storage; }
        set enableImageContextualSuggestions(value) { this.#enableImageContextualSuggestions_accessor_storage = value; }
        #smartComposeEnabled_accessor_storage = loadTimeData.getBoolean('composeboxSmartComposeEnabled');
        get smartComposeEnabled() { return this.#smartComposeEnabled_accessor_storage; }
        set smartComposeEnabled(value) { this.#smartComposeEnabled_accessor_storage = value; }
        #smartTabSharingActive_accessor_storage = false;
        get smartTabSharingActive() { return this.#smartTabSharingActive_accessor_storage; }
        set smartTabSharingActive(value) { this.#smartTabSharingActive_accessor_storage = value; }
        #smartTabSharingVisible_accessor_storage = getLoadTimeBoolean('composeboxSmartTabSharingVisible', false);
        get smartTabSharingVisible() { return this.#smartTabSharingVisible_accessor_storage; }
        set smartTabSharingVisible(value) { this.#smartTabSharingVisible_accessor_storage = value; }
        #contextManagementInComposeboxEnabled_accessor_storage = getLoadTimeBoolean('contextManagementInComposeboxEnabled', false);
        get contextManagementInComposeboxEnabled() { return this.#contextManagementInComposeboxEnabled_accessor_storage; }
        set contextManagementInComposeboxEnabled(value) { this.#contextManagementInComposeboxEnabled_accessor_storage = value; }
        #tabDeselectionEnabled_accessor_storage = getLoadTimeBoolean('composeboxContextMenuEnableTabDeselection', false);
        get tabDeselectionEnabled() { return this.#tabDeselectionEnabled_accessor_storage; }
        set tabDeselectionEnabled(value) { this.#tabDeselectionEnabled_accessor_storage = value; }
        contextMenuDescriptionEnabled = getLoadTimeBoolean('composeboxShowContextMenuDescription', false);
        #showContextMenuDescription_accessor_storage = this.contextMenuDescriptionEnabled;
        get showContextMenuDescription() { return this.#showContextMenuDescription_accessor_storage; }
        set showContextMenuDescription(value) { this.#showContextMenuDescription_accessor_storage = value; }
        #shouldShowGhostFiles_accessor_storage = false;
        get shouldShowGhostFiles() { return this.#shouldShowGhostFiles_accessor_storage; }
        set shouldShowGhostFiles(value) { this.#shouldShowGhostFiles_accessor_storage = value; }
        #showMenuOnClick_accessor_storage = true;
        get showMenuOnClick() { return this.#showMenuOnClick_accessor_storage; }
        set showMenuOnClick(value) { this.#showMenuOnClick_accessor_storage = value; }
        #isCanvasQuerySubmitted_accessor_storage = false;
        get isCanvasQuerySubmitted() { return this.#isCanvasQuerySubmitted_accessor_storage; }
        set isCanvasQuerySubmitted(value) { this.#isCanvasQuerySubmitted_accessor_storage = value; }
        #recentTabId_accessor_storage = null;
        get recentTabId() { return this.#recentTabId_accessor_storage; }
        set recentTabId(value) { this.#recentTabId_accessor_storage = value; }
        #hasVoiceSearchError_accessor_storage = false;
        // If voice search error scrim is showing:
        get hasVoiceSearchError() { return this.#hasVoiceSearchError_accessor_storage; }
        set hasVoiceSearchError(value) { this.#hasVoiceSearchError_accessor_storage = value; }
        #isListening_accessor_storage = false;
        // Voice search is listening if there is no error and voice search
        // overlay is open (and active).
        get isListening() { return this.#isListening_accessor_storage; }
        set isListening(value) { this.#isListening_accessor_storage = value; }
        #voiceSearchCoherenceEnabled_accessor_storage = false;
        get voiceSearchCoherenceEnabled() { return this.#voiceSearchCoherenceEnabled_accessor_storage; }
        set voiceSearchCoherenceEnabled(value) { this.#voiceSearchCoherenceEnabled_accessor_storage = value; }
        #tabFaviconChipsToCoinsEnabled_accessor_storage = loadTimeData.getBoolean('tabFaviconChipsToCoinsEnabled');
        get tabFaviconChipsToCoinsEnabled() { return this.#tabFaviconChipsToCoinsEnabled_accessor_storage; }
        set tabFaviconChipsToCoinsEnabled(value) { this.#tabFaviconChipsToCoinsEnabled_accessor_storage = value; }
        #energyEffectEnabled_accessor_storage = false;
        get energyEffectEnabled() { return this.#energyEffectEnabled_accessor_storage; }
        set energyEffectEnabled(value) { this.#energyEffectEnabled_accessor_storage = value; }
        #energyEffectAnimationEnabled_accessor_storage = false;
        get energyEffectAnimationEnabled() { return this.#energyEffectAnimationEnabled_accessor_storage; }
        set energyEffectAnimationEnabled(value) { this.#energyEffectAnimationEnabled_accessor_storage = value; }
        browserTabContextAdded = false;
        pendingUploads = new Set();
        dragAndDropEnabled = loadTimeData.getBoolean('composeboxContextDragAndDropEnabled');
        composeboxSource = loadTimeData.getString('composeboxSource');
        maxFileCount = loadTimeData.getInteger('composeboxFileMaxCount');
        maxFileSize = loadTimeData.getInteger('composeboxFileMaxSize');
        attachmentFileTypes = loadTimeData.getString('composeboxAttachmentFileTypes').split(',');
        imageFileTypes = loadTimeData.getString('composeboxImageFileTypes').split(',');
        contextMenuOpened = false;
        hasCachedSubmittedTabsThisTurn = false;
        eventTracker = new EventTracker();
        get keepMenuOpenOnTabSelect() {
            return false;
        }
        #canSubmitFilesAndInput_accessor_storage = true;
        get canSubmitFilesAndInput() { return this.#canSubmitFilesAndInput_accessor_storage; }
        set canSubmitFilesAndInput(value) { this.#canSubmitFilesAndInput_accessor_storage = value; }
        #clearAllInputsWhenSubmittingQuery_accessor_storage = false;
        get clearAllInputsWhenSubmittingQuery() { return this.#clearAllInputsWhenSubmittingQuery_accessor_storage; }
        set clearAllInputsWhenSubmittingQuery(value) { this.#clearAllInputsWhenSubmittingQuery_accessor_storage = value; }
        #closeOnEscape_accessor_storage = true;
        get closeOnEscape() { return this.#closeOnEscape_accessor_storage; }
        set closeOnEscape(value) { this.#closeOnEscape_accessor_storage = value; }
        #composeboxNoFlickerSuggestionsFix_accessor_storage = false;
        get composeboxNoFlickerSuggestionsFix() { return this.#composeboxNoFlickerSuggestionsFix_accessor_storage; }
        set composeboxNoFlickerSuggestionsFix(value) { this.#composeboxNoFlickerSuggestionsFix_accessor_storage = value; }
        #contextMenuEnabled_accessor_storage = loadTimeData.getBoolean('composeboxShowContextMenu');
        get contextMenuEnabled() { return this.#contextMenuEnabled_accessor_storage; }
        set contextMenuEnabled(value) { this.#contextMenuEnabled_accessor_storage = value; }
        #errorMessage_accessor_storage = '';
        get errorMessage() { return this.#errorMessage_accessor_storage; }
        set errorMessage(value) { this.#errorMessage_accessor_storage = value; }
        #files_accessor_storage = new Map();
        // Files/tabs added by the user for the current turn (query).
        get files() { return this.#files_accessor_storage; }
        set files(value) { this.#files_accessor_storage = value; }
        #fileUploadsComplete_accessor_storage = true;
        get fileUploadsComplete() { return this.#fileUploadsComplete_accessor_storage; }
        set fileUploadsComplete(value) { this.#fileUploadsComplete_accessor_storage = value; }
        #hasAllowedInputs_accessor_storage = false;
        get hasAllowedInputs() { return this.#hasAllowedInputs_accessor_storage; }
        set hasAllowedInputs(value) { this.#hasAllowedInputs_accessor_storage = value; }
        #input_accessor_storage = '';
        get input() { return this.#input_accessor_storage; }
        set input(value) { this.#input_accessor_storage = value; }
        #inputPlaceholder_accessor_storage = loadTimeData.getString('searchboxComposePlaceholder');
        get inputPlaceholder() { return this.#inputPlaceholder_accessor_storage; }
        set inputPlaceholder(value) { this.#inputPlaceholder_accessor_storage = value; }
        #inputState_accessor_storage = null;
        get inputState() { return this.#inputState_accessor_storage; }
        set inputState(value) { this.#inputState_accessor_storage = value; }
        #inToolMode_accessor_storage = false;
        get inToolMode() { return this.#inToolMode_accessor_storage; }
        set inToolMode(value) { this.#inToolMode_accessor_storage = value; }
        #submitButtonIconType_accessor_storage = SubmitButtonIconType.UPWARD;
        get submitButtonIconType() { return this.#submitButtonIconType_accessor_storage; }
        set submitButtonIconType(value) { this.#submitButtonIconType_accessor_storage = value; }
        #inVoiceSearchMode_accessor_storage = false;
        // Indicates if voice search overlay is open. Does not indicate if it
        // is 'listening'. This is because there might be an error scrim showing
        // (see `hasVoiceSearchError`), making voice search not 'listening'.
        get inVoiceSearchMode() { return this.#inVoiceSearchMode_accessor_storage; }
        set inVoiceSearchMode(value) { this.#inVoiceSearchMode_accessor_storage = value; }
        #maxSuggestions_accessor_storage = null;
        get maxSuggestions() { return this.#maxSuggestions_accessor_storage; }
        set maxSuggestions(value) { this.#maxSuggestions_accessor_storage = value; }
        #receivedSpeech_accessor_storage = false;
        get receivedSpeech() { return this.#receivedSpeech_accessor_storage; }
        set receivedSpeech(value) { this.#receivedSpeech_accessor_storage = value; }
        #result_accessor_storage = null;
        get result() { return this.#result_accessor_storage; }
        set result(value) { this.#result_accessor_storage = value; }
        #shareTabsFlyoutOpen_accessor_storage = false;
        // Keeps track of whether the "Share Tabs" submenu/flyout in the context
        // menu is open. This state is owned by the mixin so that it can be
        // reset when the context menu is closed, and passed down to keep the
        // child menu's rendering in sync.
        get shareTabsFlyoutOpen() { return this.#shareTabsFlyoutOpen_accessor_storage; }
        set shareTabsFlyoutOpen(value) { this.#shareTabsFlyoutOpen_accessor_storage = value; }
        #suggestionActivityEnabled_accessor_storage = true;
        get suggestionActivityEnabled() { return this.#suggestionActivityEnabled_accessor_storage; }
        set suggestionActivityEnabled(value) { this.#suggestionActivityEnabled_accessor_storage = value; }
        #searchboxLayoutMode_accessor_storage = '';
        get searchboxLayoutMode() { return this.#searchboxLayoutMode_accessor_storage; }
        set searchboxLayoutMode(value) { this.#searchboxLayoutMode_accessor_storage = value; }
        #searchboxNextEnabled_accessor_storage = false;
        get searchboxNextEnabled() { return this.#searchboxNextEnabled_accessor_storage; }
        set searchboxNextEnabled(value) { this.#searchboxNextEnabled_accessor_storage = value; }
        #queryZpsOnLoad_accessor_storage = true;
        get queryZpsOnLoad() { return this.#queryZpsOnLoad_accessor_storage; }
        set queryZpsOnLoad(value) { this.#queryZpsOnLoad_accessor_storage = value; }
        selectedMatch = null;
        #selectedMatchIndex_accessor_storage = -1;
        get selectedMatchIndex() { return this.#selectedMatchIndex_accessor_storage; }
        set selectedMatchIndex(value) { this.#selectedMatchIndex_accessor_storage = value; }
        #showDropdown_accessor_storage = loadTimeData.getBoolean('composeboxShowZps');
        get showDropdown() { return this.#showDropdown_accessor_storage; }
        set showDropdown(value) { this.#showDropdown_accessor_storage = value; }
        #dropdownNeeded_accessor_storage = true;
        get dropdownNeeded() { return this.#dropdownNeeded_accessor_storage; }
        set dropdownNeeded(value) { this.#dropdownNeeded_accessor_storage = value; }
        #showFileCarousel_accessor_storage = false;
        get showFileCarousel() { return this.#showFileCarousel_accessor_storage; }
        set showFileCarousel(value) { this.#showFileCarousel_accessor_storage = value; }
        #showTypedSuggestWithContext_accessor_storage = false;
        get showTypedSuggestWithContext() { return this.#showTypedSuggestWithContext_accessor_storage; }
        set showTypedSuggestWithContext(value) { this.#showTypedSuggestWithContext_accessor_storage = value; }
        #usePecApi_accessor_storage = false;
        get usePecApi() { return this.#usePecApi_accessor_storage; }
        set usePecApi(value) { this.#usePecApi_accessor_storage = value; }
        #isOblongShape_accessor_storage = false;
        get isOblongShape() { return this.#isOblongShape_accessor_storage; }
        set isOblongShape(value) { this.#isOblongShape_accessor_storage = value; }
        #webuiOmniboxSimplificationEnabled_accessor_storage = false;
        get webuiOmniboxSimplificationEnabled() { return this.#webuiOmniboxSimplificationEnabled_accessor_storage; }
        set webuiOmniboxSimplificationEnabled(value) { this.#webuiOmniboxSimplificationEnabled_accessor_storage = value; }
        searchboxListenerIds = [];
        showZps = loadTimeData.getBoolean('composeboxShowZps');
        #showVoiceSearch_accessor_storage = false;
        // Attribute that can be set by parent to enable/disable voice search
        // overlay. `inVoiceSearchMode` indicates if voice search overlay is (at
        // least) open.
        get showVoiceSearch() { return this.#showVoiceSearch_accessor_storage; }
        set showVoiceSearch(value) { this.#showVoiceSearch_accessor_storage = value; }
        #smartComposeInlineHint_accessor_storage = '';
        get smartComposeInlineHint() { return this.#smartComposeInlineHint_accessor_storage; }
        set smartComposeInlineHint(value) { this.#smartComposeInlineHint_accessor_storage = value; }
        #smartComposeStats_accessor_storage = {
            enabled: loadTimeData.getBoolean('composeboxSmartComposeEnabled'),
            shownCount: 0,
            acceptedCount: 0,
            charactersAccepted: 0,
            shownLength: 0,
        };
        get smartComposeStats() { return this.#smartComposeStats_accessor_storage; }
        set smartComposeStats(value) { this.#smartComposeStats_accessor_storage = value; }
        #state_accessor_storage = null;
        get state() { return this.#state_accessor_storage; }
        set state(value) { this.#state_accessor_storage = value; }
        #suggestInventory_accessor_storage = null;
        get suggestInventory() { return this.#suggestInventory_accessor_storage; }
        set suggestInventory(value) { this.#suggestInventory_accessor_storage = value; }
        #submitEnabled_accessor_storage = false;
        get submitEnabled() { return this.#submitEnabled_accessor_storage; }
        set submitEnabled(value) { this.#submitEnabled_accessor_storage = value; }
        #tabSuggestions_accessor_storage = [];
        get tabSuggestions() { return this.#tabSuggestions_accessor_storage; }
        set tabSuggestions(value) { this.#tabSuggestions_accessor_storage = value; }
        #tabSuggestionsState_accessor_storage = TabSuggestionsState.NOT_STARTED;
        get tabSuggestionsState() { return this.#tabSuggestionsState_accessor_storage; }
        set tabSuggestionsState(value) { this.#tabSuggestionsState_accessor_storage = value; }
        #transcript_accessor_storage = '';
        get transcript() { return this.#transcript_accessor_storage; }
        set transcript(value) { this.#transcript_accessor_storage = value; }
        #uploadButtonDisabled_accessor_storage = false;
        get uploadButtonDisabled() { return this.#uploadButtonDisabled_accessor_storage; }
        set uploadButtonDisabled(value) { this.#uploadButtonDisabled_accessor_storage = value; }
        showTypedSuggest = loadTimeData.getBoolean('composeboxShowTypedSuggest');
        // Tracks the latest query sent for autocompletion. Used to filter out
        // stale results. `activeQueryId` is reset to -1 when the last query
        // needs to be abandoned. `nextQueryId_` is monotonically increasing to
        // avoid reusing IDs.
        activeQueryId = -1;
        nextQueryId_ = 0;
        lastQueriedInput = '';
        haveReceivedSynchronousAutocompleteResponse = false;
        lensSendRawFileMediaTypesEnabled = loadTimeData.getBoolean('lensSendRawFileMediaTypesEnabled');
        smartComposeAnnounceTimeout_ = null;
        // =====================================================================
        // Lifecycle Hooks
        // =====================================================================
        connectedCallback() {
            super.connectedCallback();
            this.searchboxListenerIds = [
                this.getSearchboxCallbackRouter()
                    .autocompleteResultChanged.addListener(this.onAutocompleteResultChanged.bind(this)),
                this.getSearchboxCallbackRouter()
                    .onContextualInputStatusChanged.addListener(this.onContextualInputStatusChanged.bind(this)),
                this.getSearchboxCallbackRouter().onTabStripChanged.addListener(() => this.refreshTabSuggestions(/*forceRefresh=*/ true)),
                this.getSearchboxCallbackRouter().addFileContext.addListener(this.addFileContextFromBrowser.bind(this)),
                this.getSearchboxCallbackRouter().onInputStateChanged.addListener(this.onInputStateChanged.bind(this)),
                this.getSearchboxCallbackRouter()
                    .setAimThreadRestoredTabs.addListener(this.setAimThreadRestoredTabs.bind(this)),
            ];
            // 
            const listenerId = ComposeboxProxyImpl.getInstance().observeSmartTabSharingActive((active) => {
                this.smartTabSharingActive = active;
            });
            this.searchboxListenerIds.push(listenerId);
            // 
            this.getSearchboxHandler().notifySessionStarted();
            this.initializeInitialState_();
            if (this.hasUpdated) {
                this.setupInputListener_();
            }
            // For "next" searchboxes (Realbox Next, Omnibox Next, etc.), the zps
            // autocomplete query is triggered after the state has been
            // initialized.
            if (this.queryZpsOnLoad && !this.searchboxNextEnabled) {
                this.queryAutocomplete(/* clearMatches= */ false);
            }
        }
        async initializeInitialState_() {
            const inputStateResponse = await this.getSearchboxHandler().getInputState();
            if (inputStateResponse) {
                this.inputState = inputStateResponse.state;
            }
        }
        disconnectedCallback() {
            super.disconnectedCallback();
            this.getSearchboxHandler().notifySessionAbandoned();
            this.searchboxListenerIds.forEach(id => assert(this.getSearchboxCallbackRouter().removeListener(id)));
            this.searchboxListenerIds = [];
            this.eventTracker.removeAll();
        }
        firstUpdated(changedProperties) {
            super.firstUpdated(changedProperties);
            this.setupInputListener_();
        }
        setupInputListener_() {
            const inputElem = this.getInputElement()?.inputElement;
            if (inputElem) {
                this.eventTracker.add(inputElem, 'input', () => {
                    this.submitEnabled = this.computeSubmitEnabled();
                });
                // The following two event handlers are to re-enable the inputState
                // update after the submission of a query when
                // submitting is true.
                // This occurs when the user quickly cancels the navigation and
                // starts interacting with the input element again.
                this.eventTracker.add(inputElem, 'mousedown', () => {
                    this.submitting = false;
                });
                this.eventTracker.add(inputElem, 'keydown', () => {
                    this.submitting = false;
                });
            }
        }
        willUpdate(changedProperties) {
            super.willUpdate(changedProperties);
            const changedPrivateProperties = changedProperties;
            // 
            if (changedPrivateProperties.has('smartTabSharingVisible')) {
                if (this.smartTabSharingVisible) {
                    ComposeboxProxyImpl.getInstance().getSmartTabSharingActive().then(({ active }) => {
                        this.smartTabSharingActive = active;
                    });
                }
                else {
                    this.smartTabSharingActive = false;
                }
            }
            // 
            // When the result initially gets set check if dropdown should show.
            if (changedPrivateProperties.has('input') ||
                changedPrivateProperties.has('result') ||
                changedPrivateProperties.has('files') ||
                changedPrivateProperties.has('errorMessage')) {
                this.showFileCarousel = this.tabFaviconChipsToCoinsEnabled ?
                    this.getFilteredCarouselFiles().length > 0 :
                    this.files.size > 0;
                this.showDropdown = this.computeShowDropdown();
            }
            if (changedPrivateProperties.has('input') ||
                changedPrivateProperties.has('selectedMatchIndex') ||
                changedPrivateProperties.has('inputState') ||
                changedPrivateProperties.has('isFollowupQuery') ||
                changedPrivateProperties.has('files') ||
                changedPrivateProperties.has('submitEnabled') ||
                changedPrivateProperties.has('fileUploadsComplete')) {
                this.submitEnabled = this.computeSubmitEnabled();
                this.uploadButtonDisabled = !this.fileUploadsComplete;
                // `canSubmitFilesAndInput` checks if there is a valid query rather
                // than if submit is enabled, as `submitEnabled` only defines if the
                // submit button should be shown rather than its actual active
                // state.
                this.canSubmitFilesAndInput =
                    this.hasValidQuery() && this.fileUploadsComplete;
            }
            if (changedPrivateProperties.has('canSubmitFilesAndInput')) {
                this.fire('can-submit-files-and-input-changed', {
                    canSubmitFilesAndInput: this.canSubmitFilesAndInput,
                });
            }
            if (changedPrivateProperties.has('inputState') && this.inputState) {
                this.hasAllowedInputs =
                    (this.inputState.allowedModels.length > 0 ||
                        this.inputState.allowedTools.length > 0 ||
                        this.inputState.allowedInputTypes.length > 0);
                this.inToolMode =
                    this.inputState.activeTool !== ToolMode.kUnspecified;
                this.dispatchEvent(new CustomEvent('input-state-changed', {
                    detail: { inputState: this.inputState },
                }));
            }
            if (changedPrivateProperties.has('files') ||
                changedPrivateProperties.has('inputState') ||
                changedPrivateProperties.has('inputState.activeTool')) {
                // Non-default Suggest Inventory should not be shown when context
                // or tools are present.
                if (this.hasContent()) {
                    this.suggestInventory = null;
                }
                this.updateInputPlaceholder();
            }
            // Listening is true if there is no error and voice search is open.
            if (changedProperties.has('inVoiceSearchMode') ||
                changedProperties.has('hasVoiceSearchError')) {
                this.isListening =
                    this.inVoiceSearchMode && !this.hasVoiceSearchError;
            }
            if (!this.hasUpdated) {
                this.voiceSearchCoherenceEnabled = loadTimeData.getBoolean('voiceSearchCoherenceComposeboxesEnabled');
            }
        }
        updated(changedProperties) {
            super.updated(changedProperties);
            if (changedProperties.has('inputState')) {
                const oldInputState = changedProperties.get('inputState');
                if (oldInputState &&
                    this.inputState?.activeTool !== oldInputState.activeTool &&
                    !this.submitting) {
                    this.focusInput();
                    this.queryAutocomplete(/* clearMatches= */ true);
                }
            }
            const changedPrivateProperties = changedProperties;
            if (changedPrivateProperties.has('selectedMatchIndex')) {
                if (this.selectedMatch) {
                    // Update the input.
                    this.input = this.selectedMatch.fillIntoEdit;
                }
                else if (!this.lastQueriedInput) {
                    // This is for cases when focus leaves the matches/input.
                    // If there was already text in the input do not clear it.
                    // Only clear input when not empty, otherwise this impacts
                    // suggestions getting fetched for zero state.
                    if (this.input) {
                        this.clearInput();
                    }
                }
                else {
                    // For typed queries reset the input back to typed value when
                    // focus leaves the match.
                    this.input = this.lastQueriedInput;
                }
            }
            if (changedPrivateProperties.has('files')) {
                this.dispatchEvent(new CustomEvent('on-context-files-changed'));
            }
            if (changedPrivateProperties.has('smartComposeInlineHint')) {
                if (this.smartComposeAnnounceTimeout_ !== null) {
                    clearTimeout(this.smartComposeAnnounceTimeout_);
                    this.smartComposeAnnounceTimeout_ = null;
                }
                if (this.smartComposeInlineHint) {
                    this.smartComposeAnnounceTimeout_ = setTimeout(() => {
                        this.smartComposeAnnounceTimeout_ = null;
                        if (this.smartComposeInlineHint) {
                            const announcer = getAnnouncerInstance(this.closest('dialog') || document.body);
                            announcer.announce(this.smartComposeInlineHint + ', ' +
                                this.i18n('composeboxSmartComposeTitle'));
                        }
                    }, 1000);
                }
            }
            if (changedPrivateProperties.has('state') && this.state) {
                this.updateState(this.state);
            }
        }
        // =====================================================================
        // Embedder-provided methods for DOM and Mojo access
        // =====================================================================
        getInputElement() {
            assertNotReached();
        }
        getDropdownElement() {
            assertNotReached();
        }
        getActiveElement() {
            assertNotReached();
        }
        getPageHandler() {
            assertNotReached();
        }
        getSearchboxCallbackRouter() {
            assertNotReached();
        }
        getSearchboxHandler() {
            assertNotReached();
        }
        getContextEntrypointElement() {
            assertNotReached();
        }
        getLensButtonElement() {
            return null;
        }
        // =====================================================================
        // Common event handlers
        // =====================================================================
        onVoicePermissionChanged(e) {
            if (e.detail.isOpened) {
                // Only for when the permission prompt is showing, fire a resize
                // event if the permission prompt has a height and width.
                if (e.detail.height > 0 && e.detail.width > 0) {
                    this.fire('voice-permission-prompt-changed', e.detail);
                }
                // Not listening if no permission granted. Needed to turn off
                // animation.
                this.isListening = false;
            }
            else {
                // Listening is set as `true` if permission is granted.
                this.isListening =
                    this.inVoiceSearchMode && !this.hasVoiceSearchError;
                this.fire('voice-permission-prompt-changed', e.detail);
            }
            const audioAnimation = this.shadowRoot?.querySelector('#animatedSearchElement');
            if (audioAnimation) {
                if (e.detail.isOpened) { // Permission prompt opened.
                    audioAnimation.classList.add(PERMISSION_PROMPT_CSS_CLASS);
                }
                else { // Permission prompt closed.
                    audioAnimation.classList.remove(PERMISSION_PROMPT_CSS_CLASS);
                }
            }
            const voiceSearchElement = this.shadowRoot?.querySelector('#voiceSearch');
            if (voiceSearchElement) {
                if (e.detail.isOpened) { // Permission prompt opened.
                    voiceSearchElement.classList.add(PERMISSION_PROMPT_CSS_CLASS);
                }
                else { // Permission prompt closed.
                    voiceSearchElement.classList.remove(PERMISSION_PROMPT_CSS_CLASS);
                }
            }
        }
        // This function is called when backend starts a file upload flow,
        // whether through `addFileFromAttachment_`,
        // `addFileContextFromBrowser`, etc. This contrasts with the workflows
        // where the frontend starts a file upload flow
        // (`addFileContext`).
        onFileContextAdded(file) {
            const newFiles = new Map(this.files);
            newFiles.set(file.uuid, file);
            this.files = newFiles;
            if (file.status !== ContextUploadStatus.kUploadSuccessful) {
                this.addToPendingUploads(file.uuid);
            }
        }
        addFileContextForTesting(file) {
            this.onFileContextAdded(file);
        }
        onTranscriptUpdate(e) {
            this.transcript = e.detail;
        }
        onSpeechReceived() {
            this.receivedSpeech = true;
        }
        onDismissErrorScrim() {
            this.errorMessage = '';
        }
        onSelectedMatchIndexChanged(e) {
            this.selectedMatchIndex = e.detail.value;
            this.selectedMatch =
                this.result?.matches[this.selectedMatchIndex] || null;
        }
        onMatchClick(e) {
            this.submitting = true;
            this.clearAutocompleteMatches();
            // We only close the composebox when opening in a new tab because
            // doing so in the current tab causes a visual jitter where the
            // composebox closes before the new results page finishes loading.
            if (e && e.detail &&
                (e.detail.ctrlKey || e.detail.metaKey || e.detail.shiftKey)) {
                this.closeComposebox();
            }
        }
        /**
         * @param e Event containing index of the match that received focus.
         */
        onMatchFocusin(e) {
            // Select the match that received focus.
            this.getDropdownElement().selectIndex(e.detail.index);
        }
        onInputStateChanged(inputState) {
            this.inputState = inputState;
            const allowedTypes = this.inputState.allowedInputTypes;
            this.files.forEach((file, uuid) => {
                if (!allowedTypes.includes(file.inputType)) {
                    this.deleteFile(uuid);
                }
            });
            if (this.tabSuggestionsState === TabSuggestionsState.LOADED) {
                this.refreshTabSuggestions(/*forceRefresh=*/ true);
            }
        }
        setAimThreadRestoredTabs(tabs) {
            this.aimThreadRestoredTabs = tabs;
            this.refreshTabSuggestions(/*forceRefresh=*/ true);
            this.requestUpdate();
        }
        onAutocompleteResultChanged(result) {
            if (this.submitting) {
                return;
            }
            if (result.queryId !== this.activeQueryId) {
                return;
            }
            this.result = result;
            const hasMatches = this.result.matches.length > 0;
            const firstMatch = hasMatches ? this.result.matches[0] : null;
            // Zero suggest matches are not allowed to be default. Therefore, this
            // makes sure zero suggest results aren't focused when they are
            // returned.
            if (firstMatch && firstMatch.allowedToBeDefaultMatch) {
                this.selectFirstMatch();
            }
            else if (this.input.trim() && hasMatches && this.selectedMatchIndex >= 0 &&
                this.selectedMatchIndex < this.result.matches.length) {
                // Restore the selection and update the input. Don't restore when
                // the user deletes all their input and autocomplete is queried or
                // else the empty input will change to the value of the first
                // result.
                this.getDropdownElement().selectIndex(this.selectedMatchIndex);
                // Set the selected match since the `selectedMatchIndex` does not
                // change (and therefore `selectedMatch` does not get updated since
                // `onSelectedMatchIndexChanged_` is not called).
                this.selectedMatch = this.result.matches[this.selectedMatchIndex];
                this.input = this.selectedMatch.fillIntoEdit;
            }
            else {
                this.getDropdownElement().unselect();
            }
            // Smart compose hints should only be updated from the async response.
            // This prevents the hint flicker from an empty smart compose response
            // in the synchronous pass.
            if (this.haveReceivedSynchronousAutocompleteResponse) {
                // Populate the smart compose suggestion.
                const nextHint = this.result.smartComposeInlineHint?.trim() ?
                    this.result.smartComposeInlineHint :
                    '';
                if (this.smartComposeInlineHint !== nextHint) {
                    this.smartComposeInlineHint = nextHint;
                }
                // Smart compose stats are incremented on every response from the
                // server.
                if (this.smartComposeInlineHint) {
                    this.smartComposeStats.shownCount++;
                    this.smartComposeStats.shownLength +=
                        this.smartComposeInlineHint.length;
                }
            }
            this.haveReceivedSynchronousAutocompleteResponse = true;
        }
        onContextualInputStatusChanged(token, status, errorType) {
            // If error message is updated, then the returned file is stale and
            // removed from carousel. File is removed from carousel on
            // `kUploadReplaced` as well despite no error message being returned
            // (special case). Else, `file` below is updated to its most recent
            // state, and `errorMessage` is null.
            const { file, errorMessage } = this.updateFileStatus(token, status, errorType);
            if (errorMessage) { // `file` value is definitely stale.
                this.errorMessage = errorMessage;
                this.pendingUploads.delete(token);
                this.fileUploadsComplete = this.pendingUploads.size === 0;
            }
            else if (file) {
                // Treat `kUploadReplaced` like an error upload state
                // (like `kUploadFailed`. `kValidationFailed`,
                // `kUploadExpired`), just without setting `errorMessage`.
                // This means for `kUploadReplaced`, we do not fetch suggestions,
                // etc.
                if (file.status === ContextUploadStatus.kUploadReplaced) {
                    this.pendingUploads.delete(file.uuid);
                    this.fileUploadsComplete = this.pendingUploads.size === 0;
                    return;
                }
                else if (file.status === ContextUploadStatus.kUploadSuccessful) {
                    // At this point, due to the error message handling above (for
                    // `kValidationFailed`, `kUploadExpired`, and `kUploadFailed`),
                    // if kUploadSuccessful, the file upload is complete.
                    // Else, the file upload is in progress.
                    this.pendingUploads.delete(file.uuid);
                    this.fileUploadsComplete = this.pendingUploads.size === 0;
                    const announcer = getAnnouncerInstance();
                    announcer.announce(this.i18n('composeboxFileUploadCompleteText'));
                }
                else if (file.status === ContextUploadStatus.kProcessing ||
                    file.status ===
                        ContextUploadStatus.kProcessingSuggestSignalsReady) {
                    // `NotUploaded`, `UploadStarted` come before and after
                    // `kProcessing`
                    //  respectively, so we only need to add to `pendingUploads` when
                    //  in a type of processing state.
                    this.addToPendingUploads(file.uuid);
                }
                // Fetch contextual suggestions for processingSuggestSignalsReady
                // non-images:
                if (status === ContextUploadStatus.kProcessingSuggestSignalsReady &&
                    this.showZps && !file.type.includes('image')) {
                    // Query autocomplete to get contextual suggestions for files.
                    this.queryAutocomplete(/* clearMatches= */ true);
                }
                // For image files:
                if (status === ContextUploadStatus.kProcessingSuggestSignalsReady &&
                    file.type.includes('image')) {
                    if (this.enableImageContextualSuggestions) {
                        // Query autocomplete to get contextual suggestions for files.
                        this.queryAutocomplete(/* clearMatches= */ true);
                    }
                    else {
                        this.showDropdown = false;
                    }
                }
                // Query autocomplete to get contextual suggestions for tabs.
                if (status === ContextUploadStatus.kProcessing &&
                    file.type.includes('tab')) {
                    this.queryAutocomplete(/* clearMatches= */ true);
                }
            }
        }
        onInputInput(_e) {
            // Clear suggestInventory when the user edits query.
            this.suggestInventory = null;
            const newInput = this.getInputElement().input;
            if (this.smartComposeEnabled && this.smartComposeInlineHint) {
                if (newInput === this.input + this.smartComposeInlineHint[0]) {
                    this.smartComposeInlineHint =
                        this.smartComposeInlineHint.substring(1);
                }
                else if (newInput !== this.input) {
                    this.smartComposeInlineHint = '';
                }
            }
            this.input = newInput;
            // `clearMatches` is true if input is empty stop any in progress
            // providers before requerying for on-focus (zero-suggest) inputs. The
            // searchbox doesn't allow zero-suggest requests to be made while the
            // ACController is not done.
            if (this.composeboxNoFlickerSuggestionsFix) {
                // If the composebox no flickering fix is enabled, stop the
                // ACController from querying for suggestions when the input is
                // empty, but don't clear the matches so the dropdown doesn't close.
                if (this.input === '') {
                    this.getSearchboxHandler().stopAutocomplete(
                    /*clearResult=*/ true);
                }
                this.queryAutocomplete(/* clearMatches= */ false);
            }
            else {
                this.queryAutocomplete(/* clearMatches= */ this.input === '');
            }
        }
        onClearSmartCompose() {
            this.smartComposeInlineHint = '';
        }
        onInputFocusin() {
            if (this.submitting) {
                return;
            }
            // if there's a last queried input, it's guaranteed that at least
            // the verbatim match will exist.
            if (this.lastQueriedInput) {
                this.selectFirstMatch();
            }
        }
        onRecordingStopped(e) {
            const newTranscript = e.detail;
            if (newTranscript && newTranscript.trim().length > 0) {
                this.input = newTranscript;
                this.queryAutocomplete(/* clearMatches= */ false);
            }
            this.voiceSearchEndCleanup();
        }
        isFocusInInput() {
            return this.getActiveElement() === this.getInputElement();
        }
        finalizeMatchSelection(e) {
            this.smartComposeInlineHint = '';
            e.preventDefault();
            if (this.getActiveElement() === this.getDropdownElement()) {
                this.getDropdownElement().focusSelected();
            }
        }
        handleArrowKey_(e) {
            if (!this.dropdownNeeded) {
                return;
            }
            if (this.isFocusInInput() && !this.showDropdown) {
                return;
            }
            if (!this.hasMatches() || hasKeyModifiers(e)) {
                return;
            }
            if (e.key === 'ArrowDown') {
                this.getDropdownElement().selectNext();
            }
            else if (e.key === 'ArrowUp') {
                this.getDropdownElement().selectPrevious();
            }
            this.finalizeMatchSelection(e);
        }
        handleTab_(e) {
            if (this.isFocusInInput()) {
                // If focus leaves the input, unselect the first match.
                if (e.shiftKey) {
                    this.getDropdownElement().unselect();
                }
                else if (this.smartComposeEnabled && this.smartComposeInlineHint) {
                    this.smartComposeStats.acceptedCount++;
                    this.smartComposeStats.charactersAccepted +=
                        this.smartComposeInlineHint.length;
                    this.getSearchboxHandler().setInputMethod(InputMethod.kSmartCompose);
                    this.input = this.input + this.smartComposeInlineHint;
                    this.smartComposeInlineHint = '';
                    e.preventDefault();
                    this.queryAutocomplete(/* clearMatches= */ false);
                }
                return;
            }
            if (this.hasMatches() && this.dropdownNeeded && !hasKeyModifiers(e)) {
                // If focus goes past the last match, unselect the last match.
                if (this.selectedMatchIndex === this.result.matches.length - 1) {
                    if (this.selectedMatch.supportsDeletion) {
                        const focusedMatchElem = this.getActiveElement()?.shadowRoot?.activeElement;
                        const focusedButtonElem = focusedMatchElem?.shadowRoot?.activeElement;
                        if (focusedButtonElem?.id === 'remove') {
                            this.getDropdownElement().unselect();
                        }
                    }
                    else {
                        this.getDropdownElement().unselect();
                    }
                }
            }
        }
        handleEnter_(e) {
            if (this.getActiveElement() === this.getDropdownElement() ||
                !e.shiftKey) {
                e.preventDefault();
                if (this.canSubmitFilesAndInput) {
                    this.submitQuery(e);
                }
            }
        }
        handleEscape_(e) {
            this.handleEscapeKeyLogic();
            e.stopPropagation();
            e.preventDefault();
        }
        handlePageNavigation_(e) {
            if (!this.hasMatches() || !this.dropdownNeeded ||
                hasKeyModifiers(e)) {
                return;
            }
            if (e.key === 'PageUp') {
                this.selectFirstMatch();
            }
            else {
                this.getDropdownElement().selectLast();
            }
            this.finalizeMatchSelection(e);
        }
        onKeydown(e) {
            const HANDLED_KEYS = [
                'ArrowDown',
                'ArrowUp',
                'Enter',
                'Escape',
                'PageDown',
                'PageUp',
                'Tab',
            ];
            if (!HANDLED_KEYS.includes(e.key)) {
                return;
            }
            if (e.isComposing) {
                return;
            }
            const handlers = {
                'ArrowDown': (e) => this.handleArrowKey_(e),
                'ArrowUp': (e) => this.handleArrowKey_(e),
                'Enter': (e) => this.handleEnter_(e),
                'Escape': (e) => this.handleEscape_(e),
                'Tab': (e) => this.handleTab_(e),
                'PageUp': (e) => this.handlePageNavigation_(e),
                'PageDown': (e) => this.handlePageNavigation_(e),
            };
            handlers[e.key](e);
        }
        updateInputPlaceholder() {
            if (this.inputState) {
                if (this.inputState.activeTool !== ToolMode.kUnspecified) {
                    const config = this.inputState.toolConfigs.find(c => c.tool === this.inputState.activeTool);
                    if (config?.hintText) {
                        this.inputPlaceholder = config.hintText;
                        return;
                    }
                }
                if (this.inputState.activeModel !== ModelMode.kUnspecified) {
                    const config = this.inputState.modelConfigs.find(c => c.model === this.inputState.activeModel);
                    if (config?.hintText) {
                        this.inputPlaceholder = config.hintText;
                        return;
                    }
                }
                if (this.inputState.hintText) {
                    this.inputPlaceholder = this.inputState.hintText;
                    return;
                }
            }
            if (this.inputState?.activeTool === ToolMode.kDeepSearch) {
                this.inputPlaceholder =
                    loadTimeData.getString('composeDeepSearchPlaceholder');
            }
            else if (this.inputState?.activeTool === ToolMode.kImageGen) {
                this.inputPlaceholder =
                    loadTimeData.getString('composeCreateImagePlaceholder');
            }
            else {
                this.inputPlaceholder =
                    loadTimeData.getString('searchboxComposePlaceholder');
            }
        }
        isTogglingOff(tool) {
            return this.inputState?.activeTool === tool;
        }
        onToolClick(e) {
            if (!this.isTogglingOff(e.detail.toolMode)) {
                recordToolModeSelection(e.detail.toolMode, this.composeboxSource, 'AimPopup');
            }
            this.handleToolClick(e.detail.toolMode);
        }
        handleToolClick(tool) {
            const isTogglingOff = this.isTogglingOff(tool);
            const newToolMode = isTogglingOff ? ToolMode.kUnspecified : tool;
            if (isTogglingOff) {
                const metricName = `ContextualSearch.UserAction.InputStateDeletion.${this.composeboxSource}`;
                recordEnumerationValue(metricName, ContextualSearchInputStateDeletionType.TOOL, ContextualSearchInputStateDeletionType.MAX_VALUE + 1);
                const userActionName = `ContextualSearch.UserAction.InputStateDeletion.Tool.${this.composeboxSource}`;
                recordUserAction(userActionName);
            }
            else {
                this.getSearchboxHandler().recordToolSelectionAction(newToolMode);
            }
            this.handleToolModeUpdate(newToolMode);
        }
        handleToolModeUpdate(newTool) {
            this.getSearchboxHandler().setActiveToolMode(newTool);
            this.queryAutocomplete(/* clearMatches= */ true);
            this.updateInputPlaceholder();
        }
        onModelClick(e) {
            recordModelModeSelection(e.detail.model, this.composeboxSource, 'AimPopup');
            this.getSearchboxHandler().recordModelSelectionAction(e.detail.model);
            this.getSearchboxHandler().setActiveModelMode(e.detail.model);
            this.updateInputPlaceholder();
        }
        onOpenImageUpload() {
            recordContextualElementClickedMetric(this.composeboxSource, 'AimPopup', ContextType.IMAGE);
        }
        onOpenFileUpload() {
            recordContextualElementClickedMetric(this.composeboxSource, 'AimPopup', ContextType.FILE);
        }
        async onOpenDriveUpload() {
            recordContextualElementClickedMetric(this.composeboxSource, 'AimPopup', ContextType.DRIVE);
            // Check if the user has accepted the Drive disclaimer. This handles
            // the edge case where a user sees the drive option in the menu, but
            // then revokes Drive permissions.
            const { status } = await this.getSearchboxHandler().getDriveDisclaimerStatus();
            // Abort only if the account is restricted/ineligible. If unconsented
            // (kNotAccepted), allow the click through so onDriveUploadClicked
            // can trigger the ConsentKit disclaimer onboarding dialog.
            if (status === DriveDisclaimerStatus.kRestricted) {
                return;
            }
            const { response } = await this.getSearchboxHandler().onDriveUploadClicked();
            this.addDriveUploads(response.files, response.error ?? undefined);
        }
        onSmartTabSharingActiveChanged(_e) {
            // 
            this.smartTabSharingActive = _e.detail.active;
            ComposeboxProxyImpl.getInstance().setSmartTabSharingActive(_e.detail.active);
            // 
        }
        onContextMenuContainerMousedown(e) {
            // Special treatment for the "Tall" layout variants where not clicking
            // on an inner element should be treated as clicking on a
            // non-focusable area.
            if (this.searchboxLayoutMode !== 'Compact' &&
                (e.target instanceof HTMLElement &&
                    e.target.id === 'contextMenuContainer')) {
                e.preventDefault();
                e.stopPropagation();
            }
        }
        onContextMenuContainerClick(e) {
            e.preventDefault();
            e.stopPropagation();
            // Ignore non-primary button clicks.
            if (e.button !== 0) {
                return;
            }
            if (this.searchboxLayoutMode !== 'Compact') {
                this.focusInput();
            }
        }
        async onDeleteTabContext(e) {
            const tabId = e.detail.tabId;
            const token = this.addedTabsIds.get(tabId);
            if (token) {
                this.deleteFile(token, e.detail.fromUserAction);
            }
            else {
                this.getSearchboxHandler().deleteTabContext(tabId);
            }
            this.aimThreadRestoredTabs =
                this.aimThreadRestoredTabs.filter(tab => tab.tabId !== tabId);
            await this.updateComplete;
            this.keepMenuOpenForMultiSelection();
        }
        async onAddTabContext(e) {
            if (!this.browserTabContextAdded) {
                recordContextualElementClickedMetric(this.composeboxSource, 'AimPopup', ContextType.TAB);
                this.browserTabContextAdded = true;
            }
            await this.addTabContextHandleCallback({
                tabId: e.detail.id,
                title: e.detail.title,
                url: e.detail.url,
                delayUpload: e.detail.delayUpload,
                origin: e.detail.origin,
            });
            await this.updateComplete;
            // Only keep menu open if it was from menu and not recent tab.
            if (e.detail.origin === TabUploadOrigin.CONTEXT_MENU) {
                this.keepMenuOpenForMultiSelection();
            }
        }
        onShareTabsFlyoutOpenChanged(e) {
            this.shareTabsFlyoutOpen = e.detail.open;
        }
        shouldShowSuggestionActivityLink() {
            const showActivityLink = this.result && this.showDropdown &&
                this.result.matches.some((match) => match.isNoncannedAimSuggestion);
            this.fire('show-suggestion-activity-link', showActivityLink);
            return !!showActivityLink;
        }
        onLinkClicked(e) {
            // Manually handle navigation to support WebView environments where
            // default link clicks may be ignored.
            e.detail.event.preventDefault();
            const href = e.detail.event.currentTarget.href;
            if (href) {
                this.getPageHandler().navigateUrl(href);
            }
        }
        async addTabContextHandleCallback(tabUpload, _replaceAutoActiveTabToken = false, onBeforeUpdateFiles) {
            try {
                const token = await this.getSearchboxHandler().addTabContext(tabUpload.tabId, tabUpload.delayUpload);
                if (!token) {
                    return null;
                }
                const attachment = ComposeboxFile.createFromTab(token, tabUpload.tabId, tabUpload.title, tabUpload.url, { supportsUnimodal: true });
                if (onBeforeUpdateFiles) {
                    onBeforeUpdateFiles(attachment);
                }
                this.files = new Map([
                    ...this.files.entries(),
                    [attachment.uuid, attachment],
                ]);
                this.addedTabsIds = new Map([
                    ...this.addedTabsIds.entries(),
                    [tabUpload.tabId, attachment.uuid],
                ]);
                this.focusInput();
                return attachment;
            }
            catch (e) {
                const err = e;
                if (FILE_VALIDATION_ERRORS_MAP.has(err)) {
                    this.errorMessage =
                        this.i18n(FILE_VALIDATION_ERRORS_MAP.get(err));
                }
                return null;
            }
        }
        async keepMenuOpenForMultiSelection() {
            // Conditionally keep menu open only if context management is enabled.
            // Otherwise, always keep menu open.
            if (this.contextManagementInComposeboxEnabled &&
                !this.keepMenuOpenOnTabSelect) {
                return;
            }
            this.shareTabsFlyoutOpen = true;
            await this.updateComplete;
            const entrypointAndMenu = this.getContextEntrypointElement();
            if (entrypointAndMenu) {
                entrypointAndMenu
                    .openMenuForMultiSelection();
            }
        }
        async updateState(state) {
            if (!this.inputState) {
                const inputStateResponse = await this.getSearchboxHandler().getInputState();
                // Check if a newer updateState is running and we can quit.
                if (state !== this.state) {
                    return;
                }
                if (inputStateResponse) {
                    this.inputState = inputStateResponse.state;
                }
            }
            this.suggestInventory = state.suggestInventory ?? null;
            const text = state.text || '';
            const files = state.files || [];
            const mode = state.mode ?? ToolMode.kUnspecified;
            let model = state.model ?? ModelMode.kUnspecified;
            this.activeQueryId = -1;
            if (text) {
                this.input = text;
                this.lastQueriedInput = text;
            }
            if (this.showZps && files.length === 0) {
                this.queryAutocomplete(/* clearMatches= */ false);
            }
            if (files.length > 0 || state.error !== undefined) {
                const dataTransfer = new DataTransfer();
                const driveUploads = [];
                for (const file of files) {
                    if ('tabId' in file) {
                        if (file.origin === TabUploadOrigin.CONTEXT_MENU) {
                            this.keepMenuOpenForMultiSelection();
                        }
                        this.addTabContextHandleCallback({
                            tabId: file.tabId,
                            title: file.title,
                            url: file.url,
                            delayUpload: file.delayUpload,
                            origin: file.origin,
                        }, 
                        /*replaceAutoActiveTabToken=*/ false);
                    }
                    else if ('mimeType' in file) {
                        driveUploads.push(file);
                    }
                    else {
                        dataTransfer.items.add(file.file);
                    }
                }
                this.processFiles(dataTransfer.files);
                if (driveUploads.length > 0 || state.error !== undefined) {
                    this.addDriveUploads(driveUploads, state.error);
                }
            }
            if (mode !== ToolMode.kUnspecified) {
                this.handleToolClick(mode);
            }
            if (!!this.inputState && model === ModelMode.kUnspecified &&
                this.inputState.allowedModels.length > 0) {
                model = this.inputState.allowedModels[0];
            }
            this.getSearchboxHandler().setActiveModelMode(model);
            this.updateInputPlaceholder();
            await this.updateComplete;
        }
        async onContextMenuClosed() {
            this.contextMenuOpened = false;
            // Force reset of the flyout open state so that the flyout is closed
            // when the main context menu is reopened.
            this.shareTabsFlyoutOpen = false;
            await this.updateComplete;
            this.focusInput();
        }
        onContextMenuOpened() {
            this.browserTabContextAdded = false;
            this.contextMenuOpened = true;
            if (this.tabSuggestionsState === TabSuggestionsState.LOADED) {
                const selectedTabIds = new Set(this.addedTabsIds.keys());
                const restoredTabIds = this.contextManagementInComposeboxEnabled ?
                    new Set((this.aimThreadRestoredTabs || []).map(tab => tab.tabId)) :
                    new Set();
                const selected = this.tabSuggestions.filter(tab => selectedTabIds.has(tab.tabId));
                const restored = this.tabSuggestions.filter(tab => restoredTabIds.has(tab.tabId) &&
                    !selectedTabIds.has(tab.tabId));
                const other = this.tabSuggestions.filter(tab => !selectedTabIds.has(tab.tabId) &&
                    !restoredTabIds.has(tab.tabId));
                this.tabSuggestions = [...selected, ...restored, ...other];
                if (this.inputState) {
                    const { allowedInputTypes, disabledInputTypes } = this.inputState;
                    if (allowedInputTypes.includes(InputType.kBrowserTab) &&
                        !disabledInputTypes.includes(InputType.kBrowserTab) &&
                        this.tabSuggestions.length > 0) {
                        recordInputTypeShown(InputType.kBrowserTab, this.composeboxSource, 'AimPopup');
                    }
                }
            }
            else {
                this.refreshTabSuggestions();
            }
            this.getPageHandler().onContextMenuOpened();
            if (this.inputState) {
                const { allowedInputTypes, disabledInputTypes } = this.inputState;
                allowedInputTypes.forEach((inputType) => {
                    if (inputType !== InputType.kBrowserTab &&
                        !disabledInputTypes.includes(inputType)) {
                        recordInputTypeShown(inputType, this.composeboxSource, 'AimPopup');
                    }
                });
                const { allowedTools, disabledTools } = this.inputState;
                allowedTools.forEach((tool) => {
                    if (!disabledTools.includes(tool)) {
                        recordToolModeShown(tool, this.composeboxSource, 'AimPopup');
                    }
                });
                const { allowedModels, disabledModels } = this.inputState;
                allowedModels.forEach((model) => {
                    if (!disabledModels.includes(model)) {
                        recordModelModeShown(model, this.composeboxSource, 'AimPopup');
                    }
                });
            }
        }
        onVoiceSearchButtonClick() {
            const isSystemVoiceSearchEnabled = loadTimeData.valueExists('isSystemVoiceSearchEnabled') &&
                loadTimeData.getBoolean('isSystemVoiceSearchEnabled');
            if (isSystemVoiceSearchEnabled) {
                this.getPageHandler().startPlatformVoiceRecognition();
                return;
            }
            if (this.inVoiceSearchMode) {
                return;
            }
            this.inVoiceSearchMode = true;
            this.hasVoiceSearchError = false;
            this.animationState = GlowAnimationState.LISTENING;
            this.fire('voice-search-action', { value: VoiceSearchAction.ACTIVATE });
            // For contextual tasks composebox voice metrics.
            this.fire('composebox-voice-search-start');
            this.shadowRoot
                ?.querySelector('cr-composebox-voice-search')
                ?.start();
        }
        onFileChange(e) {
            this.processFiles(e.detail.files);
            recordContextAdditionMethod(0 /* ComposeboxContextAddedMethod.CONTEXT_MENU */, this.composeboxSource);
        }
        onPaste(event) {
            if (!this.dragAndDropEnabled || !event.clipboardData?.items) {
                return;
            }
            const dataTransfer = new DataTransfer();
            for (const item of event.clipboardData.items) {
                if (item.kind === 'file') {
                    const file = item.getAsFile();
                    if (file) {
                        dataTransfer.items.add(file);
                    }
                }
            }
            const fileList = dataTransfer.files;
            if (fileList.length > 0) {
                event.preventDefault();
                this.processFiles(fileList);
                recordContextAdditionMethod(1 /* ComposeboxContextAddedMethod.COPY_PASTE */, this.composeboxSource);
            }
        }
        onDeleteFile(e) {
            this.deleteFile(e.detail.uuid, e.detail.fromUserAction);
        }
        onCancelClick() {
            if (this.hasContent()) {
                this.resetModes();
                this.clearAllInputs(
                /* querySubmitted= */ false, 
                /* shouldBlockAutoSuggestedTabs= */ true);
                this.focusInput();
                this.queryAutocomplete(/* clearMatches= */ true);
                if (!this.disableCaretColorAnimation) {
                    this.getInputElement().resetCaret();
                }
            }
            else {
                this.closeComposebox();
            }
        }
        onSubmitFocusin(_e) {
            // Matches should always be greater than 0 due to verbatim match.
            if (this.input && !this.selectedMatch) {
                this.selectFirstMatch();
            }
        }
        onSubmitClick(e) {
            if (this.hasFiles() ||
                this.inputState?.activeTool !== ToolMode.kUnspecified) {
                this.getPageHandler().notifyComposeboxQuerySubmittedWithContext();
            }
            this.submitQuery(e);
        }
        // =====================================================================
        // Common helper methods
        // =====================================================================
        addToPendingUploads(uuid) {
            this.pendingUploads.add(uuid);
            this.fileUploadsComplete = false;
        }
        closeMenu() {
            const entrypointAndMenu = this.getContextEntrypointElement();
            if (entrypointAndMenu instanceof ContextualEntrypointAndMenuElement) {
                entrypointAndMenu.closeMenu();
            }
        }
        deleteFileContext(uuidToDelete, fromAutoSuggestedChip = false) {
            this.files = new Map([...this.files.entries()].filter(([uuid, _]) => uuid !== uuidToDelete));
            this.pendingUploads.delete(uuidToDelete);
            this.fileUploadsComplete = this.pendingUploads.size === 0;
            this.getSearchboxHandler().deleteContext(uuidToDelete, fromAutoSuggestedChip);
        }
        deleteFile(uuidToDelete, fromUserAction, fromAutoSuggestedChip = false) {
            const file = uuidToDelete ? this.files.get(uuidToDelete) : null;
            if (!file) {
                return null;
            }
            if (file?.tabId) {
                this.addedTabsIds = new Map([...this.addedTabsIds.entries()].filter(([id, _]) => id !== file.tabId));
            }
            if (fromUserAction === true) {
                const isTab = !!file?.tabId;
                const deletionType = isTab ?
                    ContextualSearchInputStateDeletionType.TAB :
                    ContextualSearchInputStateDeletionType.FILE;
                const metricName = `ContextualSearch.UserAction.InputStateDeletion.${this.composeboxSource}`;
                recordEnumerationValue(metricName, deletionType, ContextualSearchInputStateDeletionType.MAX_VALUE + 1);
                const typeStr = isTab ? 'Tab' : 'File';
                const userActionName = `ContextualSearch.UserAction.InputStateDeletion.${typeStr}.${this.composeboxSource}`;
                recordUserAction(userActionName);
            }
            this.deleteFileContext(uuidToDelete, fromAutoSuggestedChip);
            this.focusInput();
            return file;
        }
        focusInput() {
            this.getInputElement().inputElement.focus();
        }
        hasContent() {
            return this.inputState?.activeTool !== ToolMode.kUnspecified ||
                this.input.trim().length > 0 || this.files.size > 0;
        }
        clearInput() {
            this.input = '';
            this.activeQueryId = -1;
            this.lastQueriedInput = '';
            this.getDropdownElement().unselect();
        }
        clearAllInputs(querySubmitted, shouldBlockAutoSuggestedTabs) {
            this.clearInput();
            this.getInputElement().resetHeight();
            // Let `querySubmit` handle clearing files if the tool mode is a tool
            // mode that should be cleared after submitting. For all other general
            // clearing, clear input here.
            if (!querySubmitted) {
                this.resetModes();
                // If context management flag is on, do not delete persisted
                // (restored) tabs unless the source is Omnibox.
                if (this.composeboxSource === 'Omnibox' ||
                    !this.contextManagementInComposeboxEnabled) {
                    this.resetRestoredTabs();
                }
            }
            // `undeletableFiles` is for files; `SubmitCleanup()` still deletes
            // TABS only after this, regardless of `undeletableFiles`. Adding tabs
            // to `undeletableFiles` does nothing to prevent deletion from
            // `this.files`. Adding files to `undeletableFiles` does prevent
            // deletion.
            const undeletableFiles = Array.from(this.files.values()).filter(file => !file.isDeletable);
            if (undeletableFiles.length !== this.files.size) {
                this.files =
                    new Map(undeletableFiles.map(file => [file.uuid, file]));
                this.addedTabsIds =
                    new Map(undeletableFiles.filter(file => file.tabId)
                        .map(file => [file.tabId, file.uuid]));
            }
            // Reset files in set to match remaining files in carousel that are
            // still uploading.
            this.pendingUploads = new Set(Array.from(this.files.values())
                .filter(file => !isContextUploadStatusTerminal(file.status))
                .map(file => file.uuid));
            this.smartComposeInlineHint = '';
            this.resetSmartComposeStats();
            // Ask the searchbox handler to clear its own state when the clear all
            // button is clicked. Otherwise, it will clear its own state when mojo submit
            // query is sent.
            // TODO(crbug.com/532712756): Browser process should fully own
            // clearing logic, and frontend should listen and follow which files
            // browser process says to keep. This is better than having to
            // maintain and align two different hard coded logic for clearing
            // files across composebox (here) and browser process code (session
            // handle). `restoredTabs` is the server telling the frontend which tabs
            // are persistent, but the logic for clearing context on submit/clear all
            // should still be handled by browser process, not frontend and browser process.
            if (!querySubmitted) {
                this.getSearchboxHandler().clearFiles(shouldBlockAutoSuggestedTabs);
            }
            this.fileUploadsComplete = this.pendingUploads.size === 0;
            if (this.inVoiceSearchMode) {
                this.voiceSearchEndCleanup();
            }
        }
        handleProcessFilesError(error) {
            if (error === ProcessFilesError.NONE) {
                return;
            }
            let metric = ComposeboxFileValidationError.NONE;
            switch (error) {
                case ProcessFilesError.MAX_FILES_EXCEEDED:
                    metric = ComposeboxFileValidationError.TOO_MANY_FILES;
                    this.errorMessage = this.i18n('maxFilesReachedError');
                    break;
                case ProcessFilesError.MAX_IMAGES_EXCEEDED:
                    metric = ComposeboxFileValidationError.TOO_MANY_FILES;
                    this.errorMessage = this.i18n('maxImagesReachedError');
                    break;
                case ProcessFilesError.MAX_PDFS_EXCEEDED:
                    metric = ComposeboxFileValidationError.TOO_MANY_FILES;
                    this.errorMessage = this.i18n('maxPdfsReachedError');
                    break;
                case ProcessFilesError.FILE_EMPTY:
                    metric = ComposeboxFileValidationError.FILE_EMPTY;
                    this.errorMessage =
                        this.i18n('composeboxFileUploadInvalidEmptySize');
                    break;
                case ProcessFilesError.FILE_TOO_LARGE:
                    metric = ComposeboxFileValidationError.FILE_SIZE_TOO_LARGE;
                    const unitMiB = 1024 * 1024;
                    this.errorMessage = this.i18n('composeboxFileUploadInvalidTooLarge', Math.floor(this.maxFileSize / unitMiB));
                    break;
                case ProcessFilesError.INVALID_TYPE:
                    this.errorMessage = this.i18n('composeFileTypesAllowedError');
                    break;
                case ProcessFilesError.FILE_UPLOAD_NOT_ALLOWED:
                    this.errorMessage = this.i18n('composeboxFileUploadNotAllowed');
                    break;
                default:
                    break;
            }
            this.recordFileValidationMetric(metric);
            this.closeMenu();
        }
        isFileAllowed(fileType) {
            if (this.lensSendRawFileMediaTypesEnabled) {
                return true;
            }
            return this.isMimeTypeAllowed(fileType, this.imageFileTypes) ||
                this.isMimeTypeAllowed(fileType, this.attachmentFileTypes);
        }
        isMimeTypeAllowed(mimeType, allowedTypes) {
            const lowerMimeType = mimeType.toLowerCase();
            return allowedTypes.some(type => {
                if (type.endsWith('/*')) {
                    const prefix = type.slice(0, -1);
                    return lowerMimeType.startsWith(prefix);
                }
                return lowerMimeType === type;
            });
        }
        getInputType(type) {
            if (type === 'tab') {
                return InputType.kBrowserTab;
            }
            if (type === 'image') {
                return InputType.kLensImage;
            }
            if (type === 'pdf') {
                return InputType.kLensFile;
            }
            if (this.imageFileTypes.some(t => {
                if (t.endsWith('/*')) {
                    const prefix = t.slice(0, -1);
                    return type.startsWith(prefix);
                }
                return type === t;
            })) {
                return InputType.kLensImage;
            }
            // Arbitrary file types are treated as Lens files.
            return InputType.kLensFile;
        }
        resetModes() {
            const previousTool = this.inputState?.activeTool;
            this.uploadButtonDisabled = false;
            if (previousTool !== ToolMode.kUnspecified) {
                this.showContextMenuDescription =
                    this.contextMenuDescriptionEnabled;
                this.handleToolModeUpdate(ToolMode.kUnspecified);
            }
        }
        resetRestoredTabs() {
            this.aimThreadRestoredTabs = [];
            this.hasCachedSubmittedTabsThisTurn = false;
        }
        setDefaultModel() {
            if (this.inputState?.activeModel &&
                this.inputState.activeModel !==
                    ModelMode.kUnspecified) {
                this.getSearchboxHandler().setActiveModelMode(this.inputState.activeModel);
            }
            else if (this.inputState?.allowedModels &&
                this.inputState.allowedModels.length > 0) {
                this.getSearchboxHandler().setActiveModelMode(this.inputState.allowedModels[0]);
            }
        }
        resetToolsAndModels() {
            if (this.inputState) {
                this.getSearchboxHandler().setActiveToolMode(ToolMode.kUnspecified);
                this.getSearchboxHandler().setActiveModelMode(ModelMode.kUnspecified);
            }
        }
        closeDropdown() {
            this.clearAutocompleteMatches();
        }
        closeComposebox() {
            this.resetModes();
            this.getSearchboxHandler().clearFiles(
            /*shouldBlockAutoSuggestedTabs=*/ false);
            this.resetToolsAndModels();
            this.suggestInventory = null;
            this.fire('close-composebox', { composeboxText: this.input });
        }
        handleEscapeKeyLogic() {
            if (!this.closeOnEscape && this.hasContent()) {
                this.resetModes();
                this.clearAllInputs(
                /* querySubmitted= */ false, 
                /* shouldBlockAutoSuggestedTabs= */ false);
                this.focusInput();
                this.queryAutocomplete(/* clearMatches= */ true);
            }
            else {
                this.closeComposebox();
            }
        }
        submitQuery(e) {
            if (!this.canSubmitFilesAndInput || !this.hasValidQuery()) {
                return;
            }
            // Submissions do not need a mouse or keyboard event to be submitted.
            // For example, inputs that are injected into the composebox can be
            // set to submit immediately after injection.
            const mouseButton = e?.button ?? 0;
            const altKey = e?.altKey ?? false;
            const ctrlKey = e?.ctrlKey ?? false;
            const metaKey = e?.metaKey ?? false;
            const shiftKey = e?.shiftKey ?? false;
            // If there is a match that is selected, open that match, else follow
            // the non-autocomplete submission flow. The non-autocomplete
            // submission flow will not have omnibox metrics recorded for it.
            if (this.selectedMatchIndex >= 0) {
                const match = this.result.matches[this.selectedMatchIndex];
                assert(match);
                this.getSearchboxHandler().setSmartComposeStats(this.smartComposeStats);
                const viaKeyboard = !!e && e instanceof KeyboardEvent;
                this.getSearchboxHandler().openAutocompleteMatch(this.selectedMatchIndex, match.destinationUrl, 
                /* are_matches_showing */ true, mouseButton, altKey, ctrlKey, metaKey, shiftKey, viaKeyboard);
            }
            else {
                this.getSearchboxHandler().submitQuery(this.input.trim(), mouseButton, altKey, ctrlKey, metaKey, shiftKey, /*isVoiceSearch=*/ false);
            }
            this.submitCleanup();
            // Only close the composebox when opening in a new tab because
            // doing so in the current tab causes a visual jitter where the
            // composebox closes before the new results page finishes loading.
            if (ctrlKey || metaKey || shiftKey) {
                this.closeComposebox();
            }
        }
        cacheSubmittedTabs() {
            if (!this.contextManagementInComposeboxEnabled) {
                return;
            }
            if (this.hasCachedSubmittedTabsThisTurn) {
                return;
            }
            if (this.addedTabsIds && this.addedTabsIds.size > 0) {
                const submittedTabs = this.getSharedTabs();
                if (submittedTabs.length > 0) {
                    this.aimThreadRestoredTabs = [
                        ...this.aimThreadRestoredTabs,
                        ...submittedTabs,
                    ];
                    this.hasCachedSubmittedTabsThisTurn = true;
                }
            }
        }
        submitCleanup() {
            this.submitting = true;
            this.clearAutocompleteMatches();
            this.resetSmartComposeStats();
            this.animationState = GlowAnimationState.SUBMITTING;
            this.cacheSubmittedTabs();
            if (this.addedTabsIds && this.addedTabsIds.size > 0) {
                const activeTabsArray = Array.from(this.addedTabsIds.keys());
                for (const tabId of activeTabsArray) {
                    const token = this.addedTabsIds.get(tabId);
                    if (token) {
                        this.files.delete(token);
                        this.addedTabsIds.delete(tabId);
                    }
                }
                this.files = new Map(this.files);
                this.addedTabsIds = new Map(this.addedTabsIds);
            }
            // Standard behavior: clear inputs if flag is enabled
            if (this.clearAllInputsWhenSubmittingQuery) {
                this.clearAllInputs(
                /* querySubmitted= */ true, 
                /* shouldBlockAutoSuggestedTabs= */ false);
            }
            this.fire('composebox-submit');
            this.hasCachedSubmittedTabsThisTurn = false;
        }
        hasImageFiles() {
            return Array.from(this.files.values())
                .some(file => file.type.includes('image'));
        }
        hasMatches() {
            return !!(this.result && this.result.matches.length > 0);
        }
        selectFirstMatch() {
            if (this.result?.matches.length) {
                this.getDropdownElement().selectFirst();
            }
        }
        hasFiles() {
            return this.files.size > 0;
        }
        resetSmartComposeStats() {
            this.smartComposeStats = {
                enabled: loadTimeData.getBoolean('composeboxSmartComposeEnabled'),
                shownCount: 0,
                acceptedCount: 0,
                charactersAccepted: 0,
                shownLength: 0,
            };
        }
        queryAutocomplete(clearMatches) {
            if (clearMatches) {
                this.clearAutocompleteMatches();
            }
            this.activeQueryId = this.nextQueryId_++;
            this.lastQueriedInput = this.input;
            this.haveReceivedSynchronousAutocompleteResponse = false;
            // Get the cursor position from the DOM. Since DOM updates are async
            // in lit, if the input was set via code rather than user interaction,
            // the cursor position fetched from the dom would be stale, so use the
            // text length instead, since that's what the dom cursor position will
            // be set to once the update propagates.
            const cursorPosition = this.getInputElement().inputElement.value === this.input ?
                this.getInputElement().inputElement.selectionStart || 0 :
                this.input.length;
            this.getSearchboxHandler().queryAutocompleteWithSuggestInventory(this.activeQueryId, this.input, false, cursorPosition, this.suggestInventory ?? SuggestInventory.kDefault);
        }
        clearAutocompleteMatches() {
            this.showDropdown = false;
            this.result = null;
            this.getDropdownElement().unselect();
            this.getSearchboxHandler().stopAutocomplete(/*clearResult=*/ true);
            // Autocomplete sends updates once it is stopped. Invalidate those
            // results by setting `activeQueryId` to -1.
            this.activeQueryId = -1;
            this.lastQueriedInput = '';
        }
        computeSubmitEnabled() {
            // `submitEnabled` controls the visibility of the submit button.
            // Since files can be added but technically not be submittable (like
            // injected inputs), this needs to check if any files are present to
            // show the submit button. The button will still appear disabled
            // because that is controlled by `canSubmitFilesAndInput`.
            return this.hasValidQuery() || this.files.size > 0;
        }
        hasValidQuery() {
            // If there is at least one file that supports unimodal search, the
            // query is valid.
            if (Array.from(this.files.values())
                .some((file) => file.supportsUnimodal)) {
                return true;
            }
            // If an autocomplete match is selected, it's a valid query.
            if (this.selectedMatchIndex >= 0 && !!this.result) {
                return true;
            }
            // If there is non-empty text input.
            if (this.input.trim().length > 0) {
                return true;
            }
            return false;
        }
        recordFileValidationMetric(enumValue) {
            recordEnumerationValue('ContextualSearch.File.WebUI.UploadAttemptFailure.' +
                this.composeboxSource, enumValue, ComposeboxFileValidationError.MAX_VALUE + 1);
        }
        async addFileContext(files) {
            const composeboxFiles = new Map();
            for (const file of files) {
                const fileBuffer = await file.arrayBuffer();
                const bigBuffer = {
                    bytes: Array.from(new Uint8Array(fileBuffer)),
                };
                let token;
                try {
                    token = await this.getSearchboxHandler().addFileContext({
                        fileName: file.name,
                        imageDataUrl: null,
                        mimeType: file.type,
                        isDeletable: true,
                        selectionTime: new Date(),
                        thumbnailUrl: null,
                    }, bigBuffer);
                }
                catch (e) {
                    const err = e;
                    if (FILE_VALIDATION_ERRORS_MAP.has(err)) {
                        this.errorMessage =
                            this.i18n(FILE_VALIDATION_ERRORS_MAP.get(err));
                    }
                    continue;
                }
                const attachment = ComposeboxFile.createFromFile(token, file, ContextUploadStatus.kNotUploaded, {
                    dataUrl: null,
                    objectUrl: (file.type.includes('image') ||
                        file.type.includes('video')) ?
                        URL.createObjectURL(file) :
                        null,
                    iconName: null,
                    supportsUnimodal: true,
                });
                composeboxFiles.set(token, attachment);
                const announcer = getAnnouncerInstance();
                announcer.announce(this.i18n('composeboxFileUploadStartedText'));
            }
            this.files =
                new Map([...this.files.entries(), ...composeboxFiles.entries()]);
            this.recordFileValidationMetric(ComposeboxFileValidationError.NONE);
            this.focusInput();
        }
        addFileContextFromBrowser(uuid, fileInfo) {
            const attachment = {
                uuid: uuid,
                name: fileInfo.fileName,
                dataUrl: fileInfo.imageDataUrl ?? null,
                thumbnailUrl: fileInfo.thumbnailUrl ?? null,
                objectUrl: null,
                type: fileInfo.mimeType || (fileInfo.imageDataUrl ? 'image' : ''),
                inputType: fileInfo.imageDataUrl ? InputType.kLensImage :
                    InputType.kLensFile,
                status: fileInfo.imageDataUrl ?
                    ContextUploadStatus.kUploadSuccessful :
                    ContextUploadStatus.kNotUploaded,
                url: null,
                tabId: null,
                isDeletable: fileInfo.isDeletable,
                iconName: null,
                supportsUnimodal: true,
            };
            this.onFileContextAdded(attachment);
        }
        addDroppedFiles(files) {
            this.processFiles(files);
            recordContextAdditionMethod(2 /* ComposeboxContextAddedMethod.DRAG_AND_DROP */, this.composeboxSource);
        }
        addDriveUploads(driveUploads, error) {
            // Handle any pre-existing error passed in (e.g., from the NTP)
            if (error === DriveUploadError.kMaxFilesExceeded) {
                this.handleProcessFilesError(ProcessFilesError.MAX_FILES_EXCEEDED);
            }
            else if (error === DriveUploadError.kSizeLimitExceeded) {
                this.handleProcessFilesError(ProcessFilesError.FILE_TOO_LARGE);
            }
            // Add the successful files to the Composebox
            const composeboxFiles = new Map();
            for (const file of driveUploads) {
                const attachment = ComposeboxFile.createFromFile(file.token, { name: file.fileName, type: file.mimeType }, ContextUploadStatus.kNotUploaded, {
                    dataUrl: file.thumbnailUrl ?? null,
                    objectUrl: null,
                    iconName: null,
                    supportsUnimodal: true,
                    thumbnailUrl: file.thumbnailUrl ?? null,
                    iconUrl: file.iconUrl ?? null,
                });
                composeboxFiles.set(file.token, attachment);
                const announcer = getAnnouncerInstance();
                announcer.announce(this.i18n('composeboxFileUploadStartedText'));
            }
            if (composeboxFiles.size > 0) {
                this.files = new Map([
                    ...this.files.entries(),
                    ...composeboxFiles.entries(),
                ]);
                this.recordFileValidationMetric(ComposeboxFileValidationError.NONE);
                this.focusInput();
                this.showDropdown = false;
            }
        }
        // LINT.IfChange(getValidationError)
        getValidationError(inputType, fileType, fileSize, totalCount, currentTypeCount) {
            if (this.inputState?.activeTool === ToolMode.kDeepSearch) {
                return ProcessFilesError.FILE_UPLOAD_NOT_ALLOWED;
            }
            if (this.inputState?.activeTool !== ToolMode.kUnspecified) {
                const disabledTypes = this.inputState?.disabledInputTypes || [];
                if (disabledTypes.includes(inputType)) {
                    return ProcessFilesError.INVALID_TYPE;
                }
            }
            if (fileSize === 0 || fileSize > this.maxFileSize) {
                return fileSize === 0 ? ProcessFilesError.FILE_EMPTY :
                    ProcessFilesError.FILE_TOO_LARGE;
            }
            if (!this.isFileAllowed(fileType)) {
                return ProcessFilesError.INVALID_TYPE;
            }
            let maxTotal = this.maxFileCount;
            if (this.inputState && this.inputState.maxTotalInputs > 0) {
                maxTotal = this.inputState.maxTotalInputs;
            }
            let maxType = maxTotal;
            if (this.inputState &&
                this.inputState.maxInputsByType[inputType] !== undefined) {
                maxType = this.inputState.maxInputsByType[inputType];
            }
            if (currentTypeCount >= maxType) {
                switch (inputType) {
                    case InputType.kLensImage:
                        return ProcessFilesError.MAX_IMAGES_EXCEEDED;
                    case InputType.kLensFile:
                        return ProcessFilesError.MAX_PDFS_EXCEEDED;
                    default:
                        return ProcessFilesError.MAX_FILES_EXCEEDED;
                }
            }
            if (totalCount >= maxTotal) {
                return ProcessFilesError.MAX_FILES_EXCEEDED;
            }
            return ProcessFilesError.NONE;
        }
        // LINT.ThenChange(//chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.cc:ContextualSearchboxHandler_AddFileContextFromBrowser)
        processFiles(files) {
            if (!files || files.length === 0) {
                return;
            }
            if (this.inputState?.activeTool === ToolMode.kDeepSearch) {
                this.handleProcessFilesError(ProcessFilesError.FILE_UPLOAD_NOT_ALLOWED);
                return;
            }
            const filesToUpload = [];
            let errorToDisplay = ProcessFilesError.NONE;
            const counts = new Map();
            counts.set(InputType.kLensImage, 0);
            counts.set(InputType.kLensFile, 0);
            counts.set(InputType.kBrowserTab, 0);
            for (const file of this.files.values()) {
                const type = this.getInputType(file.type);
                counts.set(type, (counts.get(type) || 0) + 1);
            }
            let totalCount = this.files.size;
            let maxTotal = this.maxFileCount;
            if (this.inputState && this.inputState.maxTotalInputs > 0) {
                maxTotal = this.inputState.maxTotalInputs;
            }
            if (totalCount + files.length > maxTotal) {
                errorToDisplay =
                    Math.max(errorToDisplay, ProcessFilesError.MAX_FILES_EXCEEDED);
            }
            for (const file of files) {
                const inputType = this.getInputType(file.type);
                const currentTypeCount = counts.get(inputType) || 0;
                const error = this.getValidationError(inputType, file.type, file.size, totalCount, currentTypeCount);
                if (error !== ProcessFilesError.NONE) {
                    errorToDisplay = Math.max(errorToDisplay, error);
                    continue;
                }
                filesToUpload.push(file);
                totalCount++;
                counts.set(inputType, currentTypeCount + 1);
            }
            if (filesToUpload.length > 0) {
                this.addFileContext(filesToUpload);
            }
            this.handleProcessFilesError(errorToDisplay);
        }
        updateFileStatus(token, status, errorType) {
            let errorMessage = null;
            let file = this.files.get(token) ?? null;
            if (file) {
                if (isContextUploadStatusTerminal(status) &&
                    status !== ContextUploadStatus.kUploadSuccessful) {
                    this.files.delete(token);
                    if (file.tabId) {
                        this.addedTabsIds =
                            new Map([...this.addedTabsIds.entries()].filter(([id, _]) => id !== file.tabId));
                    }
                    switch (status) {
                        case ContextUploadStatus.kValidationFailed:
                            if (errorType) {
                                errorMessage = this.i18n(FILE_VALIDATION_ERRORS_MAP.get(errorType) ??
                                    'composeboxFileUploadValidationFailed');
                            }
                            else {
                                errorMessage =
                                    this.i18n('composeboxFileUploadValidationFailed');
                            }
                            break;
                        case ContextUploadStatus.kUploadFailed:
                            errorMessage = this.i18n('composeboxFileUploadFailed');
                            break;
                        case ContextUploadStatus.kUploadExpired:
                            errorMessage = this.i18n('composeboxFileUploadExpired');
                            break;
                        case ContextUploadStatus.kUploadReplaced:
                            // Update `composebox.ts` with the status since
                            // this should not return an error message for this
                            // 'non-uploaded' terminal file state, meaning
                            // its file status is still needed for understanding state
                            // when returned and back in the context of the function
                            // caller.
                            file = { ...file, status: status };
                            break;
                        default:
                            break;
                    }
                    this.closeMenu();
                }
                else {
                    file = { ...file, status: status };
                    this.files.set(token, file);
                }
                this.files = new Map([...this.files]);
            }
            else {
                // File is unknown but its status is known. Show this if
                // ghost/unknown files in frontend are allowed to be in
                // carousel.
                if (this.shouldShowGhostFiles) {
                    file = {
                        uuid: token,
                        name: '',
                        objectUrl: null,
                        dataUrl: null,
                        type: '',
                        inputType: InputType.kLensFile,
                        // Override this since first upload status is this or
                        // processing. Need this or processing in order to show tab
                        // spinner.
                        status: ContextUploadStatus.kUploadStarted,
                        url: null,
                        tabId: null,
                        isDeletable: true,
                        iconName: null,
                        supportsUnimodal: true,
                    };
                    // Update pending uploads in 'composebox.ts' to disable
                    // submit button.
                    this.onFileContextAdded(file);
                }
            }
            return { file, errorMessage };
        }
        /**
         * Fetches recent tab suggestions from the backend.
         * @param forceRefresh If true, forces a reload even if suggestions have
         *     already loaded. This is used when the context menu is opened or
         *     the tab strip changes while the menu is open, ensuring
         * suggestions remain fresh. If false, it serves suggestions from the
         * cache to minimize Mojo overhead.
         */
        async refreshTabSuggestions(forceRefresh = false) {
            if (this.tabSuggestionsState === TabSuggestionsState.LOADING ||
                (this.tabSuggestionsState === TabSuggestionsState.LOADED &&
                    !forceRefresh)) {
                return;
            }
            this.tabSuggestionsState = TabSuggestionsState.LOADING;
            try {
                const { tabs } = await this.getSearchboxHandler().getRecentTabs();
                this.recentTabId = tabs[0]?.tabId ?? null;
                const openTabIds = new Set(tabs.map(t => t.tabId));
                // Gather UUIDs in a temporary array to prevent modifying
                // `this.files` mid-iteration, since `deleteFile()` replaces the Map
                // reference.
                const uuidsToDelete = [];
                this.files.forEach((file, uuid) => {
                    if (file.tabId && !openTabIds.has(file.tabId)) {
                        uuidsToDelete.push(uuid);
                    }
                });
                uuidsToDelete.forEach(uuid => {
                    this.deleteFile(uuid, /*fromUserAction=*/ false);
                });
                if (this.tabDeselectionEnabled) {
                    const openTabUrls = new Map(tabs.map(t => [t.tabId, t.url]));
                    const closedOrNavigatedRestoredTabs = this.aimThreadRestoredTabs.filter(tab => {
                        const currentUrl = openTabUrls.get(tab.tabId);
                        return !currentUrl || currentUrl !== tab.url;
                    });
                    closedOrNavigatedRestoredTabs.forEach(tab => {
                        this.getSearchboxHandler().deleteTabContext(tab.tabId);
                    });
                    this.aimThreadRestoredTabs =
                        this.aimThreadRestoredTabs.filter(tab => {
                            const currentUrl = openTabUrls.get(tab.tabId);
                            return currentUrl && currentUrl === tab.url;
                        });
                }
                const restored = this.contextManagementInComposeboxEnabled ?
                    (this.aimThreadRestoredTabs || []) :
                    [];
                const processedRecentTabs = dedupeTabs(restored, tabs);
                const selectedTabIds = new Set(this.addedTabsIds.keys());
                const selectedRecent = processedRecentTabs.filter(tab => selectedTabIds.has(tab.tabId));
                const unselectedRecent = processedRecentTabs.filter(tab => !selectedTabIds.has(tab.tabId));
                this.tabSuggestions = [
                    ...selectedRecent,
                    ...restored,
                    ...unselectedRecent,
                ];
                this.tabSuggestionsState = TabSuggestionsState.LOADED;
                if (this.inputState) {
                    const { allowedInputTypes, disabledInputTypes } = this.inputState;
                    if (allowedInputTypes.includes(InputType.kBrowserTab) &&
                        !disabledInputTypes.includes(InputType.kBrowserTab)) {
                        const filteredSuggestions = this.tabSuggestions;
                        if (filteredSuggestions.length > 0) {
                            recordInputTypeShown(InputType.kBrowserTab, this.composeboxSource, 'AimPopup');
                        }
                    }
                }
            }
            finally {
                if (this.tabSuggestionsState === TabSuggestionsState.LOADING) {
                    this.tabSuggestionsState = TabSuggestionsState.NOT_STARTED;
                }
            }
        }
        onRequestTabSuggestionsLoad() {
            this.refreshTabSuggestions(/*forceRefresh=*/ true);
        }
        async onGetTabPreview(e) {
            const { previewDataUrl } = await this.getSearchboxHandler().getTabPreview(e.detail.tabId);
            e.detail.onPreviewFetched(previewDataUrl || '');
        }
        async onWaitForTabLoad(e) {
            e.stopPropagation();
            try {
                const { faviconDataUrl } = await this.getSearchboxHandler().waitForTabFaviconLoad(e.detail.tabId);
                const urlStr = faviconDataUrl?.url ||
                    (faviconDataUrl ? faviconDataUrl : undefined);
                e.detail.onTabLoaded(urlStr);
            }
            catch (error) {
                e.detail.onTabLoaded(undefined);
            }
        }
        voiceSearchEndCleanup() {
            this.inVoiceSearchMode = false;
            this.animationState = GlowAnimationState.VOICE_EXITED;
            this.transcript = '';
        }
        onVoiceSearchFinalResult(e) {
            e.stopPropagation();
            this.voiceSearchEndCleanup();
            // For contextual tasks composebox voice metrics.
            // TODO(crbug.com/466412331): Don't only fire this for composebox,
            // this should be recorded for all.
            this.fire('composebox-voice-search-transcription-success');
            // TODO(crbug.com/466412331): Remove, only recorded for the NTP.
            this.fire('voice-search-action', { value: VoiceSearchAction.QUERY_SUBMITTED });
            this.input = e.detail;
            const metricName = `ContextualSearch.UserAction.SubmitVoiceQuery.${this.composeboxSource}`;
            recordUserAction(metricName);
            recordBoolean(metricName, true);
            this.getSearchboxHandler().submitQuery(e.detail, /*mouse_button=*/ 0, /*alt_key=*/ false, 
            /*ctrl_key=*/ false, /*meta_key=*/ false, /*shift_key=*/ false, 
            /*is_voice_search=*/ true);
            this.submitCleanup();
        }
        onVoiceSearchCancel(e) {
            // If closing was the user canceling voice search:
            if (e.detail) {
                // For contextual tasks composebox voice metrics.
                this.fire('composebox-voice-search-user-canceled');
            }
            this.voiceSearchEndCleanup();
            this.receivedSpeech = false;
        }
        onVoiceSearchError(e) {
            this.hasVoiceSearchError = true;
            // For contextual tasks composebox voice metrics:
            if (e.detail) {
                // An error that canceled voice search.
                this.fire('composebox-voice-search-error-and-canceled');
            }
            else {
                // An error that did not cancel voice search.
                this.fire('composebox-voice-search-error');
            }
            // Do not call `voiceSearchEndCleanup()` here since
            // error scrim should stay open, or close itself based on timer.
            // When scrim does that, it will call `onVoiceSearchCancel` in this
            // file.
        }
        shouldShowVoiceSearch() {
            return this.showVoiceSearch &&
                WindowProxy.getInstance().hasWebkitSpeechRecognition();
        }
        shouldShowVoiceSearchAnimation() {
            return !this.disableVoiceSearchAnimation &&
                this.shouldShowVoiceSearch();
        }
        shouldShowVoiceSearchAtBottom() {
            return (this.searchboxLayoutMode === 'TallBottomContext' ||
                !this.searchboxLayoutMode) &&
                this.shouldShowVoiceSearch();
        }
        getFilteredCarouselFiles() {
            // Gets the list of files to display in the file carousel.
            // When the context management flag is enabled, tabs (files with a
            // URL) are filtered out because they would be displayed as favicons
            // instead of chips.
            const filesArray = Array.from(this.files.values());
            if (this.tabFaviconChipsToCoinsEnabled) {
                return filesArray.filter(f => !f.url);
            }
            return filesArray;
        }
        getNonTabFileNum() {
            return Array.from(this.files.values())
                .filter(file => file.inputType !== InputType.kBrowserTab)
                .length;
        }
        // Returns all attached tabs in composebox.
        getSharedTabs() {
            return Array.from(this.files.values())
                .filter(file => !!file.url)
                .map(file => ({
                tabId: file.tabId,
                title: file.name,
                url: file.url,
            }));
        }
        hasTabs() {
            return this.tabFaviconChipsToCoinsEnabled &&
                Array.from(this.files.values()).some(f => !!f.url);
        }
        shouldShowDivider() {
            if (this.tabFaviconChipsToCoinsEnabled && this.hasTabs()) {
                const hasNonTabFiles = Array.from(this.files.values()).some(f => !f.url);
                if (!hasNonTabFiles) {
                    return false;
                }
            }
            return this.showDropdown &&
                (this.showFileCarousel || this.shouldShowSubmitButton() ||
                    this.inToolMode);
        }
        shouldShowSubmitButton() {
            return this.searchboxNextEnabled && this.submitEnabled;
        }
        shouldDisableFileInputs() {
            return !this.contextMenuEnabled || !this.showMenuOnClick;
        }
        computeCancelButtonTitle() {
            return this.input.trim().length > 0 || this.files.size > 0 ?
                this.i18n('composeboxCancelButtonTitleInput') :
                this.i18n('composeboxCancelButtonTitle');
        }
        computeShowDropdown() {
            // Don't show dropdown if there's multiple files.
            if (this.files.size > 1) {
                return false;
            }
            // Don't show dropdown if there's no results.
            if (!this.result?.matches.length) {
                return false;
            }
            // Don't show dropdown if there's only verbatim match.
            if (this.result?.matches.length === 1 &&
                this.result?.matches[0]?.allowedToBeDefaultMatch) {
                return false;
            }
            // Do not show dropdown if there's an error scrim.
            if (this.errorMessage !== '') {
                return false;
            }
            // Do not show dropdown if there's an image and contextual image
            // suggestions are disabled.
            if (!this.enableImageContextualSuggestions && this.hasImageFiles()) {
                return false;
            }
            if (this.showTypedSuggest && this.lastQueriedInput.trim()) {
                // If context is present, but not enabled, continue to avoid showing
                // the dropdown.
                if (!this.showTypedSuggestWithContext && this.files.size > 0) {
                    return false;
                }
                // Do not show the dropdown for multiline input or if only the
                // verbatim match is present (we always expect a verbatim match for
                // typed suggest, so we ensure the length of the matches is >1).
                if (this.getInputElement().inputElement.scrollHeight <= 48 &&
                    this.result?.matches.length > 1) {
                    return true;
                }
            }
            // lastQueriedInput is used here since the input changes based on
            // the selected match. If typed suggest is not enabled and input is
            // used, the dropdown will hide if the user keys down over zps
            // matches.
            return this.showZps && !this.lastQueriedInput;
        }
    }
    return ComposeboxEmbedderMixin;
};
