// Copyright 2016 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 */
/* eslint-disable @devtools/no-lit-render-outside-of-view */

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 Protocol from '../../generated/protocol.js';
import * as uiI18n from '../../ui/i18n/i18n.js';
import * as UI from '../../ui/legacy/legacy.js';
import {render} from '../../ui/lit/lit.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
import * as PanelsCommon from '../common/common.js';

import accessibilityNodeStyles from './accessibilityNode.css.js';
import {AXAttributes, AXNativeSourceTypes, AXSourceTypes} from './AccessibilityStrings.js';
import {AccessibilitySubPane} from './AccessibilitySubPane.js';

const UIStrings = {
  /**
   * @description Text in Accessibility Node View of the Accessibility panel
   */
  computedProperties: 'Computed Properties',
  /**
   * @description Text in Accessibility Node View of the Accessibility panel
   */
  noAccessibilityNode: 'No accessibility node',
  /**
   * @description Text in Accessibility Node View of the Accessibility panel
   */
  accessibilityNodeNotExposed: 'Accessibility node not exposed',
  /**
   * @description Text in Accessibility Node View of the Accessibility panel
   */
  invalidSource: 'Invalid source.',
  /**
   * @description Text in Accessibility Node View of the Accessibility panel
   */
  notSpecified: 'Not specified',
  /**
   * @description Text in Accessibility Node View of the Accessibility panel
   */
  noNodeWithThisId: 'No node with this ID.',
  /**
   * @description Text which appears in the Accessibility Node View of the Accessibility panel when an element is covered by a modal/popup window
   */
  elementIsHiddenBy: 'Element is hidden by active modal dialog:\xA0',
  /**
   * @description Text which appears in the Accessibility Node View of the Accessibility panel when an element is hidden by another accessibility tree.
   */
  elementIsHiddenByChildTree: 'Element is hidden by child tree:\xA0',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  ancestorChildrenAreAll: 'Ancestor\'s children are all presentational:\xA0',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   * @example {aria-hidden} PH1
   */
  elementIsPlaceholder: 'Element is {PH1}.',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   * @example {aria-hidden} PH1
   * @example {true} PH2
   */
  placeholderIsPlaceholderOnAncestor: '{PH1} is {PH2} on ancestor:\xA0',
  /**
   * @description Text in Accessibility Node View of the Accessibility panel
   */
  elementHasEmptyAltText: 'Element has empty alt text.',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  noTextContent: 'No text content.',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  elementIsInert: 'Element is `inert`.',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  elementIsInAnInertSubTree: 'Element is in an `inert` subtree from\xA0',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  elementsInheritsPresentational: 'Element inherits presentational role from\xA0',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  partOfLabelElement: 'Part of label element:\xA0',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  labelFor: 'Label for\xA0',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  elementIsNotRendered: 'Element is not rendered.',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  elementIsNotVisible: 'Element is not visible.',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel. Indicates the
   *ARIA role for this element, which will always have the format 'role=', but with different roles
   *(which are not translated). https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles
   * @example {role=link} PH1
   */
  elementHasPlaceholder: 'Element has {PH1}.',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility panel
   */
  elementIsPresentational: 'Element is presentational.',
  /**
   * @description Reason element in Accessibility Node View of the Accessibility pane. Here
   * 'interesting' is from the perspective of the accessibility engine in Chrome. A non-interesting
   * element doesn't have any special accessibility considerations
   */
  elementNotInteresting: 'Element not interesting for accessibility.',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/accessibility/AccessibilityNodeView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class AXNodeSubPane extends AccessibilitySubPane {
  override axNode: SDK.AccessibilityModel.AccessibilityNode|null;
  private readonly noNodeInfo: UI.Widget.Widget;
  private readonly ignoredInfo: UI.Widget.Widget;
  private readonly treeOutline: UI.TreeOutline.TreeOutline;
  private readonly ignoredReasonsTree: UI.TreeOutline.TreeOutline;
  constructor() {
    super({
      title: i18nString(UIStrings.computedProperties),
      viewId: 'computed-properties',
      jslog: `${VisualLogging.section('computed-properties')}`,
    });
    this.registerRequiredCSS(accessibilityNodeStyles);

    this.axNode = null;

    this.contentElement.classList.add('ax-subpane');

    this.noNodeInfo = this.createInfo(i18nString(UIStrings.noAccessibilityNode));
    this.ignoredInfo = this.createInfo(i18nString(UIStrings.accessibilityNodeNotExposed), 'ax-ignored-info', 'hidden');

    this.treeOutline = this.createTreeOutline();
    this.ignoredReasonsTree = this.createTreeOutline();

    this.element.classList.add('accessibility-computed');

    this.treeOutline.setFocusable(true);
  }

  override setAXNode(axNode: SDK.AccessibilityModel.AccessibilityNode|null): void {
    if (this.axNode === axNode) {
      return;
    }
    this.axNode = axNode;

    const treeOutline = this.treeOutline;
    treeOutline.removeChildren();
    const ignoredReasons = this.ignoredReasonsTree;
    ignoredReasons.removeChildren();

    if (!axNode) {
      treeOutline.element.classList.add('hidden');
      this.ignoredInfo.element.classList.add('hidden');
      ignoredReasons.element.classList.add('hidden');

      this.noNodeInfo.element.classList.remove('hidden');
      this.element.classList.add('ax-ignored-node-pane');

      return;
    }

    if (axNode.ignored()) {
      this.noNodeInfo.element.classList.add('hidden');
      treeOutline.element.classList.add('hidden');
      this.element.classList.add('ax-ignored-node-pane');

      this.ignoredInfo.element.classList.remove('hidden');
      ignoredReasons.element.classList.remove('hidden');
      function addIgnoredReason(property: Protocol.Accessibility.AXProperty): void {
        ignoredReasons.appendChild(
            new AXNodeIgnoredReasonTreeElement(property, axNode as SDK.AccessibilityModel.AccessibilityNode));
      }
      const ignoredReasonsArray = axNode.ignoredReasons() as Protocol.Accessibility.AXProperty[];
      for (const reason of ignoredReasonsArray) {
        addIgnoredReason(reason);
      }
      if (!ignoredReasons.firstChild()) {
        ignoredReasons.element.classList.add('hidden');
      }
      return;
    }
    this.element.classList.remove('ax-ignored-node-pane');

    this.ignoredInfo.element.classList.add('hidden');
    ignoredReasons.element.classList.add('hidden');
    this.noNodeInfo.element.classList.add('hidden');

    treeOutline.element.classList.remove('hidden');

    function addProperty(property: SDK.AccessibilityModel.CoreOrProtocolAxProperty): void {
      treeOutline.appendChild(
          new AXNodePropertyTreePropertyElement(property, axNode as SDK.AccessibilityModel.AccessibilityNode));
    }

    for (const property of axNode.coreProperties()) {
      addProperty(property);
    }

    const role = axNode.role();
    if (role) {
      const roleProperty: SDK.AccessibilityModel.CoreOrProtocolAxProperty = {
        name: SDK.AccessibilityModel.CoreAxPropertyName.ROLE,
        value: role,
      };
      addProperty(roleProperty);
    }
    for (const property of axNode.properties() as Protocol.Accessibility.AXProperty[]) {
      addProperty(property);
    }

    const firstNode = treeOutline.firstChild();
    if (firstNode) {
      firstNode.select(/* omitFocus= */ true, /* selectedByUser= */ false);
    }
  }

  override setNode(node: SDK.DOMModel.DOMNode|null): void {
    super.setNode(node);
    this.axNode = null;
  }
}

export class AXNodePropertyTreeElement extends UI.TreeOutline.TreeElement {
  protected axNode: SDK.AccessibilityModel.AccessibilityNode;
  constructor(axNode: SDK.AccessibilityModel.AccessibilityNode) {
    // Pass an empty title, the title gets made later in onattach.
    super('');
    this.axNode = axNode;
  }

  static createSimpleValueElement(type: Protocol.Accessibility.AXValueType|null, value: string): Element {
    let valueElement;
    if (!type || type === Protocol.Accessibility.AXValueType.ValueUndefined ||
        type === Protocol.Accessibility.AXValueType.ComputedString) {
      valueElement = document.createElement('span');
    } else {
      valueElement = document.createElement('span');
      valueElement.classList.add('monospace');
    }
    let valueText;
    const isStringProperty = type && StringProperties.has(type);
    if (isStringProperty) {
      // Render \n as a nice unicode cr symbol.
      valueText = '"' + value.replace(/\n/g, '\u21B5') + '"';
    } else {
      valueText = String(value);
    }

    if (type && type in TypeStyles) {
      valueElement.classList.add(TypeStyles[type]);
    }

    valueElement.setTextContentTruncatedIfNeeded(valueText || '');

    UI.Tooltip.Tooltip.install(valueElement, String(value) || '');

    return valueElement;
  }

  static createExclamationMark(tooltip: string): Element {
    const exclamationElement = UI.UIUtils.createIconLabel({iconName: 'warning-filled', color: 'var(--icon-warning)'});
    UI.Tooltip.Tooltip.install(exclamationElement, tooltip);
    return exclamationElement;
  }

  appendNameElement(name: string): void {
    const nameElement = document.createElement('span');
    if (name in AXAttributes) {
      // @ts-expect-error TS can't cast name here but we checked it's valid.
      const attribute = AXAttributes[name];
      nameElement.textContent = attribute.name();
      UI.Tooltip.Tooltip.install(nameElement, attribute.description());
      nameElement.classList.add('ax-readable-name');
    } else {
      nameElement.textContent = name;
      nameElement.classList.add('ax-name');
      nameElement.classList.add('monospace');
    }
    this.listItemElement.appendChild(nameElement);
  }

  appendValueElement(value: Protocol.Accessibility.AXValue): void {
    if (value.type === Protocol.Accessibility.AXValueType.Idref ||
        value.type === Protocol.Accessibility.AXValueType.Node ||
        value.type === Protocol.Accessibility.AXValueType.IdrefList ||
        value.type === Protocol.Accessibility.AXValueType.NodeList) {
      this.appendRelatedNodeListValueElement(value);
      return;
    }
    if (value.sources) {
      const sources = value.sources;
      for (let i = 0; i < sources.length; i++) {
        const source = sources[i];
        const child = new AXValueSourceTreeElement(source, this.axNode);
        this.appendChild(child);
      }
      this.expand();
    }
    const element = AXNodePropertyTreeElement.createSimpleValueElement(value.type, String(value.value));
    this.listItemElement.appendChild(element);
  }

  appendRelatedNode(relatedNode: Protocol.Accessibility.AXRelatedNode, _index: number): void {
    const deferredNode =
        new SDK.DOMModel.DeferredDOMNode(this.axNode.accessibilityModel().target(), relatedNode.backendDOMNodeId);
    const nodeTreeElement = new AXRelatedNodeSourceTreeElement({deferredNode}, relatedNode);
    this.appendChild(nodeTreeElement);
  }

  appendRelatedNodeInline(relatedNode: Protocol.Accessibility.AXRelatedNode): void {
    const deferredNode =
        new SDK.DOMModel.DeferredDOMNode(this.axNode.accessibilityModel().target(), relatedNode.backendDOMNodeId);
    const linkedNode = new AXRelatedNodeElement({deferredNode});
    this.listItemElement.appendChild(linkedNode.render());
  }

  appendRelatedNodeListValueElement(value: Protocol.Accessibility.AXValue): void {
    if (value.relatedNodes?.length === 1 && !value.value) {
      this.appendRelatedNodeInline(value.relatedNodes[0]);
      return;
    }

    if (value.relatedNodes) {
      value.relatedNodes.forEach(this.appendRelatedNode, this);
    }
    if (value.relatedNodes && value.relatedNodes.length <= 3) {
      this.expand();
    } else {
      this.collapse();
    }
  }
}

export const TypeStyles: Record<string, string> = {
  attribute: 'ax-value-string',
  boolean: 'object-value-boolean',
  booleanOrUndefined: 'object-value-boolean',
  computedString: 'ax-readable-string',
  idref: 'ax-value-string',
  idrefList: 'ax-value-string',
  integer: 'object-value-number',
  internalRole: 'ax-internal-role',
  number: 'ax-value-number',
  role: 'ax-role',
  string: 'ax-value-string',
  tristate: 'object-value-boolean',
  valueUndefined: 'ax-value-undefined',
};

export const StringProperties = new Set<Protocol.Accessibility.AXValueType>([
  Protocol.Accessibility.AXValueType.String,
  Protocol.Accessibility.AXValueType.ComputedString,
  Protocol.Accessibility.AXValueType.IdrefList,
  Protocol.Accessibility.AXValueType.Idref,
]);

export class AXNodePropertyTreePropertyElement extends AXNodePropertyTreeElement {
  private readonly property: SDK.AccessibilityModel.CoreOrProtocolAxProperty;
  override toggleOnClick: boolean;
  constructor(
      property: SDK.AccessibilityModel.CoreOrProtocolAxProperty, axNode: SDK.AccessibilityModel.AccessibilityNode) {
    super(axNode);

    this.property = property;
    this.toggleOnClick = true;

    this.listItemElement.classList.add('property');
  }

  override onattach(): void {
    this.update();
  }

  private update(): void {
    this.listItemElement.removeChildren();

    this.appendNameElement(this.property.name);

    this.listItemElement.createChild('span', 'separator').textContent = ':\xA0';

    this.appendValueElement(this.property.value);
  }
}

export class AXValueSourceTreeElement extends AXNodePropertyTreeElement {
  private readonly source: Protocol.Accessibility.AXValueSource;
  constructor(source: Protocol.Accessibility.AXValueSource, axNode: SDK.AccessibilityModel.AccessibilityNode) {
    super(axNode);
    this.source = source;
  }

  override onattach(): void {
    this.update();
  }

  appendRelatedNodeWithIdref(relatedNode: Protocol.Accessibility.AXRelatedNode, idref: string): void {
    const deferredNode =
        new SDK.DOMModel.DeferredDOMNode(this.axNode.accessibilityModel().target(), relatedNode.backendDOMNodeId);
    const nodeTreeElement = new AXRelatedNodeSourceTreeElement({deferredNode, idref}, relatedNode);
    this.appendChild(nodeTreeElement);
  }

  appendIDRefValueElement(value: Protocol.Accessibility.AXValue): void {
    if (value.value === null) {
      return;
    }

    const relatedNodes = value.relatedNodes || [];

    // Content attribute is empty, but if the relationship was set via the IDL
    // then there may be related nodes.
    if (value.value === '') {
      for (const node of relatedNodes) {
        const idref = node.idref || '';
        this.appendRelatedNodeWithIdref(node, idref);
      }
      return;
    }

    const idrefs = value.value.trim().split(/\s+/);
    for (const idref of idrefs) {
      const matchingNode = relatedNodes.find(node => node.idref === idref);

      // If there is exactly one related node, it is rendered on the same line
      // of the label. If there are more, they are each rendered on their own
      // line below the label.
      // TODO(aboxhall): exclamation mark if not idreflist type
      if (matchingNode) {
        this.appendRelatedNodeWithIdref(matchingNode, idref);
      } else if (idrefs.length === 1) {
        this.listItemElement.appendChild(new AXRelatedNodeElement({idref}).render());
      } else {
        this.appendChild(new AXRelatedNodeSourceTreeElement({idref}));
      }
    }
  }

  override appendRelatedNodeListValueElement(value: Protocol.Accessibility.AXValue): void {
    const relatedNodes = value.relatedNodes;
    const numNodes = relatedNodes ? relatedNodes.length : 0;

    if (value.type === Protocol.Accessibility.AXValueType.IdrefList ||
        value.type === Protocol.Accessibility.AXValueType.Idref) {
      this.appendIDRefValueElement(value);
    } else {
      super.appendRelatedNodeListValueElement(value);
    }

    if (numNodes <= 3) {
      this.expand();
    } else {
      this.collapse();
    }
  }

  appendSourceNameElement(source: Protocol.Accessibility.AXValueSource): void {
    const nameElement = document.createElement('span');
    const type = source.type;
    switch (type) {
      case Protocol.Accessibility.AXValueSourceType.Attribute:
      case Protocol.Accessibility.AXValueSourceType.Placeholder:
      case Protocol.Accessibility.AXValueSourceType.RelatedElement:
        if (source.nativeSource) {
          const nativeSource = source.nativeSource;
          nameElement.textContent = AXNativeSourceTypes[nativeSource].name();
          UI.Tooltip.Tooltip.install(nameElement, AXNativeSourceTypes[nativeSource].description());
          nameElement.classList.add('ax-readable-name');
          break;
        }
        nameElement.textContent = source.attribute || null;
        nameElement.classList.add('ax-name');
        nameElement.classList.add('monospace');
        break;
      default:
        if (type in AXSourceTypes) {
          nameElement.textContent = AXSourceTypes[type].name();
          UI.Tooltip.Tooltip.install(nameElement, AXSourceTypes[type].description());
          nameElement.classList.add('ax-readable-name');
        } else {
          console.warn(type, 'not in AXSourceTypes');
          nameElement.textContent = type;
        }
    }
    this.listItemElement.appendChild(nameElement);
  }

  private update(): void {
    this.listItemElement.removeChildren();

    if (this.source.invalid) {
      const exclamationMark = AXNodePropertyTreeElement.createExclamationMark(i18nString(UIStrings.invalidSource));
      this.listItemElement.appendChild(exclamationMark);
      this.listItemElement.classList.add('ax-value-source-invalid');
    } else if (this.source.superseded) {
      this.listItemElement.classList.add('ax-value-source-unused');
    }

    this.appendSourceNameElement(this.source);

    this.listItemElement.createChild('span', 'separator').textContent = ':\xA0';

    if (this.source.attributeValue) {
      this.appendValueElement(this.source.attributeValue);
      UI.UIUtils.createTextChild(this.listItemElement, '\xA0');
    } else if (this.source.nativeSourceValue) {
      this.appendValueElement(this.source.nativeSourceValue);
      UI.UIUtils.createTextChild(this.listItemElement, '\xA0');
      if (this.source.value) {
        this.appendValueElement(this.source.value);
      }
    } else if (this.source.value) {
      this.appendValueElement(this.source.value);
    } else {
      const valueElement = AXNodePropertyTreeElement.createSimpleValueElement(
          Protocol.Accessibility.AXValueType.ValueUndefined, i18nString(UIStrings.notSpecified));
      this.listItemElement.appendChild(valueElement);
      this.listItemElement.classList.add('ax-value-source-unused');
    }

    if (this.source.value && this.source.superseded) {
      this.listItemElement.classList.add('ax-value-source-superseded');
    }
  }
}

export class AXRelatedNodeSourceTreeElement extends UI.TreeOutline.TreeElement {
  private value: Protocol.Accessibility.AXRelatedNode|undefined;
  private readonly axRelatedNodeElement: AXRelatedNodeElement;

  constructor(
      node: {
        deferredNode?: SDK.DOMModel.DeferredDOMNode,
        idref?: string,
      },
      value?: Protocol.Accessibility.AXRelatedNode) {
    super('');

    this.value = value;
    this.axRelatedNodeElement = new AXRelatedNodeElement(node);
    this.selectable = true;
  }

  override onattach(): void {
    this.listItemElement.appendChild(this.axRelatedNodeElement.render());
    if (!this.value) {
      return;
    }

    if (this.value.text) {
      this.listItemElement.appendChild(AXNodePropertyTreeElement.createSimpleValueElement(
          Protocol.Accessibility.AXValueType.ComputedString, this.value.text));
    }
  }

  override onenter(): boolean {
    this.axRelatedNodeElement.revealNode();
    return true;
  }
}

export class AXRelatedNodeElement {
  private readonly deferredNode: SDK.DOMModel.DeferredDOMNode|undefined;
  private readonly idref: string|undefined;
  constructor(node: {
    deferredNode?: SDK.DOMModel.DeferredDOMNode,
    idref?: string,
  }) {
    this.deferredNode = node.deferredNode;
    this.idref = node.idref;
  }

  render(): Element {
    const element = document.createElement('span');

    if (this.deferredNode) {
      const valueElement = document.createElement('span');
      element.appendChild(valueElement);
      void this.deferredNode.resolvePromise().then(node => {
        if (!node) {
          return;
        }
        render(
            PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {
              preventKeyboardFocus: true,
            }),
            valueElement);
      });
    } else if (this.idref) {
      element.classList.add('invalid');
      const valueElement = AXNodePropertyTreeElement.createExclamationMark(i18nString(UIStrings.noNodeWithThisId));
      UI.UIUtils.createTextChild(valueElement, this.idref);
      element.appendChild(valueElement);
    }

    return element;
  }

  /**
   * Attempts to cause the node referred to by the related node to be selected in the tree.
   */
  revealNode(): void {
    if (this.deferredNode) {
      void this.deferredNode.resolvePromise().then(node => Common.Revealer.reveal(node));
    }
  }
}

