// 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 { TrackedElementManager } from '//resources/js/tracked_element/tracked_element_manager.js';
import { ensureTransitionEndEvent } from '//resources/js/util.js';
import { CrLitElement } from '//resources/lit/v3_0/lit.rollup.js';
import { LhsChipIdentifier, PermissionAction, PermissionChipTheme, PermissionPromptStyle } from '/shared/toolbar_ui_api_data_model.mojom-webui.js';
import { BrowserProxyImpl } from './browser_proxy.js';
import { getCss } from './permission_chip.css.js';
import { getHtml } from './permission_chip.html.js';
import { BUTTON_LEFT } from './toolbar_button.js';
export class PermissionChipElement extends CrLitElement {
    static get is() {
        return 'permission-chip';
    }
    static get styles() {
        return getCss();
    }
    render() {
        return getHtml.bind(this)();
    }
    static get properties() {
        return {
            chipState: { type: Object },
            hasDivider: { type: Boolean, attribute: 'has-divider', reflect: true },
        };
    }
    #chipState_accessor_storage = null;
    get chipState() { return this.#chipState_accessor_storage; }
    set chipState(value) { this.#chipState_accessor_storage = value; }
    #hasDivider_accessor_storage = false;
    get hasDivider() { return this.#hasDivider_accessor_storage; }
    set hasDivider(value) { this.#hasDivider_accessor_storage = value; }
    isFullyCollapsed_ = true;
    trackedElementManager_;
    constructor() {
        super();
        this.trackedElementManager_ = TrackedElementManager.getInstance();
    }
    connectedCallback() {
        super.connectedCallback();
        const id = this.id === 'request-chip' ?
            'PermissionChipView::kPermissionRequestChipElementId' :
            'PermissionChipView::kIndicatorChipElementId';
        this.trackedElementManager_.startTracking(this, id, {
            onHighlightChanged: (highlighted) => {
                // Manually toggle the DOM attribute to bypass Lit's asynchronous update
                // batching, ensuring the style updates synchronously.
                // TODO(crbug.com/502598627): Re-evaluate how big the visual flash
                // problem is, and whether we really need to manually toggle it or if we
                // can use a reflected Lit property.
                this.toggleAttribute('anchor-highlighted', highlighted);
            },
        });
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this.trackedElementManager_.stopTracking(this);
    }
    updated(changedProperties) {
        super.updated(changedProperties);
        if (changedProperties.has('chipState')) {
            this.updateColors_();
            this.onChipStateChanged_();
        }
    }
    onChipStateChanged_() {
        if (!this.chipState) {
            return;
        }
        if (this.chipState.isFullyCollapsed === this.isFullyCollapsed_) {
            return;
        }
        this.isFullyCollapsed_ = this.chipState.isFullyCollapsed;
        const isCollapsed = this.isFullyCollapsed_;
        // In Native Views, animation state transitions are perfectly
        // synchronized. In WebUI, the state change updates the DOM, which
        // triggers a CSS transition. We must wait for this transition to
        // complete before telling C++ the animation is over. If we fire this
        // Mojo IPC synchronously, C++ will instantly hide the element and
        // kill the animation prematurely.
        const fireIpc = () => {
            if (isCollapsed) {
                BrowserProxyImpl.getInstance()
                    .toolbarUIHandler.onLhsChipCollapseAnimationEnded(this.getIdentifier_());
            }
            else {
                BrowserProxyImpl.getInstance()
                    .toolbarUIHandler.onLhsChipExpandAnimationEnded(this.getIdentifier_());
            }
        };
        const chipEl = this.shadowRoot.querySelector('#chip');
        if (chipEl) {
            chipEl.addEventListener('transitionend', fireIpc, { once: true });
            // Fallback in case there is no CSS transition (e.g., hidden
            // element) or the tab is backgrounded, which might delay or
            // suppress transitionend. `ensureTransitionEndEvent` fetches the
            // duration from the CSS.
            ensureTransitionEndEvent(chipEl);
        }
    }
    getIdentifier_() {
        return this.id === 'request-chip' ? LhsChipIdentifier.kPermissionRequest :
            LhsChipIdentifier.kPermissionIndicator;
    }
    onPointerenter_() {
        BrowserProxyImpl.getInstance().toolbarUIHandler.onLhsChipPointerEntered(this.getIdentifier_());
    }
    onPointerleave_() {
        BrowserProxyImpl.getInstance().toolbarUIHandler.onLhsChipPointerExited(this.getIdentifier_());
    }
    onPointercancel_() {
        this.onPointerleave_();
    }
    onPointerdown_(e) {
        if (e.button !== BUTTON_LEFT) {
            return;
        }
        BrowserProxyImpl.getInstance().toolbarUIHandler.onLhsChipMousePressed(this.getIdentifier_());
    }
    onClick_(e) {
        // Note:'click' event dispatches using PointerEvents. Keyboard clicks
        // (Enter/Space) also dispatch PointerEvents, but they have an empty
        // pointerType (""). We only want to suppress true pointer interactions
        // (mouse, touch, pen).
        BrowserProxyImpl.getInstance().toolbarUIHandler.onLhsChipClicked(this.getIdentifier_(), e.pointerType !== '');
    }
    // Computes the foreground and background colors for the chip based on its
    // logical state (theme, user decision, prompt style).
    // Replicating this logic in TypeScript instead of passing explicit colors
    // over Mojo is the standard WebUI approach. It preserves the separation of
    // concerns: C++ dictates the logical state, and WebUI CSS handles the actual
    // color resolution (automatically managing light/dark mode variations).
    // This logic must strictly mirror the Native Views implementation found in:
    // - PermissionChipView::GetForegroundColor()
    // - PermissionChipView::GetBackgroundColor()
    updateColors_() {
        if (!this.chipState) {
            return;
        }
        let bgColor = 'var(--color-omnibox-chip-background)';
        let fgColor = 'var(--color-omnibox-chip-foreground-normal-visibility)';
        switch (this.chipState.theme) {
            case PermissionChipTheme.kActivityIndicator:
            case PermissionChipTheme.kInUseActivityIndicator:
                bgColor =
                    'var(--color-omnibox-chip-in-use-activity-indicator-background)';
                fgColor =
                    'var(--color-omnibox-chip-in-use-activity-indicator-foreground)';
                break;
            case PermissionChipTheme.kBlockedActivityIndicator:
                bgColor =
                    'var(--color-omnibox-chip-blocked-activity-indicator-background)';
                fgColor =
                    'var(--color-omnibox-chip-blocked-activity-indicator-foreground)';
                break;
            case PermissionChipTheme.kOnSystemBlockedActivityIndicator:
                bgColor = 'var(--color-omnibox-chip-' +
                    'on-system-blocked-activity-indicator-background)';
                fgColor = 'var(--color-omnibox-chip-' +
                    'on-system-blocked-activity-indicator-foreground)';
                break;
            case PermissionChipTheme.kLowVisibility:
                fgColor = 'var(--color-omnibox-chip-foreground-low-visibility)';
                break;
            case PermissionChipTheme.kNormalVisibility:
            default:
                break;
        }
        // Apply native fallback color logic for foreground text and icon.
        // This logic only applies if the theme isn't one of the special indicators.
        // (In native, GetForegroundColor() returns early for the indicators above).
        if (this.chipState.theme === PermissionChipTheme.kNormalVisibility ||
            this.chipState.theme === PermissionChipTheme.kLowVisibility) {
            if (this.chipState.promptStyle === PermissionPromptStyle.kQuietChip) {
                fgColor = 'var(--color-omnibox-chip-foreground-low-visibility)';
            }
            switch (this.chipState.userDecision) {
                case PermissionAction.kGranted:
                case PermissionAction.kGrantedOnce:
                    fgColor = 'var(--color-omnibox-chip-foreground-normal-visibility)';
                    break;
                case PermissionAction.kDenied:
                case PermissionAction.kDismissed:
                case PermissionAction.kIgnored:
                case PermissionAction.kRevoked:
                    fgColor = 'var(--color-omnibox-chip-foreground-low-visibility)';
                    break;
                default:
                    break;
            }
            if (this.chipState.shouldShowBlockedIcon) {
                fgColor = 'var(--color-omnibox-chip-foreground-low-visibility)';
            }
        }
        this.style.setProperty('--chip-bg-color', bgColor);
        this.style.setProperty('--chip-fg-color', fgColor);
    }
    getIconUrl_() {
        if (!this.chipState || !this.chipState.iconName) {
            return '';
        }
        let iconName = '';
        // TODO(crbug.com/502598627): This maps new rounded icon names and old names
        // to the same pre-canned SVG, which means it doesn't actually support
        // rounded icons. We will need to add the rounded SVGs and update this
        // mapping if we want to support them in the future.
        switch (this.chipState.iconName) {
            case 'kLocationOnIcon':
            case 'kLocationOnChromeRefreshOldIcon':
                iconName = 'location_on_chrome_refresh';
                break;
            case 'kLocationOffIcon':
            case 'kLocationOffChromeRefreshOldIcon':
                iconName = 'location_off_chrome_refresh';
                break;
            case 'kVideocamIcon':
            case 'kVideocamChromeRefreshOldIcon':
                iconName = 'videocam_chrome_refresh';
                break;
            case 'kVideocamOffIcon':
            case 'kVideocamOffChromeRefreshOldIcon':
                iconName = 'videocam_off_chrome_refresh';
                break;
            case 'kMicIcon':
            case 'kMicChromeRefreshOldIcon':
                iconName = 'mic_chrome_refresh';
                break;
            case 'kMicOffIcon':
            case 'kMicOffChromeRefreshOldIcon':
                iconName = 'mic_off_chrome_refresh';
                break;
            case 'kNotificationsIcon':
            case 'kNotificationsChromeRefreshOldIcon':
                iconName = 'notifications_chrome_refresh';
                break;
            case 'kNotificationsOffIcon':
            case 'kNotificationsOffChromeRefreshOldIcon':
                iconName = 'notifications_off_chrome_refresh';
                break;
            default:
                break;
        }
        return iconName ? `url('rhs_icons/${iconName}.svg')` : '';
    }
}
customElements.define(PermissionChipElement.is, PermissionChipElement);
