// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable @devtools/no-imperative-dom-api */
import '../../ui/kit/kit.js';
import * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
import locationsSettingsTabStyles from './locationsSettingsTab.css.js';
const UIStrings = {
    /**
     * @description Title in the Locations Settings Tab, where custom geographic locations that the user
     *has entered are stored.
     */
    locations: 'Locations',
    /**
     * @description Label for the name of a geographic location that the user has entered.
     */
    locationName: 'Location name',
    /**
     * @description Abbreviation of latitude in Locations Settings Tab of the Device Toolbar
     */
    lat: 'Lat',
    /**
     * @description Abbreviation of longitude in Locations Settings Tab of the Device Toolbar
     */
    long: 'Long',
    /**
     * @description Text in Sensors View of the Device Toolbar
     */
    timezoneId: 'Timezone ID',
    /**
     * @description Label for text input for the locale of a particular location.
     */
    locale: 'Locale',
    /**
     * @description Label for text input for the latitude of a GPS position.
     */
    latitude: 'Latitude',
    /**
     * @description Label for text input for the longitude of a GPS position.
     */
    longitude: 'Longitude',
    /**
     * @description Label for text input for the accuracy of a GPS position.
     */
    accuracy: 'Accuracy',
    /**
     * @description Error message in the Locations settings pane that declares the location name input must not be empty
     */
    locationNameCannotBeEmpty: 'Location name cannot be empty',
    /**
     * @description Error message in the Locations settings pane that declares the maximum length of the location name
     * @example {50} PH1
     */
    locationNameMustBeLessThanS: 'Location name must be less than {PH1} characters',
    /**
     * @description Error message in the Locations settings pane that declares that the value for the latitude input must be a number
     */
    latitudeMustBeANumber: 'Latitude must be a number',
    /**
     * @description Error message in the Locations settings pane that declares the minimum value for the latitude input
     * @example {-90} PH1
     */
    latitudeMustBeGreaterThanOrEqual: 'Latitude must be greater than or equal to {PH1}',
    /**
     * @description Error message in the Locations settings pane that declares the maximum value for the latitude input
     * @example {90} PH1
     */
    latitudeMustBeLessThanOrEqualToS: 'Latitude must be less than or equal to {PH1}',
    /**
     * @description Error message in the Locations settings pane that declares that the value for the longitude input must be a number
     */
    longitudeMustBeANumber: 'Longitude must be a number',
    /**
     * @description Error message in the Locations settings pane that declares the minimum value for the longitude input
     * @example {-180} PH1
     */
    longitudeMustBeGreaterThanOr: 'Longitude must be greater than or equal to {PH1}',
    /**
     * @description Error message in the Locations settings pane that declares the maximum value for the longitude input
     * @example {180} PH1
     */
    longitudeMustBeLessThanOrEqualTo: 'Longitude must be less than or equal to {PH1}',
    /**
     * @description Error message in the Locations settings pane that declares timezone ID input invalid
     */
    timezoneIdMustContainAlphabetic: 'Timezone ID must contain alphabetic characters',
    /**
     * @description Error message in the Locations settings pane that declares locale input invalid
     */
    localeMustContainAlphabetic: 'Locale must contain alphabetic characters',
    /**
     * @description Error message in the Locations settings pane that declares that the value for the accuracy input must be a number
     */
    accuracyMustBeANumber: 'Accuracy must be a number',
    /**
     * @description Error message in the Locations settings pane that declares the minimum value for the accuracy input
     * @example {0} PH1
     */
    accuracyMustBeGreaterThanOrEqual: 'Accuracy must be greater than or equal to {PH1}',
    /**
     * @description Text of add locations button in Locations Settings Tab of the Device Toolbar
     */
    addLocation: 'Add location',
};
const str_ = i18n.i18n.registerUIStrings('panels/sensors/LocationsSettingsTab.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class LocationsSettingsTab extends UI.Widget.VBox {
    list;
    customSetting;
    editor;
    constructor() {
        super({
            jslog: `${VisualLogging.pane('emulation-locations')}`,
            useShadowDom: true,
        });
        this.registerRequiredCSS(locationsSettingsTabStyles);
        const settingsContent = this.contentElement.createChild('div', 'settings-card-container-wrapper').createChild('div');
        settingsContent.classList.add('settings-card-container');
        const locationsCard = settingsContent.createChild('devtools-card');
        locationsCard.heading = i18nString(UIStrings.locations);
        const listContainer = locationsCard.createChild('div');
        this.list = new UI.ListWidget.ListWidget(this, undefined, true);
        this.list.element.classList.add('locations-list');
        this.list.registerRequiredCSS(locationsSettingsTabStyles);
        this.list.show(listContainer);
        this.customSetting =
            Common.Settings.Settings.instance().moduleSetting('emulation.locations');
        const list = this.customSetting.get().map(location => replaceLocationTitles(location, this.customSetting.defaultValue));
        function replaceLocationTitles(location, defaultValues) {
            // This check is done for locations that might had been cached wrongly due to crbug.com/1171670.
            // Each of the default values would have been stored without a title if the user had added a new location
            // while the bug was present in the application. This means that getting the setting's default value with the `get`
            // method would return the default locations without a title. To cope with this, the setting values are
            // preemptively checked and corrected so that any default value mistakenly stored without a title is replaced
            // with the corresponding declared value in the pre-registered setting.
            if (!location.title) {
                const replacement = defaultValues.find(defaultLocation => defaultLocation.lat === location.lat && defaultLocation.long === location.long &&
                    defaultLocation.timezoneId === location.timezoneId && defaultLocation.locale === location.locale);
                if (!replacement) {
                    console.error('Could not determine a location setting title');
                }
                else {
                    return replacement;
                }
            }
            return location;
        }
        const addButton = new Buttons.Button.Button();
        addButton.classList.add('add-locations-button');
        addButton.data = {
            variant: "outlined" /* Buttons.Button.Variant.OUTLINED */,
            iconName: 'plus',
            jslogContext: 'emulation.add-location',
        };
        addButton.textContent = i18nString(UIStrings.addLocation);
        addButton.addEventListener('click', () => this.addButtonClicked());
        locationsCard.append(addButton);
        this.customSetting.set(list);
        this.customSetting.addChangeListener(this.locationsUpdated, this);
    }
    wasShown() {
        super.wasShown();
        this.locationsUpdated();
    }
    locationsUpdated() {
        this.list.clear();
        const conditions = this.customSetting.get();
        for (const condition of conditions) {
            this.list.appendItem(condition, true);
        }
        this.list.appendSeparator();
    }
    addButtonClicked() {
        this.list.addNewItem(this.customSetting.get().length, {
            title: '',
            lat: 0,
            long: 0,
            timezoneId: '',
            locale: '',
            accuracy: SDK.EmulationModel.Location.DEFAULT_ACCURACY
        });
    }
    renderItem(location, _editable) {
        const element = document.createElement('div');
        element.role = 'row';
        element.classList.add('locations-list-item');
        const title = element.createChild('div', 'locations-list-text locations-list-title');
        title.role = 'cell';
        const titleText = title.createChild('div', 'locations-list-title-text');
        titleText.textContent = location.title;
        UI.Tooltip.Tooltip.install(titleText, location.title);
        element.createChild('div', 'locations-list-separator');
        const lat = element.createChild('div', 'locations-list-text');
        lat.textContent = String(location.lat);
        lat.role = 'cell';
        element.createChild('div', 'locations-list-separator');
        const long = element.createChild('div', 'locations-list-text');
        long.textContent = String(location.long);
        long.role = 'cell';
        element.createChild('div', 'locations-list-separator');
        const timezoneId = element.createChild('div', 'locations-list-text');
        timezoneId.textContent = location.timezoneId;
        timezoneId.role = 'cell';
        element.createChild('div', 'locations-list-separator');
        const locale = element.createChild('div', 'locations-list-text');
        locale.textContent = location.locale;
        locale.role = 'cell';
        element.createChild('div', 'locations-list-separator');
        element.createChild('div', 'locations-list-text').textContent =
            String(location.accuracy || SDK.EmulationModel.Location.DEFAULT_ACCURACY);
        return element;
    }
    removeItemRequested(_item, index) {
        const list = this.customSetting.get();
        list.splice(index, 1);
        this.customSetting.set(list);
    }
    commitEdit(location, editor, isNew) {
        location.title = editor.control('title').value.trim();
        const lat = editor.control('lat').value.trim();
        location.lat = lat ? parseFloat(lat) : 0;
        const long = editor.control('long').value.trim();
        location.long = long ? parseFloat(long) : 0;
        const timezoneId = editor.control('timezone-id').value.trim();
        location.timezoneId = timezoneId;
        const locale = editor.control('locale').value.trim();
        location.locale = locale;
        const accuracy = editor.control('accuracy').value.trim();
        location.accuracy = accuracy ? parseFloat(accuracy) : SDK.EmulationModel.Location.DEFAULT_ACCURACY;
        const list = this.customSetting.get();
        if (isNew) {
            list.push(location);
        }
        this.customSetting.set(list);
    }
    beginEdit(location) {
        const editor = this.createEditor();
        editor.control('title').value = location.title;
        editor.control('lat').value = String(location.lat);
        editor.control('long').value = String(location.long);
        editor.control('timezone-id').value = location.timezoneId;
        editor.control('locale').value = location.locale;
        editor.control('accuracy').value = String(location.accuracy || SDK.EmulationModel.Location.DEFAULT_ACCURACY);
        return editor;
    }
    createEditor() {
        if (this.editor) {
            return this.editor;
        }
        const editor = new UI.ListWidget.Editor();
        this.editor = editor;
        const content = editor.contentElement();
        const titles = content.createChild('div', 'locations-edit-row');
        titles.createChild('div', 'locations-list-text locations-list-title').textContent =
            i18nString(UIStrings.locationName);
        titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.lat);
        titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.long);
        titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.timezoneId);
        titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.locale);
        titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.accuracy);
        const fields = content.createChild('div', 'locations-edit-row');
        fields.createChild('div', 'locations-list-text locations-list-title locations-input-container')
            .appendChild(editor.createInput('title', 'text', i18nString(UIStrings.locationName), titleValidator));
        fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        let cell = fields.createChild('div', 'locations-list-text locations-input-container');
        cell.appendChild(editor.createInput('lat', 'text', i18nString(UIStrings.latitude), latValidator));
        fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        cell = fields.createChild('div', 'locations-list-text locations-list-text-longitude locations-input-container');
        cell.appendChild(editor.createInput('long', 'text', i18nString(UIStrings.longitude), longValidator));
        fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        cell = fields.createChild('div', 'locations-list-text locations-input-container');
        cell.appendChild(editor.createInput('timezone-id', 'text', i18nString(UIStrings.timezoneId), timezoneIdValidator));
        fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        cell = fields.createChild('div', 'locations-list-text locations-input-container');
        cell.appendChild(editor.createInput('locale', 'text', i18nString(UIStrings.locale), localeValidator));
        fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
        cell = fields.createChild('div', 'locations-list-text locations-input-container');
        cell.appendChild(editor.createInput('accuracy', 'text', i18nString(UIStrings.accuracy), accuracyValidator));
        return editor;
        function titleValidator(_item, _index, input) {
            const maxLength = 50;
            const value = input.value.trim();
            let errorMessage;
            if (!value.length) {
                errorMessage = i18nString(UIStrings.locationNameCannotBeEmpty);
            }
            else if (value.length > maxLength) {
                errorMessage = i18nString(UIStrings.locationNameMustBeLessThanS, { PH1: maxLength });
            }
            if (errorMessage) {
                return { valid: false, errorMessage };
            }
            return {
                valid: true,
            };
        }
        function latValidator(_item, _index, input) {
            const minLat = -90;
            const maxLat = 90;
            const value = input.value.trim();
            const parsedValue = Number(value);
            if (!value) {
                return {
                    valid: true,
                };
            }
            let errorMessage;
            if (Number.isNaN(parsedValue)) {
                errorMessage = i18nString(UIStrings.latitudeMustBeANumber);
            }
            else if (parseFloat(value) < minLat) {
                errorMessage = i18nString(UIStrings.latitudeMustBeGreaterThanOrEqual, { PH1: minLat });
            }
            else if (parseFloat(value) > maxLat) {
                errorMessage = i18nString(UIStrings.latitudeMustBeLessThanOrEqualToS, { PH1: maxLat });
            }
            if (errorMessage) {
                return { valid: false, errorMessage };
            }
            return {
                valid: true,
            };
        }
        function longValidator(_item, _index, input) {
            const minLong = -180;
            const maxLong = 180;
            const value = input.value.trim();
            const parsedValue = Number(value);
            if (!value) {
                return {
                    valid: true,
                };
            }
            let errorMessage;
            if (Number.isNaN(parsedValue)) {
                errorMessage = i18nString(UIStrings.longitudeMustBeANumber);
            }
            else if (parseFloat(value) < minLong) {
                errorMessage = i18nString(UIStrings.longitudeMustBeGreaterThanOr, { PH1: minLong });
            }
            else if (parseFloat(value) > maxLong) {
                errorMessage = i18nString(UIStrings.longitudeMustBeLessThanOrEqualTo, { PH1: maxLong });
            }
            if (errorMessage) {
                return { valid: false, errorMessage };
            }
            return {
                valid: true,
            };
        }
        function timezoneIdValidator(_item, _index, input) {
            const value = input.value.trim();
            // Chromium uses ICU's timezone implementation, which is very
            // liberal in what it accepts. ICU does not simply use an allowlist
            // but instead tries to make sense of the input, even for
            // weird-looking timezone IDs. There's not much point in validating
            // the input other than checking if it contains at least one
            // alphabetic character. The empty string resets the override,
            // and is accepted as well.
            if (value === '' || /[a-zA-Z]/.test(value)) {
                return {
                    valid: true,
                };
            }
            const errorMessage = i18nString(UIStrings.timezoneIdMustContainAlphabetic);
            return { valid: false, errorMessage };
        }
        function localeValidator(_item, _index, input) {
            const value = input.value.trim();
            // Similarly to timezone IDs, there's not much point in validating
            // input locales other than checking if it contains at least two
            // alphabetic characters.
            // https://unicode.org/reports/tr35/#Unicode_language_identifier
            // The empty string resets the override, and is accepted as
            // well.
            if (value === '' || /[a-zA-Z]{2}/.test(value)) {
                return {
                    valid: true,
                };
            }
            const errorMessage = i18nString(UIStrings.localeMustContainAlphabetic);
            return { valid: false, errorMessage };
        }
        function accuracyValidator(_item, _index, input) {
            const minAccuracy = 0;
            const value = input.value.trim();
            const parsedValue = Number(value);
            if (!value) {
                return {
                    valid: true,
                };
            }
            let errorMessage;
            if (Number.isNaN(parsedValue)) {
                errorMessage = i18nString(UIStrings.accuracyMustBeANumber);
            }
            else if (parseFloat(value) < minAccuracy) {
                errorMessage = i18nString(UIStrings.accuracyMustBeGreaterThanOrEqual, { PH1: minAccuracy });
            }
            if (errorMessage) {
                return { valid: false, errorMessage };
            }
            return {
                valid: true,
            };
        }
    }
}
//# sourceMappingURL=LocationsSettingsTab.js.map