// 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 { assert, assertNotReached } from 'chrome://resources/js/assert.js';
import { CustomElement } from 'chrome://resources/js/custom_element.js';
import { PromiseResolver } from 'chrome://resources/js/promise_resolver.js';
import { getTemplate } from './app.html.js';
import { SiteEngagementDetailsProvider } from './site_engagement_details.mojom-webui.js';
/**
 * Rounds the supplied value to two decimal places of accuracy.
 */
function roundScore(score) {
    return Math.round(score * 100) / 100;
}
/**
 * Compares two SiteEngagementDetails objects based on |sortKey|.
 * @param sortKey The name of the property to sort by.
 * @return A negative number if |a| should be ordered before |b|, a
 *     positive number otherwise.
 */
function compareTableItem(sortKey, a, b) {
    const val1 = a[sortKey];
    const val2 = b[sortKey];
    // Compare the hosts of the origin ignoring schemes.
    if (sortKey === 'origin') {
        return new URL(val1).host > new URL(val2).host ? 1 : -1;
    }
    if (sortKey === 'baseScore' || sortKey === 'installedBonus' ||
        sortKey === 'totalScore') {
        if (typeof val1 === 'number' && typeof val2 === 'number') {
            return val1 - val2;
        }
        return NaN;
    }
    assertNotReached('Unsupported sort key: ' + sortKey);
}
export class SiteEngagementAppElement extends CustomElement {
    static get is() {
        return 'site-engagement-app';
    }
    static get template() {
        return getTemplate();
    }
    engagementTableBody = null;
    info = null;
    engagementDetailsProvider = SiteEngagementDetailsProvider.getRemote();
    updateInterval = null;
    showWebUiPages = false;
    sortKey = 'totalScore';
    sortReverse = true;
    whenPopulatedResolver = new PromiseResolver();
    connectedCallback() {
        const engagementTableHeader = this.getRequiredElement('#engagement-table-header');
        this.engagementTableBody =
            this.getRequiredElement('#engagement-table-body');
        const headers = engagementTableHeader.children;
        for (let i = 0; i < headers.length; i++) {
            headers[i].addEventListener('click', e => {
                const target = e.target;
                const newSortKey = target.getAttribute('sort-key');
                assert(newSortKey);
                if (this.sortKey === newSortKey) {
                    this.sortReverse = !this.sortReverse;
                }
                else {
                    this.sortKey = newSortKey;
                    this.sortReverse = false;
                }
                const oldSortColumn = this.getRequiredElement('.sort-column');
                oldSortColumn.classList.remove('sort-column');
                target.classList.add('sort-column');
                target.toggleAttribute('sort-reverse', this.sortReverse);
                this.renderTable();
            });
        }
        const showWebUiPagesCheckbox = this.getRequiredElement('#show-webui-pages-checkbox');
        showWebUiPagesCheckbox.addEventListener('change', () => this.handleShowWebUiPages(showWebUiPagesCheckbox.checked));
        this.updateEngagementTable();
        this.enableAutoupdate();
    }
    /**
     * Creates a single row in the engagement table.
     * @param info The info to create the row from.
     */
    createRow(info) {
        const originCell = document.createElement('td');
        originCell.classList.add('origin-cell');
        originCell.textContent = info.origin;
        const baseScoreInput = document.createElement('input');
        baseScoreInput.classList.add('base-score-input');
        baseScoreInput.addEventListener('focus', () => this.disableAutoupdate());
        baseScoreInput.addEventListener('blur', () => this.enableAutoupdate());
        baseScoreInput.value = String(info.baseScore);
        const baseScoreCell = document.createElement('td');
        baseScoreCell.classList.add('base-score-cell');
        baseScoreCell.appendChild(baseScoreInput);
        const bonusScoreCell = document.createElement('td');
        bonusScoreCell.classList.add('bonus-score-cell');
        bonusScoreCell.textContent = String(info.installedBonus);
        const totalScoreCell = document.createElement('td');
        totalScoreCell.classList.add('total-score-cell');
        totalScoreCell.textContent = String(info.totalScore);
        const engagementBar = document.createElement('div');
        engagementBar.classList.add('engagement-bar');
        engagementBar.style.width = (info.totalScore * 4) + 'px';
        const engagementBarCell = document.createElement('td');
        engagementBarCell.classList.add('engagement-bar-cell');
        engagementBarCell.appendChild(engagementBar);
        const row = document.createElement('tr');
        row.appendChild(originCell);
        row.appendChild(baseScoreCell);
        row.appendChild(bonusScoreCell);
        row.appendChild(totalScoreCell);
        row.appendChild(engagementBarCell);
        baseScoreInput.addEventListener('change', (e) => this.handleBaseScoreChange(info.origin, engagementBar, e));
        return row;
    }
    disableAutoupdate() {
        if (this.updateInterval) {
            clearInterval(this.updateInterval);
        }
        this.updateInterval = null;
    }
    enableAutoupdate() {
        if (this.updateInterval) {
            clearInterval(this.updateInterval);
        }
        this.updateInterval = setInterval(() => this.updateEngagementTable(), 5000);
    }
    /**
     * Sets the base engagement score when a score input is changed.
     * Resets the length of engagement-bar-cell to match the new score.
     * Also resets the update interval.
     * @param origin The origin of the engagement score to set.
     */
    handleBaseScoreChange(origin, barCell, e) {
        const baseScoreInput = e.target;
        this.engagementDetailsProvider.setSiteEngagementBaseScoreForUrl(origin, parseFloat(baseScoreInput.value));
        barCell.style.width = (parseFloat(baseScoreInput.value) * 4) + 'px';
        baseScoreInput.blur();
        this.enableAutoupdate();
    }
    /**
     * Adds a new origin with the given base score.
     * @param originInput The text input containing the origin to add.
     * @param scoreInput The text input containing the score to add.
     */
    handleAddOrigin(originInput, scoreInput) {
        try {
            // Validate the URL. If we don't validate here, IPC will kill this
            // renderer on invalid URLs. Other checks like scheme are done on the
            // browser side.
            new URL(originInput.value);
        }
        catch {
            return;
        }
        const origin = originInput.value;
        const score = parseFloat(scoreInput.value);
        this.engagementDetailsProvider.setSiteEngagementBaseScoreForUrl(origin, score);
        scoreInput.blur();
        this.updateEngagementTable();
        this.enableAutoupdate();
    }
    /**
     * Show chrome:// and chrome-untrusted:// pages.
     */
    handleShowWebUiPages(show) {
        this.showWebUiPages = show;
        this.renderTable();
    }
    /**
     * Remove all rows from the engagement table.
     */
    clearTable() {
        assert(this.engagementTableBody);
        this.engagementTableBody.innerHTML = window.trustedTypes.emptyHTML;
    }
    /**
     * Sort the engagement info based on |sortKey| and |sortReverse|.
     */
    sortInfo() {
        assert(this.info);
        this.info.sort((a, b) => {
            return (this.sortReverse ? -1 : 1) * compareTableItem(this.sortKey, a, b);
        });
    }
    /**
     * Regenerates the engagement table from |info|.
     */
    renderTable() {
        this.clearTable();
        this.sortInfo();
        assert(this.info);
        this.info.forEach((info) => {
            if (!this.showWebUiPages &&
                (info.origin.startsWith('chrome://') ||
                    info.origin.startsWith('chrome-untrusted://'))) {
                return;
            }
            // Round all scores to 2 decimal places.
            info.baseScore = roundScore(info.baseScore);
            info.installedBonus = roundScore(info.installedBonus);
            info.totalScore = roundScore(info.totalScore);
            assert(this.engagementTableBody);
            this.engagementTableBody.appendChild(this.createRow(info));
        });
        // Add another row for adding a new origin.
        const originInput = document.createElement('input');
        originInput.classList.add('origin-input');
        originInput.addEventListener('focus', () => this.disableAutoupdate());
        originInput.addEventListener('blur', () => this.enableAutoupdate());
        originInput.value = 'http://example.com';
        const originCell = document.createElement('td');
        originCell.appendChild(originInput);
        const baseScoreInput = document.createElement('input');
        baseScoreInput.classList.add('base-score-input');
        baseScoreInput.addEventListener('focus', () => this.disableAutoupdate());
        baseScoreInput.addEventListener('blur', () => this.enableAutoupdate());
        baseScoreInput.value = '0';
        const baseScoreCell = document.createElement('td');
        baseScoreCell.classList.add('base-score-cell');
        baseScoreCell.appendChild(baseScoreInput);
        const addButton = document.createElement('button');
        addButton.textContent = 'Add';
        addButton.classList.add('add-origin-button');
        const buttonCell = document.createElement('td');
        buttonCell.colSpan = 2;
        buttonCell.classList.add('base-score-cell');
        buttonCell.appendChild(addButton);
        const row = document.createElement('tr');
        row.appendChild(originCell);
        row.appendChild(baseScoreCell);
        row.appendChild(buttonCell);
        addButton.addEventListener('click', () => this.handleAddOrigin(originInput, baseScoreInput));
        assert(this.engagementTableBody);
        this.engagementTableBody.appendChild(row);
    }
    /**
     * Retrieve site engagement info and render the engagement table.
     */
    async updateEngagementTable() {
        // Populate engagement table.
        this.info =
            (await this.engagementDetailsProvider.getSiteEngagementDetails()).info;
        this.renderTable();
        this.whenPopulatedResolver.resolve();
    }
    whenPopulatedForTest() {
        return this.whenPopulatedResolver.promise;
    }
}
customElements.define(SiteEngagementAppElement.is, SiteEngagementAppElement);
