// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '/strings.m.js';
import { assert } from 'chrome://resources/js/assert.js';
import { loadTimeData } from 'chrome://resources/js/load_time_data.js';
import { CrLitElement } from 'chrome://resources/lit/v3_0/lit.rollup.js';
import { getCss } from './experiment.css.js';
import { getHtml } from './experiment.html.js';
import { FlagsBrowserProxyImpl } from './flags_browser_proxy.js';
/**
 * Parses the element's text content and highlights it with hit markers.
 * @param searchTerm The query to highlight for.
 * @param element The element to highlight.
 */
function highlightMatch(searchTerm, element) {
    const text = element.textContent;
    const match = text.toLowerCase().indexOf(searchTerm);
    // Assert against cases that are already handled before this function.
    assert(match !== -1);
    assert(searchTerm !== '');
    // Clear content.
    element.textContent = '';
    if (match > 0) {
        const textNodePrefix = document.createTextNode(text.substring(0, match));
        element.appendChild(textNodePrefix);
    }
    const matchEl = document.createElement('mark');
    matchEl.textContent = text.substr(match, searchTerm.length);
    element.appendChild(matchEl);
    const matchSuffix = text.substring(match + searchTerm.length);
    if (matchSuffix) {
        const textNodeSuffix = document.createTextNode(matchSuffix);
        element.appendChild(textNodeSuffix);
    }
}
export class ExperimentElement extends CrLitElement {
    static get is() {
        return 'flags-experiment';
    }
    static get styles() {
        return getCss();
    }
    render() {
        return getHtml.bind(this)();
    }
    static get properties() {
        return {
            feature_: { type: Object },
            unsupported: { type: Boolean },
            isDefault_: {
                type: Boolean,
                reflect: true,
            },
            expanded_: {
                type: Boolean,
                reflect: true,
            },
            showingSearchHit_: { type: Boolean },
        };
    }
    #feature__accessor_storage = {
        internal_name: '',
        name: '',
        description: '',
        enabled: false,
        is_default: false,
        supported_platforms: [],
    };
    get feature_() { return this.#feature__accessor_storage; }
    set feature_(value) { this.#feature__accessor_storage = value; }
    #unsupported_accessor_storage = false;
    // Whether the controls to change the experiment state should be hidden.
    get unsupported() { return this.#unsupported_accessor_storage; }
    set unsupported(value) { this.#unsupported_accessor_storage = value; }
    #isDefault__accessor_storage = false;
    // Whether the currently selected value is the default value.
    get isDefault_() { return this.#isDefault__accessor_storage; }
    set isDefault_(value) { this.#isDefault__accessor_storage = value; }
    #expanded__accessor_storage = false;
    // Whether the description text is expanded. Only has an effect on narrow
    // widths (max-width: 480px).
    get expanded_() { return this.#expanded__accessor_storage; }
    set expanded_(value) { this.#expanded__accessor_storage = value; }
    #showingSearchHit__accessor_storage = false;
    // Whether search hits are currently displayed. When true, some DOM nodes are
    // replaced with cloned nodes whose textContent is not rendered by Lit, so
    // that the highlight algorithm can freely modify them. Lit does not play
    // nicely with manual DOM modifications and throws internal errors in
    // subsequent renders.
    get showingSearchHit_() { return this.#showingSearchHit__accessor_storage; }
    set showingSearchHit_(value) { this.#showingSearchHit__accessor_storage = value; }
    getRequiredElement(query) {
        const el = this.shadowRoot.querySelector(query);
        assert(el);
        assert(el instanceof HTMLElement);
        return el;
    }
    willUpdate(changedProperties) {
        super.willUpdate(changedProperties);
        this.isDefault_ = this.computeIsDefault_();
    }
    set data(feature) {
        this.feature_ = feature;
    }
    getExperimentTitle_() {
        if (this.showEnableDisableSelect_()) {
            return this.isDefault_ ? '' :
                loadTimeData.getString('experiment-enabled');
        }
        return '';
    }
    getPlatforms_() {
        return this.feature_.supported_platforms.join(', ');
    }
    getHeaderId_() {
        return `${this.feature_.internal_name}_name`;
    }
    showEnableDisableSelect_() {
        return !this.unsupported &&
            (!this.feature_.options || !this.feature_.options.length);
    }
    showMultiValueSelect_() {
        return !this.unsupported && !!this.feature_.options &&
            !!this.feature_.options.length;
    }
    onTextareaChange_(e) {
        e.stopPropagation();
        const textarea = e.target;
        this.handleSetOriginListFlag_(textarea.value);
        textarea.dispatchEvent(new Event('textarea-change', {
            bubbles: true,
            composed: true,
        }));
    }
    onTextInputChange_(e) {
        e.stopPropagation();
        const textbox = e.target;
        this.handleSetStringFlag_(textbox.value);
        textbox.dispatchEvent(new Event('input-change', {
            bubbles: true,
            composed: true,
        }));
    }
    onExperimentNameClick_(_e) {
        this.expanded_ = !this.expanded_;
    }
    getSelect() {
        return this.shadowRoot.querySelector('select');
    }
    getTextarea() {
        return this.shadowRoot.querySelector('textarea');
    }
    getTextbox() {
        return this.shadowRoot.querySelector('input');
    }
    /**
     * Looks for and highlights the first match on any of the component's title,
     * description, platforms and permalink text. Resets any pre-existing
     * highlights.
     * @param searchTerm The query to search for.
     * @return Whether or not a match was found.
     */
    async match(searchTerm) {
        this.showingSearchHit_ = false;
        if (searchTerm === '') {
            return true;
        }
        // Items to search in the desired order.
        const itemsToSearch = [
            this.feature_.name,
            this.feature_.description,
            this.getPlatforms_(),
            this.feature_.internal_name,
        ];
        const index = itemsToSearch.findIndex(item => {
            return item.toLowerCase().includes(searchTerm);
        });
        if (index === -1) {
            // No matches found. Nothing to do.
            return false;
        }
        // Render the clone elements (the ones to be highlighted with markers).
        this.showingSearchHit_ = true;
        await this.updateComplete;
        const queries = [
            '.clone.experiment-name',
            '.clone.body .description',
            '.clone.body .platforms',
            '.clone.permalink',
        ];
        // Populate clone elements with the proper text content.
        queries.forEach((query, i) => {
            const clone = this.getRequiredElement(query);
            clone.textContent = (i === 3 ? '#' : '') + itemsToSearch[i];
        });
        // Add highlights to the first clone element with matches.
        const cloneWithMatch = this.getRequiredElement(queries[index]);
        highlightMatch(searchTerm, cloneWithMatch);
        return true;
    }
    computeIsDefault_() {
        if (this.showEnableDisableSelect_() || this.showMultiValueSelect_()) {
            return this.feature_.is_default;
        }
        return true;
    }
    /**
     * Invoked when the selection of an enable/disable choice is changed.
     */
    onExperimentEnableDisableChange_(e) {
        e.stopPropagation();
        /* This function is an onchange handler, which can be invoked during page
         * restore - see https://crbug.com/1038638. */
        assert(this.feature_);
        assert(!this.feature_.options || this.feature_.options.length === 0);
        const experimentEnableDisable = e.target;
        // Manually update the local data model because modifications don't
        // result in an updated model to propagate from the backend.
        this.feature_.is_default = !this.feature_.is_default;
        this.feature_.enabled = experimentEnableDisable.value === 'enabled';
        this.requestUpdate();
        FlagsBrowserProxyImpl.getInstance().enableExperimentalFeature(this.feature_.internal_name, this.feature_.enabled);
        experimentEnableDisable.dispatchEvent(new Event('select-change', {
            bubbles: true,
            composed: true,
        }));
    }
    /**
     * Invoked when the selection of a multi-value choice is changed.
     */
    onExperimentSelectChange_(e) {
        e.stopPropagation();
        /* This function is an onchange handler, which can be invoked during page
         * restore - see https://crbug.com/1038638. */
        assert(this.feature_);
        assert(this.feature_.options && this.feature_.options.length > 0);
        const experimentSelect = e.target;
        // Manually update the local data model because modifications don't
        // result in an updated model to propagate from the backend.
        this.feature_.is_default = experimentSelect.selectedIndex === 0;
        for (let i = 0; i < this.feature_.options.length; i++) {
            this.feature_.options[i].selected = experimentSelect.selectedIndex === i;
        }
        this.requestUpdate();
        FlagsBrowserProxyImpl.getInstance().selectExperimentalFeature(this.feature_.internal_name, experimentSelect.selectedIndex);
        experimentSelect.dispatchEvent(new Event('select-change', {
            bubbles: true,
            composed: true,
        }));
    }
    /**
     * Invoked when the value of a textarea for origins is changed.
     * @param value The value of the textarea.
     */
    handleSetOriginListFlag_(value) {
        /* This function is an onchange handler, which can be invoked during page
         * restore - see https://crbug.com/1038638. */
        assert(this.feature_);
        assert(this.feature_.origin_list_value !== undefined);
        FlagsBrowserProxyImpl.getInstance().setOriginListFlag(this.feature_.internal_name, value);
    }
    /**
     * Invoked when the value of an input is changed.
     * @param value The value of the input.
     */
    handleSetStringFlag_(value) {
        /* This function is an onchange handler, which can be invoked during page
         * restore - see https://crbug.com/1038638. */
        assert(this.feature_);
        FlagsBrowserProxyImpl.getInstance().setStringFlag(this.feature_.internal_name, value);
    }
}
customElements.define(ExperimentElement.is, ExperimentElement);
