// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { assertNotNullOrUndefined } from '../../../core/platform/platform.js';
import * as Platform from '../../../core/platform/platform.js';
import * as SplitView from '../../../ui/components/split_view/split_view.js';
import * as i18n from '../../../core/i18n/i18n.js';
import * as UI from '../../../ui/legacy/legacy.js';
import * as SDK from '../../../core/sdk/sdk.js';
import * as LitHtml from '../../../ui/lit-html/lit-html.js';
import * as Bindings from '../../../models/bindings/bindings.js';
import * as PreloadingComponents from './components/components.js';
// eslint-disable-next-line rulesdir/es_modules_import
import emptyWidgetStyles from '../../../ui/legacy/emptyWidget.css.js';
import preloadingViewStyles from './preloadingView.css.js';
const UIStrings = {
    /**
     *@description DropDown title for filtering preloading attempts by rule set
     */
    filterFilterByRuleSet: 'Filter by rule set',
    /**
     *@description DropDown text for filtering preloading attempts by rule set: No filter
     */
    filterAllPreloads: 'All preloads',
    /**
     *@description Text in grid: Rule set is valid
     */
    validityValid: 'Valid',
    /**
     *@description Text in grid: Rule set must be a valid JSON object
     */
    validityInvalid: 'Invalid',
    /**
     *@description Text in grid: Rule set contains invalid rules and they are ignored
     */
    validitySomeRulesInvalid: 'Some rules invalid',
    /**
     *@description Text in grid and details: Preloading attempt is not yet triggered.
     */
    statusNotTriggered: 'Not triggered',
    /**
     *@description Text in grid and details: Preloading attempt is eligible but pending.
     */
    statusPending: 'Pending',
    /**
     *@description Text in grid and details: Preloading is running.
     */
    statusRunning: 'Running',
    /**
     *@description Text in grid and details: Preloading finished and the result is ready for the next navigation.
     */
    statusReady: 'Ready',
    /**
     *@description Text in grid and details: Ready, then used.
     */
    statusSuccess: 'Success',
    /**
     *@description Text in grid and details: Preloading failed.
     */
    statusFailure: 'Failure',
};
const str_ = i18n.i18n.registerUIStrings('panels/application/preloading/PreloadingView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
class PreloadingUIUtils {
    static status(status) {
        // See content/public/browser/preloading.h PreloadingAttemptOutcome.
        switch (status) {
            case "NotTriggered" /* SDK.PreloadingModel.PreloadingStatus.NotTriggered */:
                return i18nString(UIStrings.statusNotTriggered);
            case "Pending" /* SDK.PreloadingModel.PreloadingStatus.Pending */:
                return i18nString(UIStrings.statusPending);
            case "Running" /* SDK.PreloadingModel.PreloadingStatus.Running */:
                return i18nString(UIStrings.statusRunning);
            case "Ready" /* SDK.PreloadingModel.PreloadingStatus.Ready */:
                return i18nString(UIStrings.statusReady);
            case "Success" /* SDK.PreloadingModel.PreloadingStatus.Success */:
                return i18nString(UIStrings.statusSuccess);
            case "Failure" /* SDK.PreloadingModel.PreloadingStatus.Failure */:
                return i18nString(UIStrings.statusFailure);
            // NotSupported is used to handle unreachable case. For example,
            // there is no code path for
            // PreloadingTriggeringOutcome::kTriggeredButPending in prefetch,
            // which is mapped to NotSupported. So, we regard it as an
            // internal error.
            case "NotSupported" /* SDK.PreloadingModel.PreloadingStatus.NotSupported */:
                return i18n.i18n.lockedString('Internal error');
        }
    }
    static preloadsStatusSummary(countsByStatus) {
        const LIST = [
            "NotTriggered" /* SDK.PreloadingModel.PreloadingStatus.NotTriggered */,
            "Pending" /* SDK.PreloadingModel.PreloadingStatus.Pending */,
            "Running" /* SDK.PreloadingModel.PreloadingStatus.Running */,
            "Ready" /* SDK.PreloadingModel.PreloadingStatus.Ready */,
            "Success" /* SDK.PreloadingModel.PreloadingStatus.Success */,
            "Failure" /* SDK.PreloadingModel.PreloadingStatus.Failure */,
        ];
        return LIST.filter(status => (countsByStatus?.get(status) || 0) > 0)
            .map(status => (countsByStatus?.get(status) || 0) + ' ' + this.status(status))
            .join(', ');
    }
    // Summary of error of rule set shown in grid.
    static validity({ errorType }) {
        switch (errorType) {
            case undefined:
                return i18nString(UIStrings.validityValid);
            case "SourceIsNotJsonObject" /* Protocol.Preload.RuleSetErrorType.SourceIsNotJsonObject */:
                return i18nString(UIStrings.validityInvalid);
            case "InvalidRulesSkipped" /* Protocol.Preload.RuleSetErrorType.InvalidRulesSkipped */:
                return i18nString(UIStrings.validitySomeRulesInvalid);
        }
    }
    // Where a rule set came from, shown in grid.
    static location(ruleSet) {
        if (ruleSet.backendNodeId !== undefined) {
            return i18n.i18n.lockedString('<script>');
        }
        if (ruleSet.url !== undefined) {
            return ruleSet.url;
        }
        throw Error('unreachable');
    }
    static processLocalId(id) {
        // RuleSetId is form of '<processId>.<processLocalId>'
        const index = id.indexOf('.');
        return index === -1 ? id : id.slice(index + 1);
    }
    // TODO(https://crbug.com/1410709): Move
    // front_end/panels/application/preloading/components/PreloadingString.ts
    // to
    // front_end/panels/application/preloading/helper/PreloadingString.ts
    // and use PreloadingString.ruleSetLocationShort.
    static ruleSetLocationShort(ruleSet, pageURL) {
        const url = ruleSet.url === undefined ? pageURL : ruleSet.url;
        return Bindings.ResourceUtils.displayNameForURL(url);
    }
}
function pageURL() {
    return SDK.TargetManager.TargetManager.instance().scopeTarget()?.inspectedURL() ||
        '';
}
export class PreloadingRuleSetView extends UI.Widget.VBox {
    model;
    focusedRuleSetId = null;
    focusedPreloadingAttemptId = null;
    warningsContainer;
    warningsView = new PreloadingWarningsView();
    hsplit;
    ruleSetGrid = new PreloadingComponents.RuleSetGrid.RuleSetGrid();
    ruleSetDetails = new PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView();
    constructor(model) {
        super(/* isWebComponent */ true, /* delegatesFocus */ false);
        this.model = model;
        SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this));
        SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.ModelUpdated, this.render, this, { scoped: true });
        SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.WarningsUpdated, this.warningsView.onWarningsUpdated, this.warningsView, { scoped: true });
        // this (VBox)
        //   +- warningsContainer
        //        +- PreloadingWarningsView
        //   +- hsplit
        //        +- leftContainer
        //             +- RuleSetGrid
        //        +- rightContainer
        //             +- RuleSetDetailsView
        //
        // - If an row of RuleSetGrid selected, RuleSetDetailsView shows details of it.
        // - If not, RuleSetDetailsView hides.
        this.warningsContainer = document.createElement('div');
        this.warningsContainer.classList.add('flex-none');
        this.contentElement.insertBefore(this.warningsContainer, this.contentElement.firstChild);
        this.warningsView.show(this.warningsContainer);
        this.ruleSetGrid.addEventListener('cellfocused', this.onRuleSetsGridCellFocused.bind(this));
        LitHtml.render(LitHtml.html `
        <${SplitView.SplitView.SplitView.litTagName} .horizontal=${true} style="--min-sidebar-size: 0px">
          <div slot="main" class="overflow-auto" style="height: 100%">
            ${this.ruleSetGrid}
          </div>
          <div slot="sidebar" class="overflow-auto" style="height: 100%">
            ${this.ruleSetDetails}
          </div>
        </${SplitView.SplitView.SplitView.litTagName}>`, this.contentElement, { host: this });
        this.hsplit = this.contentElement.querySelector('devtools-split-view');
    }
    wasShown() {
        super.wasShown();
        this.registerCSSFiles([emptyWidgetStyles, preloadingViewStyles]);
        this.warningsView.wasShown();
        this.render();
    }
    onScopeChange() {
        const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
        assertNotNullOrUndefined(model);
        this.model = model;
        this.render();
    }
    revealRuleSet(revealInfo) {
        this.focusedRuleSetId = revealInfo.ruleSetId;
        this.render();
    }
    updateRuleSetDetails() {
        const id = this.focusedRuleSetId;
        const ruleSet = id === null ? null : this.model.getRuleSetById(id);
        this.ruleSetDetails.data = ruleSet;
        if (ruleSet === null) {
            this.hsplit.style.setProperty('--current-main-area-size', '100%');
        }
        else {
            this.hsplit.style.setProperty('--current-main-area-size', '60%');
        }
    }
    render() {
        // Update rule sets grid
        const countsByRuleSetId = this.model.getPreloadCountsByRuleSetId();
        const ruleSetRows = this.model.getAllRuleSets().map(({ id, value }) => {
            const countsByStatus = countsByRuleSetId.get(id) || new Map();
            return {
                ruleSet: value,
                preloadsStatusSummary: PreloadingUIUtils.preloadsStatusSummary(countsByStatus),
            };
        });
        this.ruleSetGrid.update({ rows: ruleSetRows, pageURL: pageURL() });
        this.updateRuleSetDetails();
    }
    onRuleSetsGridCellFocused(event) {
        const focusedEvent = event;
        this.focusedRuleSetId =
            focusedEvent.data.row.cells.find(cell => cell.columnId === 'id')?.value;
        this.render();
    }
    getInfobarContainerForTest() {
        return this.warningsView.contentElement;
    }
    getRuleSetGridForTest() {
        return this.ruleSetGrid;
    }
    getRuleSetDetailsForTest() {
        return this.ruleSetDetails;
    }
}
export class PreloadingAttemptView extends UI.Widget.VBox {
    model;
    focusedPreloadingAttemptId = null;
    warningsContainer;
    warningsView = new PreloadingWarningsView();
    preloadingGrid = new PreloadingComponents.PreloadingGrid.PreloadingGrid();
    preloadingDetails = new PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportView();
    ruleSetSelector;
    constructor(model) {
        super(/* isWebComponent */ true, /* delegatesFocus */ false);
        this.model = model;
        SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this));
        SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.ModelUpdated, this.render, this, { scoped: true });
        SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.WarningsUpdated, this.warningsView.onWarningsUpdated, this.warningsView, { scoped: true });
        // this (VBox)
        //   +- warningsContainer
        //        +- PreloadingWarningsView
        //   +- VBox
        //        +- toolbar (filtering)
        //        +- hsplit
        //             +- leftContainer
        //                  +- PreloadingGrid
        //             +- rightContainer
        //                  +- PreloadingDetailsReportView
        //
        // - If an row of PreloadingGrid selected, PreloadingDetailsReportView shows details of it.
        // - If not, PreloadingDetailsReportView shows some messages.
        this.warningsContainer = document.createElement('div');
        this.warningsContainer.classList.add('flex-none');
        this.contentElement.insertBefore(this.warningsContainer, this.contentElement.firstChild);
        this.warningsView.show(this.warningsContainer);
        const vbox = new UI.Widget.VBox();
        const toolbar = new UI.Toolbar.Toolbar('preloading-toolbar', vbox.contentElement);
        this.ruleSetSelector = new PreloadingRuleSetSelector(() => this.render());
        toolbar.appendToolbarItem(this.ruleSetSelector.item());
        this.preloadingGrid.addEventListener('cellfocused', this.onPreloadingGridCellFocused.bind(this));
        LitHtml.render(LitHtml.html `
        <${SplitView.SplitView.SplitView.litTagName} .horizontal=${true} style="--min-sidebar-size: 0px">
          <div slot="main" class="overflow-auto" style="height: 100%">
            ${this.preloadingGrid}
          </div>
          <div slot="sidebar" class="overflow-auto" style="height: 100%">
            ${this.preloadingDetails}
          </div>
        </${SplitView.SplitView.SplitView.litTagName}>`, vbox.contentElement, { host: this });
        vbox.show(this.contentElement);
    }
    wasShown() {
        super.wasShown();
        this.registerCSSFiles([emptyWidgetStyles, preloadingViewStyles]);
        this.warningsView.wasShown();
        this.render();
    }
    onScopeChange() {
        const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
        assertNotNullOrUndefined(model);
        this.model = model;
        this.render();
    }
    setFilter(filter) {
        const id = filter.ruleSetId;
        this.model.getRuleSetById(id) && this.ruleSetSelector.select(id);
    }
    updatePreloadingDetails() {
        const id = this.focusedPreloadingAttemptId;
        const preloadingAttempt = id === null ? null : this.model.getPreloadingAttemptById(id);
        if (preloadingAttempt === null) {
            this.preloadingDetails.data = null;
        }
        else {
            const ruleSets = preloadingAttempt.ruleSetIds.map(id => this.model.getRuleSetById(id)).filter(x => x !== null);
            this.preloadingDetails.data = {
                preloadingAttempt,
                ruleSets,
                pageURL: pageURL(),
            };
        }
    }
    render() {
        // Update preloaidng grid
        const filteringRuleSetId = this.ruleSetSelector.getSelected();
        const rows = this.model.getPreloadingAttempts(filteringRuleSetId).map(({ id, value }) => {
            const attempt = value;
            const ruleSets = attempt.ruleSetIds.flatMap(id => {
                const ruleSet = this.model.getRuleSetById(id);
                return ruleSet === null ? [] : [ruleSet];
            });
            return {
                id,
                attempt,
                ruleSets,
            };
        });
        this.preloadingGrid.update({ rows, pageURL: pageURL() });
        this.updatePreloadingDetails();
    }
    onPreloadingGridCellFocused(event) {
        const focusedEvent = event;
        this.focusedPreloadingAttemptId = focusedEvent.data.row.cells.find(cell => cell.columnId === 'id')?.value;
        this.render();
    }
    getRuleSetSelectorToolbarItemForTest() {
        return this.ruleSetSelector.item();
    }
    getPreloadingGridForTest() {
        return this.preloadingGrid;
    }
    getPreloadingDetailsForTest() {
        return this.preloadingDetails;
    }
    selectRuleSetOnFilterForTest(id) {
        this.ruleSetSelector.select(id);
    }
}
export class PreloadingResultView extends UI.Widget.VBox {
    model;
    warningsContainer;
    warningsView = new PreloadingWarningsView();
    usedPreloading = new PreloadingComponents.UsedPreloadingView.UsedPreloadingView();
    constructor(model) {
        super(/* isWebComponent */ true, /* delegatesFocus */ false);
        this.model = model;
        SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this));
        SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.ModelUpdated, this.render, this, { scoped: true });
        SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.WarningsUpdated, this.warningsView.onWarningsUpdated, this.warningsView, { scoped: true });
        this.warningsContainer = document.createElement('div');
        this.warningsContainer.classList.add('flex-none');
        this.contentElement.insertBefore(this.warningsContainer, this.contentElement.firstChild);
        this.warningsView.show(this.warningsContainer);
        const usedPreloadingContainer = new UI.Widget.VBox();
        usedPreloadingContainer.contentElement.appendChild(this.usedPreloading);
        usedPreloadingContainer.show(this.contentElement);
    }
    wasShown() {
        super.wasShown();
        this.registerCSSFiles([emptyWidgetStyles, preloadingViewStyles]);
        this.warningsView.wasShown();
        this.render();
    }
    onScopeChange() {
        const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
        assertNotNullOrUndefined(model);
        this.model = model;
        this.render();
    }
    render() {
        this.usedPreloading.data = {
            pageURL: SDK.TargetManager.TargetManager.instance().scopeTarget()?.inspectedURL() ||
                '',
            attempts: this.model.getPreloadingAttemptsOfPreviousPage().map(({ value }) => value),
        };
    }
    getUsedPreloadingForTest() {
        return this.usedPreloading;
    }
}
class PreloadingRuleSetSelector {
    model;
    onSelectionChanged = () => { };
    toolbarItem;
    listModel;
    dropDown;
    constructor(onSelectionChanged) {
        const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
        assertNotNullOrUndefined(model);
        this.model = model;
        SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this));
        SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.ModelUpdated, this.onModelUpdated, this, { scoped: true });
        this.listModel = new UI.ListModel.ListModel();
        this.dropDown = new UI.SoftDropDown.SoftDropDown(this.listModel, this);
        this.dropDown.setRowHeight(36);
        this.dropDown.setPlaceholderText(i18nString(UIStrings.filterAllPreloads));
        this.toolbarItem = new UI.Toolbar.ToolbarItem(this.dropDown.element);
        this.toolbarItem.setTitle(i18nString(UIStrings.filterFilterByRuleSet));
        this.toolbarItem.element.classList.add('toolbar-has-dropdown');
        // Initializes `listModel` and `dropDown` using data of the model.
        this.onModelUpdated();
        // Prevents emitting onSelectionChanged on the first call of `this.onModelUpdated()` for initialization.
        this.onSelectionChanged = onSelectionChanged;
    }
    onScopeChange() {
        const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
        assertNotNullOrUndefined(model);
        this.model = model;
        this.onModelUpdated();
    }
    onModelUpdated() {
        const ids = this.model.getAllRuleSets().map(({ id }) => id);
        const items = [null, ...ids];
        const selected = this.dropDown.getSelectedItem();
        const newSelected = items.indexOf(selected) === -1 ? null : selected;
        this.listModel.replaceAll(items);
        this.dropDown.selectItem(newSelected);
    }
    getSelected() {
        return this.dropDown.getSelectedItem();
    }
    select(id) {
        this.dropDown.selectItem(id);
    }
    // Method for UI.Toolbar.Provider
    item() {
        return this.toolbarItem;
    }
    // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|null>
    titleFor(id) {
        if (id === null) {
            return i18nString(UIStrings.filterAllPreloads);
        }
        const ruleSet = this.model.getRuleSetById(id);
        if (ruleSet === null) {
            return i18n.i18n.lockedString('Internal error');
        }
        return PreloadingUIUtils.ruleSetLocationShort(ruleSet, pageURL());
    }
    subtitleFor(id) {
        const countsByStatus = this.model.getPreloadCountsByRuleSetId().get(id) || new Map();
        return PreloadingUIUtils.preloadsStatusSummary(countsByStatus);
    }
    // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|null>
    createElementForItem(id) {
        const element = document.createElement('div');
        const shadowRoot = UI.Utils.createShadowRootWithCoreStyles(element, { cssFile: undefined, delegatesFocus: undefined });
        const title = shadowRoot.createChild('div', 'title');
        UI.UIUtils.createTextChild(title, Platform.StringUtilities.trimEndWithMaxLength(this.titleFor(id), 100));
        const subTitle = shadowRoot.createChild('div', 'subtitle');
        UI.UIUtils.createTextChild(subTitle, this.subtitleFor(id));
        return element;
    }
    // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|null>
    isItemSelectable(_id) {
        return true;
    }
    // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|null>
    itemSelected(_id) {
        this.onSelectionChanged();
    }
    // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|null>
    highlightedItemChanged(_from, _to, _fromElement, _toElement) {
    }
}
export class PreloadingWarningsView extends UI.Widget.VBox {
    infobar = new PreloadingComponents.PreloadingDisabledInfobar.PreloadingDisabledInfobar();
    constructor() {
        super(/* isWebComponent */ false, /* delegatesFocus */ false);
    }
    wasShown() {
        super.wasShown();
        this.registerCSSFiles([emptyWidgetStyles]);
        this.contentElement.append(this.infobar);
    }
    onWarningsUpdated(args) {
        this.infobar.data = args.data;
    }
}
//# sourceMappingURL=PreloadingView.js.map