// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as i18n from '../i18n/i18n.js';
import type * as Platform from '../platform/platform.js';
import * as Root from '../root/root.js';

import type {SettingStorageType} from './Settings.js';

const UIStrings = {
  /**
   * @description Title of the Elements Panel
   */
  elements: 'Elements',
  /**
   * @description Text for DevTools AI
   */
  ai: 'AI',
  /**
   * @description Text for DevTools appearance
   */
  appearance: 'Appearance',
  /**
   * @description Name of the Sources panel
   */
  sources: 'Sources',
  /**
   * @description Title of the Network tool
   */
  network: 'Network',
  /**
   * @description Text for the performance of something
   */
  performance: 'Performance',
  /**
   * @description Title of the Console tool
   */
  console: 'Console',
  /**
   * @description A title of the 'Persistence' setting category
   */
  persistence: 'Persistence',
  /**
   * @description Text that refers to the debugger
   */
  debugger: 'Debugger',
  /**
   * @description Text describing global shortcuts and settings that are available throughout the DevTools
   */
  global: 'Global',
  /**
   * @description Title of the Rendering tool
   */
  rendering: 'Rendering',
  /**
   * @description Title of a section on CSS Grid tooling
   */
  grid: 'Grid',
  /**
   * @description Text for the mobile platform, as opposed to desktop
   */
  mobile: 'Mobile',
  /**
   * @description Text for the memory of the page
   */
  memory: 'Memory',
  /**
   * @description Text for the extension of the page
   */
  extension: 'Extension',
  /**
   * @description Text for the adorner of the page
   */
  adorner: 'Adorner',
  /**
   * @description Header for the "Account" section in the settings UI. The "Account"
   * section allows users see their signed in account and configure which DevTools data is synced via Chrome Sync.
   */
  account: 'Account',
  /**
   * @description Text for the privacy section of the page.
   */
  privacy: 'Privacy',
} as const;
const str_ = i18n.i18n.registerUIStrings('core/common/SettingRegistration.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
let registeredSettings: SettingRegistration[] = [];
const settingNameSet = new Set<string>();

export function registerSettingExtension(registration: SettingRegistration): void {
  const settingName = registration.settingName;
  if (settingNameSet.has(settingName)) {
    throw new Error(`Duplicate setting name '${settingName}'`);
  }
  settingNameSet.add(settingName);
  registeredSettings.push(registration);
}

export function getRegisteredSettings(): SettingRegistration[] {
  return registeredSettings.filter(setting => Root.Runtime.Runtime.isDescriptorEnabled(setting));
}

export function registerSettingsForTest(settings: SettingRegistration[], forceReset = false): void {
  if (registeredSettings.length === 0 || forceReset) {
    registeredSettings = settings;
    settingNameSet.clear();
    for (const setting of settings) {
      const settingName = setting.settingName;
      if (settingNameSet.has(settingName)) {
        throw new Error(`Duplicate setting name '${settingName}'`);
      }
      settingNameSet.add(settingName);
    }
  }
}

export function resetSettings(): void {
  registeredSettings = [];
  settingNameSet.clear();
}

export function maybeRemoveSettingExtension(settingName: string): boolean {
  const settingIndex = registeredSettings.findIndex(setting => setting.settingName === settingName);
  if (settingIndex < 0 || !settingNameSet.delete(settingName)) {
    return false;
  }
  registeredSettings.splice(settingIndex, 1);
  return true;
}

export const enum SettingCategory {
  NONE = '',  // `NONE` must be a falsy value. Legacy code uses if-checks for the category.
  ELEMENTS = 'ELEMENTS',
  AI = 'AI',
  APPEARANCE = 'APPEARANCE',
  SOURCES = 'SOURCES',
  NETWORK = 'NETWORK',
  PERFORMANCE = 'PERFORMANCE',
  CONSOLE = 'CONSOLE',
  PERSISTENCE = 'PERSISTENCE',
  DEBUGGER = 'DEBUGGER',
  GLOBAL = 'GLOBAL',
  RENDERING = 'RENDERING',
  GRID = 'GRID',
  MOBILE = 'MOBILE',
  EMULATION = 'EMULATION',
  MEMORY = 'MEMORY',
  EXTENSIONS = 'EXTENSIONS',
  ADORNER = 'ADORNER',
  ACCOUNT = 'ACCOUNT',
  PRIVACY = 'PRIVACY',
}

export function getLocalizedSettingsCategory(category: SettingCategory): Platform.UIString.LocalizedString {
  switch (category) {
    case SettingCategory.ELEMENTS:
      return i18nString(UIStrings.elements);
    case SettingCategory.AI:
      return i18nString(UIStrings.ai);
    case SettingCategory.APPEARANCE:
      return i18nString(UIStrings.appearance);
    case SettingCategory.SOURCES:
      return i18nString(UIStrings.sources);
    case SettingCategory.NETWORK:
      return i18nString(UIStrings.network);
    case SettingCategory.PERFORMANCE:
      return i18nString(UIStrings.performance);
    case SettingCategory.CONSOLE:
      return i18nString(UIStrings.console);
    case SettingCategory.PERSISTENCE:
      return i18nString(UIStrings.persistence);
    case SettingCategory.DEBUGGER:
      return i18nString(UIStrings.debugger);
    case SettingCategory.GLOBAL:
      return i18nString(UIStrings.global);
    case SettingCategory.RENDERING:
      return i18nString(UIStrings.rendering);
    case SettingCategory.GRID:
      return i18nString(UIStrings.grid);
    case SettingCategory.MOBILE:
      return i18nString(UIStrings.mobile);
    case SettingCategory.EMULATION:
      return i18nString(UIStrings.console);
    case SettingCategory.MEMORY:
      return i18nString(UIStrings.memory);
    case SettingCategory.EXTENSIONS:
      return i18nString(UIStrings.extension);
    case SettingCategory.ADORNER:
      return i18nString(UIStrings.adorner);
    case SettingCategory.NONE:
      return i18n.i18n.lockedString('');
    case SettingCategory.ACCOUNT:
      return i18nString(UIStrings.account);
    case SettingCategory.PRIVACY:
      return i18nString(UIStrings.privacy);
  }
}

export const enum SettingType {
  ARRAY = 'array',
  REGEX = 'regex',
  ENUM = 'enum',
  BOOLEAN = 'boolean',
}

export interface RegExpSettingItem {
  /**
   * A regular expression matched against URLs for ignore listing.
   */
  pattern: string;
  /**
   * If true, ignore this rule.
   */
  disabled?: boolean;
  /**
   * When a rule is disabled due to requesting through a script's context menu
   * that it no longer be ignore listed, this field is set to the URL of that
   * script, so that if the user requests through the same context menu to
   * enable ignore listing, the rule can be reenabled.
   */
  disabledForUrl?: Platform.DevToolsPath.UrlString;
}

export interface SettingRegistration {
  /**
   * The category with which the setting is displayed in the UI.
   */
  category?: SettingCategory;
  /**
   * Used to sort on screen the settings that belong to the same category.
   */
  order?: number;
  /**
   * The title with which the setting is shown on screen.
   */
  title?: () => Platform.UIString.LocalizedString;
  /**
   * The identifier of the setting.
   */
  settingName: string;
  /**
   * Determines how the possible values of the setting are expressed.
   *
   * - If the setting can only be enabled and disabled use BOOLEAN
   * - If the setting has a list of possible values use ENUM
   * - If each setting value is a set of objects use ARRAY
   * - If the setting value is a regular expression use REGEX
   */
  settingType: SettingType;
  /**
   * The value set by default to the setting.
   */
  defaultValue: unknown;
  /**
   * Words used to find a setting in the Command Menu.
   */
  tags?: Array<() => Platform.UIString.LocalizedString>;
  /**
   * The possible values the setting can have, each with a description composed of a title and an optional text.
   */
  options?: SettingExtensionOption[];
  /**
   * Whether DevTools must be reloaded for a change in the setting to take effect.
   */
  reloadRequired?: boolean;
  /**
   * Determines if the setting value is stored in the global, local or session storage.
   */
  storageType?: SettingStorageType;
  /**
   * A condition that, when present in the queryParamsObject of Runtime, constraints the value
   * of the setting to be changed only if the user set it.
   */
  userActionCondition?: string;
  /**
   * The name of the experiment a setting is associated with. Enabling and disabling the declared
   * experiment will enable and disable the setting respectively.
   */
  experiment?: Root.ExperimentNames.ExperimentName;
  /**
   * A condition is a function that will make the setting available if it
   * returns true, and not available, otherwise. Make sure that objects you
   * access from inside the condition function are ready at the time when the
   * setting conditions are checked.
   */
  condition?: Root.Runtime.Condition;

  /**
   * A function that returns true if the setting should be disabled, along with
   * the reason why.
   */
  disabledCondition?: (config?: Root.Runtime.HostConfig) => DisabledConditionResult;

  /**
   * If a setting is deprecated, define this notice to show an appropriate warning according to the `warning` property.
   * If `disabled` is set, the setting will be disabled in the settings UI. In that case, `experiment` optionally can be
   * set to link to an experiment (by experiment name). The information icon in the settings UI can then be clicked to
   * jump to the experiment. If a setting is not disabled, the experiment entry will be ignored.
   */
  deprecationNotice?: {disabled: boolean, warning: () => Platform.UIString.LocalizedString, experiment?: string};
  /**
   * See {@link LearnMore} for more info.
   */
  learnMore?: LearnMore;
}

/**
 * Optional information to learn more about the setting.
 *
 * If tooltip is provided creates a (i) icon with rich tooltip with said tooltip
 *
 * If url is provided creates a (?) icon with a link to said url
 *
 * If both tooltip is provided creates a (i) icon with rich tooltip
 * and a link inside the rich tool tip with text `Learn more`
 */
export interface LearnMore {
  tooltip?: () => Platform.UIString.LocalizedString;
  url?: Platform.DevToolsPath.UrlString;
}

interface LocalizedSettingExtensionOption {
  value: boolean|string;
  title: () => Platform.UIString.LocalizedString;
  text?: () => Platform.UIString.LocalizedString;
  raw?: false;
}
interface RawSettingExtensionOption {
  value: boolean|string;
  title: () => Platform.UIString.LocalizedString;
  /**
   * Text used to describe the option. Must be localized if 'raw' is false.
   */
  text?: string;
  raw: true;
}
export type SettingExtensionOption = LocalizedSettingExtensionOption|RawSettingExtensionOption;
export type DisabledConditionResult = {
  disabled: true,
  reasons: Platform.UIString.LocalizedString[],
}|{disabled: false};
