// Copyright 2014 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.

/* eslint-disable rulesdir/no_underscored_properties */

import * as Common from '../common/common.js';
import * as i18n from '../i18n/i18n.js';
import * as UI from '../ui/ui.js';

export const UIStrings = {
  /**
  *@description Header text content in Framework Ignore List Settings Tab of the Settings
  */
  frameworkIgnoreList: 'Framework Ignore List',
  /**
  *@description Text in Framework Ignore List Settings Tab of the Settings
  */
  debuggerWillSkipThroughThe: 'Debugger will skip through the scripts and will not stop on exceptions thrown by them.',
  /**
  *@description Text in Framework Ignore List Settings Tab of the Settings
  */
  ignoreListContentScripts: 'Add content scripts to ignore list',
  /**
  *@description Ignore List content scripts title in Framework Ignore List Settings Tab of the Settings
  */
  ignoreListContentScriptsExtension: 'Add content scripts to ignore list (extension scripts in the page)',
  /**
  *@description Ignore List label in Framework Ignore List Settings Tab of the Settings
  */
  ignoreList: 'Ignore List',
  /**
  *@description Text to indicate something is not enabled
  */
  disabled: 'Disabled',
  /**
  *@description Placeholder text content in Framework Ignore List Settings Tab of the Settings
  */
  noIgnoreListPatterns: 'No ignore list patterns',
  /**
  *@description Text of the add pattern button in Framework Ignore List Settings Tab of the Settings
  */
  addPattern: 'Add pattern...',
  /**
  *@description Aria accessible name in Framework Ignore List Settings Tab of the Settings
  */
  addFilenamePattern: 'Add filename pattern',
  /**
  *@description Pattern title in Framework Ignore List Settings Tab of the Settings
  *@example {ad.*?} PH1
  */
  ignoreScriptsWhoseNamesMatchS: 'Ignore scripts whose names match \'{PH1}\'',
  /**
  *@description Aria accessible name in Framework Ignore List Settings Tab of the Settings. It labels the input
  * field used to add new or edit existing regular expressions that match file names to ignore in the debugger.
  */
  pattern: 'Pattern',
  /**
  *@description Aria accessible name in Framework Ignore List Settings Tab of the Settings
  */
  behavior: 'Behavior',
  /**
  *@description Error message in Framework Ignore List settings pane that declares pattern must not be empty
  */
  patternCannotBeEmpty: 'Pattern cannot be empty',
  /**
  *@description Error message in Framework Ignore List settings pane that declares pattern already exits
  */
  patternAlreadyExists: 'Pattern already exists',
  /**
  *@description Error message in Framework Ignore List settings pane that declares pattern must be a valid regular expression
  */
  patternMustBeAValidRegular: 'Pattern must be a valid regular expression',
};
const str_ = i18n.i18n.registerUIStrings('settings/FrameworkIgnoreListSettingsTab.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

let frameworkIgnoreListSettingsTabInstance: FrameworkIgnoreListSettingsTab;
export class FrameworkIgnoreListSettingsTab extends UI.Widget.VBox implements
    UI.ListWidget.Delegate<Common.Settings.RegExpSettingItem> {
  _ignoreListLabel: Common.UIString.LocalizedString;
  _disabledLabel: Common.UIString.LocalizedString;
  _list: UI.ListWidget.ListWidget<Common.Settings.RegExpSettingItem>;
  _setting: Common.Settings.RegExpSetting;
  _editor?: UI.ListWidget.Editor<Common.Settings.RegExpSettingItem>;

  constructor() {
    super(true);
    this.registerRequiredCSS('settings/frameworkIgnoreListSettingsTab.css', {enableLegacyPatching: true});

    const header = this.contentElement.createChild('div', 'header');
    header.textContent = i18nString(UIStrings.frameworkIgnoreList);
    UI.ARIAUtils.markAsHeading(header, 1);
    this.contentElement.createChild('div', 'intro').textContent = i18nString(UIStrings.debuggerWillSkipThroughThe);

    const ignoreListContentScripts = this.contentElement.createChild('div', 'ignore-list-content-scripts');
    ignoreListContentScripts.appendChild(UI.SettingsUI.createSettingCheckbox(
        i18nString(UIStrings.ignoreListContentScripts),
        Common.Settings.Settings.instance().moduleSetting('skipContentScripts'), true));
    UI.Tooltip.Tooltip.install(ignoreListContentScripts, i18nString(UIStrings.ignoreListContentScriptsExtension));

    this._ignoreListLabel = i18nString(UIStrings.ignoreList);
    this._disabledLabel = i18nString(UIStrings.disabled);

    this._list = new UI.ListWidget.ListWidget(this);
    this._list.element.classList.add('ignore-list');
    this._list.registerRequiredCSS('settings/frameworkIgnoreListSettingsTab.css', {enableLegacyPatching: true});

    const placeholder = document.createElement('div');
    placeholder.classList.add('ignore-list-empty');
    placeholder.textContent = i18nString(UIStrings.noIgnoreListPatterns);
    this._list.setEmptyPlaceholder(placeholder);
    this._list.show(this.contentElement);
    const addPatternButton =
        UI.UIUtils.createTextButton(i18nString(UIStrings.addPattern), this._addButtonClicked.bind(this), 'add-button');
    UI.ARIAUtils.setAccessibleName(addPatternButton, i18nString(UIStrings.addFilenamePattern));
    this.contentElement.appendChild(addPatternButton);
    this._setting =
        Common.Settings.Settings.instance().moduleSetting('skipStackFramesPattern') as Common.Settings.RegExpSetting;
    this._setting.addChangeListener(this._settingUpdated, this);

    this.setDefaultFocusedElement(addPatternButton);
  }

  static instance(opts = {forceNew: null}): FrameworkIgnoreListSettingsTab {
    const {forceNew} = opts;
    if (!frameworkIgnoreListSettingsTabInstance || forceNew) {
      frameworkIgnoreListSettingsTabInstance = new FrameworkIgnoreListSettingsTab();
    }

    return frameworkIgnoreListSettingsTabInstance;
  }

  wasShown(): void {
    super.wasShown();
    this._settingUpdated();
  }

  _settingUpdated(): void {
    this._list.clear();
    const patterns = this._setting.getAsArray();
    for (let i = 0; i < patterns.length; ++i) {
      this._list.appendItem(patterns[i], true);
    }
  }

  _addButtonClicked(): void {
    this._list.addNewItem(this._setting.getAsArray().length, {pattern: '', disabled: false});
  }

  renderItem(item: Common.Settings.RegExpSettingItem, _editable: boolean): Element {
    const element = document.createElement('div');
    element.classList.add('ignore-list-item');
    const pattern = element.createChild('div', 'ignore-list-pattern');
    pattern.textContent = item.pattern;
    UI.Tooltip.Tooltip.install(pattern, i18nString(UIStrings.ignoreScriptsWhoseNamesMatchS, {PH1: item.pattern}));
    element.createChild('div', 'ignore-list-separator');
    element.createChild('div', 'ignore-list-behavior').textContent =
        item.disabled ? this._disabledLabel : this._ignoreListLabel;
    if (item.disabled) {
      element.classList.add('ignore-list-disabled');
    }
    return element;
  }

  removeItemRequested(item: Common.Settings.RegExpSettingItem, index: number): void {
    const patterns = this._setting.getAsArray();
    patterns.splice(index, 1);
    this._setting.setAsArray(patterns);
  }

  commitEdit(
      item: Common.Settings.RegExpSettingItem, editor: UI.ListWidget.Editor<Common.Settings.RegExpSettingItem>,
      isNew: boolean): void {
    item.pattern = editor.control('pattern').value.trim();
    item.disabled = editor.control('behavior').value === this._disabledLabel;

    const list = this._setting.getAsArray();
    if (isNew) {
      list.push(item);
    }
    this._setting.setAsArray(list);
  }

  beginEdit(item: Common.Settings.RegExpSettingItem): UI.ListWidget.Editor<Common.Settings.RegExpSettingItem> {
    const editor = this._createEditor();
    editor.control('pattern').value = item.pattern;
    editor.control('behavior').value = item.disabled ? this._disabledLabel : this._ignoreListLabel;
    return editor;
  }

  _createEditor(): UI.ListWidget.Editor<Common.Settings.RegExpSettingItem> {
    if (this._editor) {
      return this._editor;
    }

    const editor = new UI.ListWidget.Editor<Common.Settings.RegExpSettingItem>();
    this._editor = editor;
    const content = editor.contentElement();

    const titles = content.createChild('div', 'ignore-list-edit-row');
    titles.createChild('div', 'ignore-list-pattern').textContent = i18nString(UIStrings.pattern);
    titles.createChild('div', 'ignore-list-separator ignore-list-separator-invisible');
    titles.createChild('div', 'ignore-list-behavior').textContent = i18nString(UIStrings.behavior);

    const fields = content.createChild('div', 'ignore-list-edit-row');
    const pattern = editor.createInput('pattern', 'text', '/framework\\.js$', patternValidator.bind(this));
    UI.ARIAUtils.setAccessibleName(pattern, i18nString(UIStrings.pattern));
    fields.createChild('div', 'ignore-list-pattern').appendChild(pattern);
    fields.createChild('div', 'ignore-list-separator ignore-list-separator-invisible');
    const behavior = editor.createSelect('behavior', [this._ignoreListLabel, this._disabledLabel], behaviorValidator);
    UI.ARIAUtils.setAccessibleName(behavior, i18nString(UIStrings.behavior));
    fields.createChild('div', 'ignore-list-behavior').appendChild(behavior);

    return editor;

    function patternValidator(
        this: FrameworkIgnoreListSettingsTab, item: Common.Settings.RegExpSettingItem, index: number,
        input: HTMLInputElement|HTMLSelectElement): UI.ListWidget.ValidatorResult {
      const pattern = input.value.trim();
      const patterns = this._setting.getAsArray();

      if (!pattern.length) {
        return {valid: false, errorMessage: i18nString(UIStrings.patternCannotBeEmpty)};
      }

      for (let i = 0; i < patterns.length; ++i) {
        if (i !== index && patterns[i].pattern === pattern) {
          return {valid: false, errorMessage: i18nString(UIStrings.patternAlreadyExists)};
        }
      }

      let regex;
      try {
        regex = new RegExp(pattern);
      } catch (e) {
      }
      if (!regex) {
        return {valid: false, errorMessage: i18nString(UIStrings.patternMustBeAValidRegular)};
      }
      return {valid: true, errorMessage: undefined};
    }

    function behaviorValidator(
        _item: Common.Settings.RegExpSettingItem, _index: number,
        _input: HTMLInputElement|HTMLSelectElement): UI.ListWidget.ValidatorResult {
      return {valid: true, errorMessage: undefined};
    }
  }
}