export class AXNodeIgnoredReasonTreeElement extends AXNodePropertyTreeElement {
  private property: Protocol.Accessibility.AXProperty;
  override toggleOnClick: boolean;
  private reasonElement?: Element|null;

  constructor(property: Protocol.Accessibility.AXProperty, axNode: SDK.AccessibilityModel.AccessibilityNode) {
    super(axNode);
    this.property = property;
    this.axNode = axNode;
    this.toggleOnClick = true;
    this.selectable = false;
  }

  static createReasonElement(reason: string|null, axNode: SDK.AccessibilityModel.AccessibilityNode|null): Element|null {
    let reasonElement: Element|null = null;
    switch (reason) {
      case 'activeModalDialog':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementIsHiddenBy, {});
        break;
      case 'hiddenByChildTree':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementIsHiddenByChildTree, {});
        break;
      case 'ancestorIsLeafNode':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.ancestorChildrenAreAll, {});
        break;
      case 'ariaHiddenElement': {
        const ariaHiddenSpan = document.createElement('span', {is: 'source-code'}).textContent = 'aria-hidden';
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementIsPlaceholder, {PH1: ariaHiddenSpan});
        break;
      }
      case 'ariaHiddenSubtree': {
        const ariaHiddenSpan = document.createElement('span', {is: 'source-code'}).textContent = 'aria-hidden';
        const trueSpan = document.createElement('span', {is: 'source-code'}).textContent = 'true';
        reasonElement = uiI18n.getFormatLocalizedString(
            str_, UIStrings.placeholderIsPlaceholderOnAncestor, {PH1: ariaHiddenSpan, PH2: trueSpan});
        break;
      }
      case 'emptyAlt':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementHasEmptyAltText, {});
        break;
      case 'emptyText':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.noTextContent, {});
        break;
      case 'inertElement':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementIsInert, {});
        break;
      case 'inertSubtree':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementIsInAnInertSubTree, {});
        break;
      case 'inheritsPresentation':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementsInheritsPresentational, {});
        break;
      case 'labelContainer':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.partOfLabelElement, {});
        break;
      case 'labelFor':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.labelFor, {});
        break;
      case 'notRendered':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementIsNotRendered, {});
        break;
      case 'notVisible':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementIsNotVisible, {});
        break;
      case 'presentationalRole': {
        const role = axNode?.role()?.value || '';
        const rolePresentationSpan = document.createElement('span', {is: 'source-code'}).textContent = 'role=' + role;
        reasonElement =
            uiI18n.getFormatLocalizedString(str_, UIStrings.elementHasPlaceholder, {PH1: rolePresentationSpan});
        break;
      }
      case 'probablyPresentational':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementIsPresentational, {});
        break;
      case 'uninteresting':
        reasonElement = uiI18n.getFormatLocalizedString(str_, UIStrings.elementNotInteresting, {});
        break;
    }
    if (reasonElement) {
      reasonElement.classList.add('ax-reason');
    }
    return reasonElement;
  }

  override onattach(): void {
    this.listItemElement.removeChildren();

    this.reasonElement = AXNodeIgnoredReasonTreeElement.createReasonElement(this.property.name, this.axNode);
    if (this.reasonElement) {
      this.listItemElement.appendChild(this.reasonElement);
    }

    const value = this.property.value;
    if (value.type === Protocol.Accessibility.AXValueType.Idref) {
      this.appendRelatedNodeListValueElement(value);
    }
  }
}
