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

import '../../ui/components/lists/lists.js';
import '../../ui/components/tooltips/tooltips.js';
import '../../ui/legacy/legacy.js';

import type * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Logs from '../../models/logs/logs.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import * as uiI18n from '../../ui/i18n/i18n.js';
import * as UI from '../../ui/legacy/legacy.js';
import {Directives, html, type LitTemplate, nothing, render} from '../../ui/lit/lit.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
import * as MobileThrottling from '../mobile_throttling/mobile_throttling.js';
import * as PanelUtils from '../utils/utils.js';

import requestConditionsDrawerStyles from './requestConditionsDrawer.css.js';

const {ref, live, ifDefined} = Directives;
const {widget} = UI.Widget;

const UIStrings = {
  /**
   * @description Text to enable blocking of network requests
   */
  enableBlockingAndThrottling: 'Enable blocking and throttling',
  /**
   * @description Tooltip text that appears when hovering over the plus button in the Blocked URLs Pane of the Network panel
   */
  addRule: 'Add rule',
  /**
   * @description Accessible label for the button to add request blocking patterns in the network request blocking tool
   */
  addPatternLabel: 'Add network request throttling or blocking pattern',
  /**
   * @description Text that shows in the network request blocking panel if no pattern has yet been added.
   */
  noPattern: 'Nothing throttled or blocked',
  /**
   * @description Text that shows in the network request blocking panel if no pattern has yet been added.
   * @example {Learn more} PH1
   */
  noThrottlingOrBlockingPattern:
      `To throttle or block a network request, add a rule here manually or via the network panel's context menu. {PH1}`,
  /**
   * @description Text in Blocked URLs Pane of the Network panel
   * @example {4} PH1
   */
  dAffected: '{PH1} affected',
  /**
   * @description Text in Blocked URLs Pane of the Network panel
   */
  textEditPattern: 'Text pattern to block or throttle matching requests; use URL Pattern syntax.',
  /**
   * @description Error text for empty list widget input in Request Blocking tool
   */
  patternInputCannotBeEmpty: 'Pattern input cannot be empty.',
  /**
   * @description Error text for duplicate list widget input in Request Blocking tool
   */
  patternAlreadyExists: 'Pattern already exists.',
  /**
   * @description Tooltip message when a pattern failed to parse as a URLPattern
   */
  patternFailedToParse: 'This pattern failed to parse as a URLPattern',
  /**
   * @description Tooltip message when a pattern failed to parse as a URLPattern because it contains RegExp groups
   */
  patternFailedWithRegExpGroups: 'RegExp groups are not allowed',
  /**
   * @description Tooltip message when a pattern was converted to a URLPattern
   * @example {example.com} PH1
   */
  patternWasUpgraded: 'This pattern was upgraded from "{PH1}"',
  /**
   * @description Message to be announced for a when list item is removed from list widget
   */
  itemDeleted: 'Item successfully deleted',
  /**
   * @description Message to be announced for a when list item is removed from list widget
   */
  learnMore: 'Learn more',
  /**
   * @description Tooltip on a button moving an entry up
   * @example {*://example.com} PH1
   */
  increasePriority: 'Move up {PH1}',
  /**
   * @description Tooltip on a button moving an entry down
   * @example {*://example.com} PH1
   */
  decreasePriority: 'Move down {PH1}',
  /**
   * @description Tooltip on a checkbox togging the effects for a pattern
   * @example {*://example.com} PH1
   */
  enableThrottlingToggleLabel: 'Throttle or block {PH1}',
  /**
   * @description Tooltip on a combobox selecting the request conditions
   */
  requestConditionsLabel: 'Request conditions',
  /**
   * @description Aria announcement when a pattern was moved up
   */
  patternMovedUp: 'URL pattern was moved up',
  /**
   * @description Aria announcemenet when a pattern was moved down
   */
  patternMovedDown: 'URL pattern was moved down',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/network/RequestConditionsDrawer.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

const NETWORK_REQUEST_BLOCKING_EXPLANATION_URL =
    'https://developer.chrome.com/docs/devtools/network-request-blocking' as Platform.DevToolsPath.UrlString;

const {bindToAction} = UI.UIUtils;

interface ViewInput {
  conditions: SDK.NetworkManager.RequestCondition[];
  editingCondition?: SDK.NetworkManager.RequestCondition;
  enabled: boolean;
  toggleEnabled: () => void;
  addPattern: () => void;
  onToggle: (condition: SDK.NetworkManager.RequestCondition) => void;
  onConditionsChanged: (condition: SDK.NetworkManager.RequestCondition,
                        conditions: SDK.NetworkManager.ThrottlingConditions) => void;
  onIncreasePriority: (condition: SDK.NetworkManager.RequestCondition) => void;
  onDecreasePriority: (condition: SDK.NetworkManager.RequestCondition) => void;
  onCommit: (condition: SDK.NetworkManager.RequestCondition, value: string) => void;
  onCancel: (condition: SDK.NetworkManager.RequestCondition) => void;
  onBeginEdit: (condition: SDK.NetworkManager.RequestCondition) => void;
  onRemove: (condition: SDK.NetworkManager.RequestCondition) => void;
  validator: (condition: SDK.NetworkManager.RequestCondition, value: string) => Common.UIString.LocalizedString | null;
  lookUpRequestCount: (condition: SDK.NetworkManager.RequestCondition) => number;
}
interface ViewOutput {
  itemRefs: Map<SDK.NetworkManager.RequestCondition, HTMLElement|undefined>;
}
type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
export const DEFAULT_VIEW: View = (input, output, target) => {
  render(
      // clang-format off
    html`
    <style>${requestConditionsDrawerStyles}</style>
    <devtools-toolbar jslog=${VisualLogging.toolbar()}>
      <devtools-checkbox
        ?checked=${input.enabled}
        @click=${input.toggleEnabled}
        .jslogContext=${'network.enable-request-blocking'}>
        ${i18nString(UIStrings.enableBlockingAndThrottling)}
      </devtools-checkbox>
      <div class="toolbar-divider"></div>
      <devtools-button ${bindToAction('network.add-network-request-blocking-pattern')}></devtools-button>
      <devtools-button ${bindToAction('network.remove-all-network-request-blocking-patterns')}></devtools-button>
    </devtools-toolbar>
    ${input.conditions.length === 0 ? html`
    <div class="list">
      <div class=empty-state>
      <span class=empty-state-header>${i18nString(UIStrings.noPattern)}</span>
      <div class=empty-state-description>
        ${uiI18n.getFormatLocalizedStringTemplate(str_, UIStrings.noThrottlingOrBlockingPattern, {PH1: learnMore()})}
      </div>
      <devtools-button
        @click=${input.addPattern}
        class=add-button
        .jslogContext=${'network.add-network-request-blocking-pattern'}
        title=${i18nString(UIStrings.addPatternLabel)}
        .variant=${Buttons.Button.Variant.TONAL}>
          ${i18nString(UIStrings.addRule)}
      </devtools-button>
    </div>
    </div>
    ` : html`
    <devtools-list
        class="blocked-urls list square-corners"
        ?deletable=${input.enabled}
        @edit=${(e: CustomEvent<{index: number}>) => input.onBeginEdit(input.conditions[e.detail.index])}
        @delete=${(e: CustomEvent<{index: number}>) => input.onRemove(input.conditions[e.detail.index])}>
      ${input.conditions.map((condition, index) => html`
        <div class="blocked-url" ${ref(e => {
            output.itemRefs.set(condition, e as HTMLElement | undefined);
        })}>
          ${renderItem({
             condition,
             editing: input.editingCondition === condition,
             editable: input.enabled,
             index,
             onToggle: input.onToggle,
             onConditionsChanged: input.onConditionsChanged,
             onIncreasePriority: input.onIncreasePriority,
             onDecreasePriority: input.onDecreasePriority,
             onCommit: input.onCommit,
             onCancel: input.onCancel,
             onBeginEdit: input.onBeginEdit,
             validator: val => input.validator(condition, val),
             lookUpRequestCount: input.lookUpRequestCount
          })}
        </div>
      `)}
    </devtools-list>
    `}
    `,
      // clang-format on
      target, {container: {classes: (!input.enabled && input.conditions.length > 0) ? ['blocking-disabled'] : []}});
};

function renderItem({
  condition,
  editing,
  editable,
  index,
  onToggle,
  onConditionsChanged,
  onIncreasePriority,
  onDecreasePriority,
  onCommit,
  onCancel,
  onBeginEdit,
  validator,
  lookUpRequestCount
}: {
  condition: SDK.NetworkManager.RequestCondition,
  editing: boolean,
  editable: boolean,
  index: number,
  onToggle: (condition: SDK.NetworkManager.RequestCondition) => void,
  onConditionsChanged:
      (condition: SDK.NetworkManager.RequestCondition, conditions: SDK.NetworkManager.ThrottlingConditions) => void,
  onIncreasePriority: (condition: SDK.NetworkManager.RequestCondition) => void,
  onDecreasePriority: (condition: SDK.NetworkManager.RequestCondition) => void,
  onCommit: (condition: SDK.NetworkManager.RequestCondition, value: string) => void,
  onCancel: (condition: SDK.NetworkManager.RequestCondition) => void,
  onBeginEdit: (condition: SDK.NetworkManager.RequestCondition) => void,
  validator: (value: string) => Common.UIString.LocalizedString | null,
  lookUpRequestCount: (condition: SDK.NetworkManager.RequestCondition) => number,
}): LitTemplate {
  const {enabled, originalOrUpgradedURLPattern, constructorStringOrWildcardURL, wildcardURL} = condition;
  const toggle = (e: Event): void => {
    e.consume(true);
    onToggle(condition);
  };

  const moveUp = (e: Event): void => {
    e.consume(true);
    onIncreasePriority(condition);
  };

  const moveDown = (e: Event): void => {
    e.consume(true);
    onDecreasePriority(condition);
  };

  const onPromptActivate = (e: Event): void => {
    if (!editable || editing) {
      return;
    }
    onBeginEdit(condition);
    e.consume(true);
  };

  const promptKeyDown = (e: Event): void => {
    if (!editable || editing) {
      return;
    }
    const keyboardEvent = e as KeyboardEvent;
    if (keyboardEvent.key === 'Enter') {
      onBeginEdit(condition);
      e.consume(true);
    }
  };

  // clang-format off
  return html`
    <input class=blocked-url-checkbox
      @change=${toggle}
      type=checkbox
      title=${i18nString(UIStrings.enableThrottlingToggleLabel, {PH1: constructorStringOrWildcardURL})}
      .checked=${live(enabled)}
      .disabled=${!editable || !originalOrUpgradedURLPattern}
      jslog=${VisualLogging.toggle().track({ change: true })}>
    <devtools-button
      .iconName=${'arrow-up'}
      .variant=${Buttons.Button.Variant.ICON}
      .title=${i18nString(UIStrings.increasePriority, {PH1: constructorStringOrWildcardURL})}
      .jslogContext=${'decrease-priority'}
      ?disabled=${!editable || !originalOrUpgradedURLPattern}
      @click=${moveUp}>
    </devtools-button>
    <devtools-button
      .iconName=${'arrow-down'}
      .variant=${Buttons.Button.Variant.ICON}
      .title=${i18nString(UIStrings.decreasePriority, {PH1: constructorStringOrWildcardURL})}
      .jslogContext=${'increase-priority'}
      ?disabled=${!editable || !originalOrUpgradedURLPattern}
      @click=${moveDown}></devtools-button>
    ${!editing && originalOrUpgradedURLPattern ? html`
      <devtools-tooltip variant=rich jslogcontext=url-pattern id=url-pattern-${index}>
        <div>hash: ${originalOrUpgradedURLPattern.hash}</div>
        <div>hostname: ${originalOrUpgradedURLPattern.hostname}</div>
        <div>password: ${originalOrUpgradedURLPattern.password}</div>
        <div>pathname: ${originalOrUpgradedURLPattern.pathname}</div>
        <div>port: ${originalOrUpgradedURLPattern.port}</div>
        <div>protocol: ${originalOrUpgradedURLPattern.protocol}</div>
        <div>search: ${originalOrUpgradedURLPattern.search}</div>
        <div>username: ${originalOrUpgradedURLPattern.username}</div>
        <hr />
        ${learnMore()}
      </devtools-tooltip>` : nothing}
    ${!editing && wildcardURL ? html`
      <devtools-icon name=warning-filled class="small warning" aria-details=url-pattern-warning-${index}>
      </devtools-icon>
      <devtools-tooltip variant=rich jslogcontext=url-pattern-warning id=url-pattern-warning-${index}>
        ${i18nString(UIStrings.patternWasUpgraded, {PH1: wildcardURL})}
      </devtools-tooltip>
      `: nothing}
    ${!editing && !originalOrUpgradedURLPattern ? html`
      <devtools-icon name=cross-circle-filled class=small aria-details=url-pattern-error-${index}>
      </devtools-icon>
      <devtools-tooltip variant=rich jslogcontext=url-pattern-warning id=url-pattern-error-${index}>
        ${SDK.NetworkManager.RequestURLPattern.isValidPattern(constructorStringOrWildcardURL) ===
            SDK.NetworkManager.RequestURLPatternValidity.HAS_REGEXP_GROUPS
            ? i18nString(UIStrings.patternFailedWithRegExpGroups)
            : i18nString(UIStrings.patternFailedToParse)}
        ${learnMore()}
      </devtools-tooltip>`: nothing}
    <devtools-prompt
      @click=${onPromptActivate}
      @keydown=${promptKeyDown}
      @focus=${onPromptActivate}
      tabindex=${ifDefined(editing ? undefined : 0)}
      @commit=${(e: UI.TextPrompt.TextPromptElement.CommitEvent) => onCommit(condition, e.detail)}
      @cancel=${() => onCancel(condition)}
      ?disabled=${!editable}
      placeholder=${i18nString(UIStrings.textEditPattern)}
      value=${constructorStringOrWildcardURL}
      ?editing=${editable && editing}
      .validator=${validator}
      class=blocked-url-label
      aria-details=url-pattern-${index}>
        ${constructorStringOrWildcardURL}
    </devtools-prompt>
    <select
       class=conditions-selector
       title=${i18nString(UIStrings.requestConditionsLabel)}
       @ConditionsChanged=${(e: CustomEvent<SDK.NetworkManager.ThrottlingConditions>) => {
         onConditionsChanged(condition, e.detail);
       }}
       ${widget(
         MobileThrottling.NetworkThrottlingSelector.NetworkThrottlingSelect, {
           variant:
             MobileThrottling.NetworkThrottlingSelector.NetworkThrottlingSelect.Variant.INDIVIDUAL_REQUEST_CONDITIONS,
           jslogContext: 'request-conditions',
           disabled: !editable,
           currentConditions: condition.conditions,
         })}></select>
    <devtools-widget
      ?disabled=${!editable || !originalOrUpgradedURLPattern}
      ${widget(AffectedCountWidget, {condition, lookUpRequestCount})}></devtools-widget>`;
  // clang-format on
}

interface AffectedCountViewInput {
  count: number;
}
type AffectedCountView = (input: AffectedCountViewInput, output: object, target: HTMLElement) => void;
export const AFFECTED_COUNT_DEFAULT_VIEW: AffectedCountView = (input, output, target) => {
  render(html`${i18nString(UIStrings.dAffected, {PH1: input.count})}`, target,
         {container: {classes: ['blocked-url-count']}});
};

function matchesUrl(conditions: SDK.NetworkManager.RequestCondition, url: string): boolean {
  return Boolean(conditions.originalOrUpgradedURLPattern?.test(url));
}

export class AffectedCountWidget extends UI.Widget.Widget {
  readonly #view: AffectedCountView;
  #condition?: SDK.NetworkManager.RequestCondition;
  #lookUpRequestCount?: (condition: SDK.NetworkManager.RequestCondition) => number;

  constructor(target?: HTMLElement, view = AFFECTED_COUNT_DEFAULT_VIEW) {
    super(target);
    this.#view = view;
  }

  get lookUpRequestCount(): ((condition: SDK.NetworkManager.RequestCondition) => number)|undefined {
    return this.#lookUpRequestCount;
  }

  set lookUpRequestCount(val: (condition: SDK.NetworkManager.RequestCondition) => number) {
    this.#lookUpRequestCount = val;
  }

  get condition(): SDK.NetworkManager.RequestCondition|undefined {
    return this.#condition;
  }

  set condition(conditions: SDK.NetworkManager.RequestCondition) {
    this.#condition = conditions;
    this.requestUpdate();
  }

  override performUpdate(): void {
    if (!this.#condition || !this.#lookUpRequestCount) {
      return;
    }

    this.#view({count: this.#lookUpRequestCount(this.#condition)}, {}, this.element);
  }

  override wasShown(): void {
    SDK.TargetManager.TargetManager.instance().addModelListener(SDK.NetworkManager.NetworkManager,
                                                                SDK.NetworkManager.Events.RequestFinished,
                                                                this.#onRequestFinished, this, {scoped: true});
    Logs.NetworkLog.NetworkLog.instance().addEventListener(Logs.NetworkLog.Events.Reset, this.requestUpdate, this);
    super.wasShown();
  }

  override willHide(): void {
    super.willHide();
    SDK.TargetManager.TargetManager.instance().removeModelListener(
        SDK.NetworkManager.NetworkManager, SDK.NetworkManager.Events.RequestFinished, this.#onRequestFinished, this);
    Logs.NetworkLog.NetworkLog.instance().removeEventListener(Logs.NetworkLog.Events.Reset, this.requestUpdate, this);
  }

  #onRequestFinished(event: Common.EventTarget.EventTargetEvent<SDK.NetworkRequest.NetworkRequest>): void {
    if (!this.#condition) {
      return;
    }

    const request = event.data;
    if ((request.appliedNetworkConditionsId && this.#condition.ruleIds.has(request.appliedNetworkConditionsId)) ||
        (request.wasBlocked() && matchesUrl(this.#condition, request.url()))) {
      this.requestUpdate();
    }
  }
}

function learnMore(): LitTemplate {
  return html`<devtools-link
        href=${NETWORK_REQUEST_BLOCKING_EXPLANATION_URL}
        tabindex=0
        class=devtools-link
        .jslogContext=${'learn-more'}>
          ${i18nString(UIStrings.learnMore)}
      </devtools-link>`;
}

export class RequestConditionsDrawer extends UI.Widget.VBox {
  private manager: SDK.NetworkManager.MultitargetNetworkManager;
  private blockedCountForUrl: Map<Platform.DevToolsPath.UrlString, number>;
  #throttledCount = new Map<string, number>();
  #view: View;
  #viewOutput: ViewOutput = {itemRefs: new Map()};
  #editingCondition?: SDK.NetworkManager.RequestCondition;

  constructor(target?: HTMLElement, view = DEFAULT_VIEW) {
    super(target, {
      jslog: `${VisualLogging.panel('network.blocked-urls').track({resize: true})}`,
      useShadowDom: true,
    });
    this.#view = view;

    this.manager = SDK.NetworkManager.MultitargetNetworkManager.instance();
    this.manager.addEventListener(SDK.NetworkManager.MultitargetNetworkManager.Events.BLOCKED_PATTERNS_CHANGED,
                                  this.requestUpdate, this);

    this.blockedCountForUrl = new Map();
    SDK.TargetManager.TargetManager.instance().addModelListener(SDK.NetworkManager.NetworkManager,
                                                                SDK.NetworkManager.Events.RequestFinished,
                                                                this.onRequestFinished, this, {scoped: true});

    this.requestUpdate();
    Logs.NetworkLog.NetworkLog.instance().addEventListener(Logs.NetworkLog.Events.Reset, this.onNetworkLogReset, this);
  }

  override performUpdate(): void {
    this.#viewOutput.itemRefs.clear();
    const enabled = this.manager.requestConditions.conditionsEnabled;
    const conditions = Array.from(this.manager.requestConditions.conditions);
    if (this.#editingCondition && !conditions.includes(this.#editingCondition)) {
      conditions.unshift(this.#editingCondition);
    }

    const input: ViewInput = {
      addPattern: this.addPattern.bind(this),
      toggleEnabled: this.toggleEnabled.bind(this),
      enabled,
      conditions,
      editingCondition: this.#editingCondition,
      onToggle: (condition: SDK.NetworkManager.RequestCondition) => {
        if (enabled) {
          condition.enabled = !condition.enabled;
        }
      },
      onConditionsChanged:
          (condition: SDK.NetworkManager.RequestCondition, conditions: SDK.NetworkManager.ThrottlingConditions) => {
            if (enabled) {
              condition.conditions = conditions;
            }
          },
      onIncreasePriority: (condition: SDK.NetworkManager.RequestCondition) => {
        if (enabled) {
          UI.ARIAUtils.LiveAnnouncer.status(i18nString(UIStrings.patternMovedUp));
          this.manager.requestConditions.increasePriority(condition);
        }
      },
      onDecreasePriority: (condition: SDK.NetworkManager.RequestCondition) => {
        if (enabled) {
          UI.ARIAUtils.LiveAnnouncer.status(i18nString(UIStrings.patternMovedDown));
          this.manager.requestConditions.decreasePriority(condition);
        }
      },
      onBeginEdit: (condition: SDK.NetworkManager.RequestCondition) => {
        if (this.#editingCondition) {
          this.#cancelEdit(this.#editingCondition);
        }
        this.#editingCondition = condition;
        this.requestUpdate();
      },
      onRemove: (condition: SDK.NetworkManager.RequestCondition) => {
        this.manager.requestConditions.delete(condition);
        UI.ARIAUtils.LiveAnnouncer.alert(UIStrings.itemDeleted);
      },
      onCommit: this.#commitEdit.bind(this),
      onCancel: this.#cancelEdit.bind(this),
      validator: this.#validator.bind(this),
      lookUpRequestCount: this.#getRequestCount.bind(this),
    };
    this.#view(input, this.#viewOutput, this.contentElement);
  }

  addPattern(): void {
    this.manager.requestConditions.conditionsEnabled = true;
    if (this.#editingCondition) {
      this.#cancelEdit(this.#editingCondition);
    }
    const condition = SDK.NetworkManager.RequestCondition.createFromSetting(
        {url: Platform.DevToolsPath.EmptyUrlString, enabled: true});
    this.#editingCondition = condition;
    this.requestUpdate();
  }

  removeAllPatterns(): void {
    this.manager.requestConditions.clear();
  }

  #validator(condition: SDK.NetworkManager.RequestCondition, value: string): Common.UIString.LocalizedString|null {
    if (!value) {
      return i18nString(UIStrings.patternInputCannotBeEmpty);
    }
    const parsedPattern =
        SDK.NetworkManager.RequestURLPattern.create(value as SDK.NetworkManager.URLPatternConstructorString);
    const stringToCheck = parsedPattern ? parsedPattern.constructorString : value;
    const existingCondition = this.manager.requestConditions.findCondition(stringToCheck);
    if (existingCondition && existingCondition !== condition) {
      return i18nString(UIStrings.patternAlreadyExists);
    }
    const isValid = SDK.NetworkManager.RequestURLPattern.isValidPattern(value);
    switch (isValid) {
      case SDK.NetworkManager.RequestURLPatternValidity.FAILED_TO_PARSE:
        return i18nString(UIStrings.patternFailedToParse);
      case SDK.NetworkManager.RequestURLPatternValidity.HAS_REGEXP_GROUPS:
        return i18nString(UIStrings.patternFailedWithRegExpGroups);
    }
    return null;
  }

  private toggleEnabled(): void {
    this.manager.requestConditions.conditionsEnabled = !this.manager.requestConditions.conditionsEnabled;
    this.requestUpdate();
  }

  #commitEdit(condition: SDK.NetworkManager.RequestCondition, value: string): void {
    if (this.#editingCondition !== condition) {
      return;
    }

    if (condition.constructorStringOrWildcardURL === value &&
        Array.from(this.manager.requestConditions.conditions).includes(condition)) {
      this.#editingCondition = undefined;
      this.requestUpdate();
      return;
    }
    const constructorString = value as SDK.NetworkManager.URLPatternConstructorString;
    const pattern = SDK.NetworkManager.RequestURLPattern.create(constructorString);
    if (!pattern) {
      return;
    }
    condition.pattern = pattern;

    // If it's a new item, it isn't in the manager yet. Add it.
    if (!Array.from(this.manager.requestConditions.conditions).includes(condition)) {
      this.manager.requestConditions.add(condition);
    }

    this.#editingCondition = undefined;
    this.requestUpdate();
  }

  #cancelEdit(condition: SDK.NetworkManager.RequestCondition): void {
    if (this.#editingCondition === condition) {
      this.#editingCondition = undefined;
      this.requestUpdate();
    }
  }

  #getRequestCount(condition: SDK.NetworkManager.RequestCondition): number {
    if (condition.isBlocking) {
      let result = 0;
      for (const blockedUrl of this.blockedCountForUrl.keys()) {
        if (matchesUrl(condition, blockedUrl)) {
          result += (this.blockedCountForUrl.get(blockedUrl) as number);
        }
      }
      return result;
    }

    let result = 0;
    for (const ruleId of condition.ruleIds) {
      result += this.#throttledCount.get(ruleId) ?? 0;
    }
    return result;
  }

  private onNetworkLogReset(_event: Common.EventTarget.EventTargetEvent<Logs.NetworkLog.ResetEvent>): void {
    this.blockedCountForUrl.clear();
    this.#throttledCount.clear();
  }

  private onRequestFinished(event: Common.EventTarget.EventTargetEvent<SDK.NetworkRequest.NetworkRequest>): void {
    const request = event.data;
    if (request.appliedNetworkConditionsId) {
      const count = this.#throttledCount.get(request.appliedNetworkConditionsId) ?? 0;
      this.#throttledCount.set(request.appliedNetworkConditionsId, count + 1);
    }
    if (request.wasBlocked()) {
      const count = this.blockedCountForUrl.get(request.url()) || 0;
      this.blockedCountForUrl.set(request.url(), count + 1);
    }
  }

  override wasShown(): void {
    UI.Context.Context.instance().setFlavor(RequestConditionsDrawer, this);
    super.wasShown();
  }

  override willHide(): void {
    super.willHide();
    UI.Context.Context.instance().setFlavor(RequestConditionsDrawer, null);
  }

  static async reveal(appliedConditions: SDK.NetworkManager.AppliedNetworkConditions): Promise<void> {
    await UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
    const drawer = UI.Context.Context.instance().flavor(RequestConditionsDrawer);
    if (!drawer) {
      console.assert(!!drawer, 'Drawer not initialized');
      return;
    }
    const condition = drawer.manager.requestConditions.conditions.find(
        condition => condition.ruleIds.has(appliedConditions.appliedNetworkConditionsId) &&
            condition.constructorString && condition.constructorString === appliedConditions.urlPattern);
    if (condition) {
      const element = drawer.#viewOutput.itemRefs.get(condition);
      if (element) {
        PanelUtils.PanelUtils.highlightElement(element);
      }
    }
  }
}

export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
  handleAction(context: UI.Context.Context, actionId: string): boolean {
    const drawer = context.flavor(RequestConditionsDrawer);
    if (drawer === null) {
      return false;
    }
    switch (actionId) {
      case 'network.add-network-request-blocking-pattern': {
        drawer.addPattern();
        return true;
      }

      case 'network.remove-all-network-request-blocking-patterns': {
        drawer.removeAllPatterns();
        return true;
      }
    }
    return false;
  }
}

export class AppliedConditionsRevealer implements
    Common.Revealer.Revealer<SDK.NetworkManager.AppliedNetworkConditions> {
  async reveal(request: SDK.NetworkManager.AppliedNetworkConditions): Promise<void> {
    if (request.urlPattern) {
      await RequestConditionsDrawer.reveal(request);
    }
  }
}
