// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

part of dart.ui;

class SemanticsAction {
  const SemanticsAction._(this.index, this.name);

  final int index;
  final String name;

  static const int _kTapIndex = 1 << 0;
  static const int _kLongPressIndex = 1 << 1;
  static const int _kScrollLeftIndex = 1 << 2;
  static const int _kScrollRightIndex = 1 << 3;
  static const int _kScrollUpIndex = 1 << 4;
  static const int _kScrollDownIndex = 1 << 5;
  static const int _kIncreaseIndex = 1 << 6;
  static const int _kDecreaseIndex = 1 << 7;
  static const int _kShowOnScreenIndex = 1 << 8;
  static const int _kMoveCursorForwardByCharacterIndex = 1 << 9;
  static const int _kMoveCursorBackwardByCharacterIndex = 1 << 10;
  static const int _kSetSelectionIndex = 1 << 11;
  static const int _kCopyIndex = 1 << 12;
  static const int _kCutIndex = 1 << 13;
  static const int _kPasteIndex = 1 << 14;
  static const int _kDidGainAccessibilityFocusIndex = 1 << 15;
  static const int _kDidLoseAccessibilityFocusIndex = 1 << 16;
  static const int _kCustomActionIndex = 1 << 17;
  static const int _kDismissIndex = 1 << 18;
  static const int _kMoveCursorForwardByWordIndex = 1 << 19;
  static const int _kMoveCursorBackwardByWordIndex = 1 << 20;
  static const int _kSetTextIndex = 1 << 21;
  static const int _kFocusIndex = 1 << 22;
  static const int _kScrollToOffsetIndex = 1 << 23;
  static const int _kExpandIndex = 1 << 24;
  static const int _kCollapseIndex = 1 << 25;

  static const SemanticsAction tap = SemanticsAction._(_kTapIndex, 'tap');
  static const SemanticsAction longPress = SemanticsAction._(_kLongPressIndex, 'longPress');
  static const SemanticsAction scrollLeft = SemanticsAction._(_kScrollLeftIndex, 'scrollLeft');
  static const SemanticsAction scrollRight = SemanticsAction._(_kScrollRightIndex, 'scrollRight');
  static const SemanticsAction scrollUp = SemanticsAction._(_kScrollUpIndex, 'scrollUp');
  static const SemanticsAction scrollDown = SemanticsAction._(_kScrollDownIndex, 'scrollDown');
  static const SemanticsAction scrollToOffset = SemanticsAction._(
    _kScrollToOffsetIndex,
    'scrollToOffset',
  );
  static const SemanticsAction increase = SemanticsAction._(_kIncreaseIndex, 'increase');
  static const SemanticsAction decrease = SemanticsAction._(_kDecreaseIndex, 'decrease');
  static const SemanticsAction showOnScreen = SemanticsAction._(
    _kShowOnScreenIndex,
    'showOnScreen',
  );
  static const SemanticsAction moveCursorForwardByCharacter = SemanticsAction._(
    _kMoveCursorForwardByCharacterIndex,
    'moveCursorForwardByCharacter',
  );
  static const SemanticsAction moveCursorBackwardByCharacter = SemanticsAction._(
    _kMoveCursorBackwardByCharacterIndex,
    'moveCursorBackwardByCharacter',
  );
  static const SemanticsAction setText = SemanticsAction._(_kSetTextIndex, 'setText');
  static const SemanticsAction setSelection = SemanticsAction._(
    _kSetSelectionIndex,
    'setSelection',
  );
  static const SemanticsAction copy = SemanticsAction._(_kCopyIndex, 'copy');
  static const SemanticsAction cut = SemanticsAction._(_kCutIndex, 'cut');
  static const SemanticsAction paste = SemanticsAction._(_kPasteIndex, 'paste');
  static const SemanticsAction didGainAccessibilityFocus = SemanticsAction._(
    _kDidGainAccessibilityFocusIndex,
    'didGainAccessibilityFocus',
  );
  static const SemanticsAction didLoseAccessibilityFocus = SemanticsAction._(
    _kDidLoseAccessibilityFocusIndex,
    'didLoseAccessibilityFocus',
  );
  static const SemanticsAction customAction = SemanticsAction._(
    _kCustomActionIndex,
    'customAction',
  );
  static const SemanticsAction dismiss = SemanticsAction._(_kDismissIndex, 'dismiss');
  static const SemanticsAction moveCursorForwardByWord = SemanticsAction._(
    _kMoveCursorForwardByWordIndex,
    'moveCursorForwardByWord',
  );
  static const SemanticsAction moveCursorBackwardByWord = SemanticsAction._(
    _kMoveCursorBackwardByWordIndex,
    'moveCursorBackwardByWord',
  );
  static const SemanticsAction focus = SemanticsAction._(_kFocusIndex, 'focus');
  static const SemanticsAction expand = SemanticsAction._(_kExpandIndex, 'expand');
  static const SemanticsAction collapse = SemanticsAction._(_kCollapseIndex, 'collapse');

  static const Map<int, SemanticsAction> _kActionById = <int, SemanticsAction>{
    _kTapIndex: tap,
    _kLongPressIndex: longPress,
    _kScrollLeftIndex: scrollLeft,
    _kScrollRightIndex: scrollRight,
    _kScrollUpIndex: scrollUp,
    _kScrollDownIndex: scrollDown,
    _kScrollToOffsetIndex: scrollToOffset,
    _kIncreaseIndex: increase,
    _kDecreaseIndex: decrease,
    _kShowOnScreenIndex: showOnScreen,
    _kMoveCursorForwardByCharacterIndex: moveCursorForwardByCharacter,
    _kMoveCursorBackwardByCharacterIndex: moveCursorBackwardByCharacter,
    _kSetSelectionIndex: setSelection,
    _kCopyIndex: copy,
    _kCutIndex: cut,
    _kPasteIndex: paste,
    _kDidGainAccessibilityFocusIndex: didGainAccessibilityFocus,
    _kDidLoseAccessibilityFocusIndex: didLoseAccessibilityFocus,
    _kCustomActionIndex: customAction,
    _kDismissIndex: dismiss,
    _kMoveCursorForwardByWordIndex: moveCursorForwardByWord,
    _kMoveCursorBackwardByWordIndex: moveCursorBackwardByWord,
    _kSetTextIndex: setText,
    _kFocusIndex: focus,
    _kExpandIndex: expand,
    _kCollapseIndex: collapse,
  };

  static List<SemanticsAction> get values => _kActionById.values.toList(growable: false);

  static SemanticsAction? fromIndex(int index) => _kActionById[index];

  @override
  String toString() => 'SemanticsAction.$name';
}

class SemanticsFlag {
  const SemanticsFlag._(this.index, this.name);

  final int index;
  final String name;

  static const int _kHasCheckedStateIndex = 1 << 0;
  static const int _kIsCheckedIndex = 1 << 1;
  static const int _kIsSelectedIndex = 1 << 2;
  static const int _kIsButtonIndex = 1 << 3;
  static const int _kIsTextFieldIndex = 1 << 4;
  static const int _kIsFocusedIndex = 1 << 5;
  static const int _kHasEnabledStateIndex = 1 << 6;
  static const int _kIsEnabledIndex = 1 << 7;
  static const int _kIsInMutuallyExclusiveGroupIndex = 1 << 8;
  static const int _kIsHeaderIndex = 1 << 9;
  static const int _kIsObscuredIndex = 1 << 10;
  static const int _kScopesRouteIndex = 1 << 11;
  static const int _kNamesRouteIndex = 1 << 12;
  static const int _kIsHiddenIndex = 1 << 13;
  static const int _kIsImageIndex = 1 << 14;
  static const int _kIsLiveRegionIndex = 1 << 15;
  static const int _kHasToggledStateIndex = 1 << 16;
  static const int _kIsToggledIndex = 1 << 17;
  static const int _kHasImplicitScrollingIndex = 1 << 18;
  static const int _kIsMultilineIndex = 1 << 19;
  static const int _kIsReadOnlyIndex = 1 << 20;
  static const int _kIsFocusableIndex = 1 << 21;
  static const int _kIsLinkIndex = 1 << 22;
  static const int _kIsSliderIndex = 1 << 23;
  static const int _kIsKeyboardKeyIndex = 1 << 24;
  static const int _kIsCheckStateMixedIndex = 1 << 25;
  static const int _kHasExpandedStateIndex = 1 << 26;
  static const int _kIsExpandedIndex = 1 << 27;
  static const int _kHasSelectedStateIndex = 1 << 28;
  static const int _kHasRequiredStateIndex = 1 << 29;
  static const int _kIsRequiredIndex = 1 << 30;
  // WARNING: JavaScript can only go up to 32 bits!

  static const SemanticsFlag hasCheckedState = SemanticsFlag._(
    _kHasCheckedStateIndex,
    'hasCheckedState',
  );
  static const SemanticsFlag isChecked = SemanticsFlag._(_kIsCheckedIndex, 'isChecked');
  static const SemanticsFlag hasSelectedState = SemanticsFlag._(
    _kHasSelectedStateIndex,
    'hasSelectedState',
  );
  static const SemanticsFlag isSelected = SemanticsFlag._(_kIsSelectedIndex, 'isSelected');
  static const SemanticsFlag isButton = SemanticsFlag._(_kIsButtonIndex, 'isButton');
  static const SemanticsFlag isTextField = SemanticsFlag._(_kIsTextFieldIndex, 'isTextField');
  static const SemanticsFlag isSlider = SemanticsFlag._(_kIsSliderIndex, 'isSlider');
  static const SemanticsFlag isKeyboardKey = SemanticsFlag._(_kIsKeyboardKeyIndex, 'isKeyboardKey');
  static const SemanticsFlag isReadOnly = SemanticsFlag._(_kIsReadOnlyIndex, 'isReadOnly');
  static const SemanticsFlag isLink = SemanticsFlag._(_kIsLinkIndex, 'isLink');
  static const SemanticsFlag isFocusable = SemanticsFlag._(_kIsFocusableIndex, 'isFocusable');
  static const SemanticsFlag isFocused = SemanticsFlag._(_kIsFocusedIndex, 'isFocused');
  static const SemanticsFlag hasEnabledState = SemanticsFlag._(
    _kHasEnabledStateIndex,
    'hasEnabledState',
  );
  static const SemanticsFlag isEnabled = SemanticsFlag._(_kIsEnabledIndex, 'isEnabled');
  static const SemanticsFlag isInMutuallyExclusiveGroup = SemanticsFlag._(
    _kIsInMutuallyExclusiveGroupIndex,
    'isInMutuallyExclusiveGroup',
  );
  static const SemanticsFlag isHeader = SemanticsFlag._(_kIsHeaderIndex, 'isHeader');
  static const SemanticsFlag isObscured = SemanticsFlag._(_kIsObscuredIndex, 'isObscured');
  static const SemanticsFlag isMultiline = SemanticsFlag._(_kIsMultilineIndex, 'isMultiline');
  static const SemanticsFlag scopesRoute = SemanticsFlag._(_kScopesRouteIndex, 'scopesRoute');
  static const SemanticsFlag namesRoute = SemanticsFlag._(_kNamesRouteIndex, 'namesRoute');
  static const SemanticsFlag isHidden = SemanticsFlag._(_kIsHiddenIndex, 'isHidden');
  static const SemanticsFlag isImage = SemanticsFlag._(_kIsImageIndex, 'isImage');
  static const SemanticsFlag isLiveRegion = SemanticsFlag._(_kIsLiveRegionIndex, 'isLiveRegion');
  static const SemanticsFlag hasToggledState = SemanticsFlag._(
    _kHasToggledStateIndex,
    'hasToggledState',
  );
  static const SemanticsFlag isToggled = SemanticsFlag._(_kIsToggledIndex, 'isToggled');
  static const SemanticsFlag hasImplicitScrolling = SemanticsFlag._(
    _kHasImplicitScrollingIndex,
    'hasImplicitScrolling',
  );
  static const SemanticsFlag isCheckStateMixed = SemanticsFlag._(
    _kIsCheckStateMixedIndex,
    'isCheckStateMixed',
  );
  static const SemanticsFlag hasExpandedState = SemanticsFlag._(
    _kHasExpandedStateIndex,
    'hasExpandedState',
  );
  static const SemanticsFlag isExpanded = SemanticsFlag._(_kIsExpandedIndex, 'isExpanded');
  static const SemanticsFlag hasRequiredState = SemanticsFlag._(
    _kHasRequiredStateIndex,
    'hasRequiredState',
  );
  static const SemanticsFlag isRequired = SemanticsFlag._(_kIsRequiredIndex, 'isRequired');

  static const Map<int, SemanticsFlag> _kFlagById = <int, SemanticsFlag>{
    _kHasCheckedStateIndex: hasCheckedState,
    _kIsCheckedIndex: isChecked,
    _kHasSelectedStateIndex: hasSelectedState,
    _kIsSelectedIndex: isSelected,
    _kIsButtonIndex: isButton,
    _kIsTextFieldIndex: isTextField,
    _kIsFocusedIndex: isFocused,
    _kHasEnabledStateIndex: hasEnabledState,
    _kIsEnabledIndex: isEnabled,
    _kIsInMutuallyExclusiveGroupIndex: isInMutuallyExclusiveGroup,
    _kIsHeaderIndex: isHeader,
    _kIsObscuredIndex: isObscured,
    _kScopesRouteIndex: scopesRoute,
    _kNamesRouteIndex: namesRoute,
    _kIsHiddenIndex: isHidden,
    _kIsImageIndex: isImage,
    _kIsLiveRegionIndex: isLiveRegion,
    _kHasToggledStateIndex: hasToggledState,
    _kIsToggledIndex: isToggled,
    _kHasImplicitScrollingIndex: hasImplicitScrolling,
    _kIsMultilineIndex: isMultiline,
    _kIsReadOnlyIndex: isReadOnly,
    _kIsFocusableIndex: isFocusable,
    _kIsLinkIndex: isLink,
    _kIsSliderIndex: isSlider,
    _kIsKeyboardKeyIndex: isKeyboardKey,
    _kIsCheckStateMixedIndex: isCheckStateMixed,
    _kHasExpandedStateIndex: hasExpandedState,
    _kIsExpandedIndex: isExpanded,
    _kHasRequiredStateIndex: hasRequiredState,
    _kIsRequiredIndex: isRequired,
  };

  static List<SemanticsFlag> get values => _kFlagById.values.toList(growable: false);

  static SemanticsFlag? fromIndex(int index) => _kFlagById[index];

  @override
  String toString() => 'SemanticsFlag.$name';
}

// Mirrors engine/src/flutter/lib/ui/semantics.dart
enum CheckedState {
  none(0),
  isTrue(1),
  isFalse(2),
  mixed(3);

  const CheckedState(this.value);
  final int value;

  bool hasConflict(CheckedState other) => this != CheckedState.none && other != CheckedState.none;

  CheckedState merge(CheckedState other) {
    if (this == CheckedState.mixed || other == CheckedState.mixed) {
      return CheckedState.mixed;
    }
    if (this == CheckedState.isTrue || other == CheckedState.isTrue) {
      return CheckedState.isTrue;
    }
    if (this == CheckedState.isFalse || other == CheckedState.isFalse) {
      return CheckedState.isFalse;
    }
    return CheckedState.none;
  }
}

enum Tristate {
  none(0),
  isTrue(1),
  isFalse(2);

  const Tristate(this.value);
  final int value;

  bool hasConflict(Tristate other) => this != Tristate.none && other != Tristate.none;

  Tristate merge(Tristate other) {
    if (this == Tristate.isTrue || other == Tristate.isTrue) {
      return Tristate.isTrue;
    }
    if (this == Tristate.isFalse || other == Tristate.isFalse) {
      return Tristate.isFalse;
    }
    return Tristate.none;
  }

  bool? toBoolOrNull() {
    switch (this) {
      case Tristate.none:
        return null;
      case Tristate.isTrue:
        return true;
      case Tristate.isFalse:
        return false;
    }
  }
}

// Mirrors engine/src/flutter/lib/ui/semantics.dart
class SemanticsFlags {
  const SemanticsFlags({
    this.isChecked = CheckedState.none,
    this.isSelected = Tristate.none,
    this.isEnabled = Tristate.none,
    this.isToggled = Tristate.none,
    this.isExpanded = Tristate.none,
    this.isRequired = Tristate.none,
    this.isFocused = Tristate.none,
    this.isButton = false,
    this.isTextField = false,
    this.isInMutuallyExclusiveGroup = false,
    this.isHeader = false,
    this.isObscured = false,
    this.scopesRoute = false,
    this.namesRoute = false,
    this.isHidden = false,
    this.isImage = false,
    this.isLiveRegion = false,
    this.hasImplicitScrolling = false,
    this.isMultiline = false,
    this.isReadOnly = false,
    this.isLink = false,
    this.isSlider = false,
    this.isKeyboardKey = false,
    this.isAccessibilityFocusBlocked = false,
  });
  static const SemanticsFlags none = SemanticsFlags();
  final CheckedState isChecked;
  final Tristate isSelected;
  final Tristate isEnabled;
  final Tristate isToggled;
  final Tristate isExpanded;
  final Tristate isRequired;
  final Tristate isFocused;
  final bool isButton;
  final bool isTextField;
  final bool isInMutuallyExclusiveGroup;
  final bool isHeader;
  final bool isObscured;
  final bool scopesRoute;
  final bool namesRoute;
  final bool isHidden;
  final bool isImage;
  final bool isLiveRegion;
  final bool hasImplicitScrolling;
  final bool isMultiline;
  final bool isReadOnly;
  final bool isLink;
  final bool isSlider;
  final bool isKeyboardKey;
  final bool isAccessibilityFocusBlocked;

  SemanticsFlags merge(SemanticsFlags other) {
    return SemanticsFlags(
      isChecked: isChecked.merge(other.isChecked),
      isSelected: isSelected.merge(other.isSelected),
      isEnabled: isEnabled.merge(other.isEnabled),
      isToggled: isToggled.merge(other.isToggled),
      isExpanded: isExpanded.merge(other.isExpanded),
      isRequired: isRequired.merge(other.isRequired),
      isFocused: isFocused.merge(other.isFocused),
      isButton: isButton || other.isButton,
      isTextField: isTextField || other.isTextField,
      isInMutuallyExclusiveGroup: isInMutuallyExclusiveGroup || other.isInMutuallyExclusiveGroup,
      isHeader: isHeader || other.isHeader,
      isObscured: isObscured || other.isObscured,
      scopesRoute: scopesRoute || other.scopesRoute,
      namesRoute: namesRoute || other.namesRoute,
      isHidden: isHidden || other.isHidden,
      isImage: isImage || other.isImage,
      isLiveRegion: isLiveRegion || other.isLiveRegion,
      hasImplicitScrolling: hasImplicitScrolling || other.hasImplicitScrolling,
      isMultiline: isMultiline || other.isMultiline,
      isReadOnly: isReadOnly || other.isReadOnly,
      isLink: isLink || other.isLink,
      isSlider: isSlider || other.isSlider,
      isKeyboardKey: isKeyboardKey || other.isKeyboardKey,
      isAccessibilityFocusBlocked: isAccessibilityFocusBlocked || other.isAccessibilityFocusBlocked,
    );
  }

  SemanticsFlags copyWith({
    CheckedState? isChecked,
    Tristate? isSelected,
    Tristate? isEnabled,
    Tristate? isToggled,
    Tristate? isExpanded,
    Tristate? isRequired,
    Tristate? isFocused,
    bool? isButton,
    bool? isTextField,
    bool? isInMutuallyExclusiveGroup,
    bool? isHeader,
    bool? isObscured,
    bool? scopesRoute,
    bool? namesRoute,
    bool? isHidden,
    bool? isImage,
    bool? isLiveRegion,
    bool? hasImplicitScrolling,
    bool? isMultiline,
    bool? isReadOnly,
    bool? isLink,
    bool? isSlider,
    bool? isKeyboardKey,
    bool? isAccessibilityFocusBlocked,
  }) {
    return SemanticsFlags(
      isChecked: isChecked ?? this.isChecked,
      isSelected: isSelected ?? this.isSelected,
      isButton: isButton ?? this.isButton,
      isTextField: isTextField ?? this.isTextField,
      isFocused: isFocused ?? this.isFocused,
      isEnabled: isEnabled ?? this.isEnabled,
      isInMutuallyExclusiveGroup: isInMutuallyExclusiveGroup ?? this.isInMutuallyExclusiveGroup,
      isHeader: isHeader ?? this.isHeader,
      isObscured: isObscured ?? this.isObscured,
      scopesRoute: scopesRoute ?? this.scopesRoute,
      namesRoute: namesRoute ?? this.namesRoute,
      isHidden: isHidden ?? this.isHidden,
      isImage: isImage ?? this.isImage,
      isLiveRegion: isLiveRegion ?? this.isLiveRegion,
      isToggled: isToggled ?? this.isToggled,
      hasImplicitScrolling: hasImplicitScrolling ?? this.hasImplicitScrolling,
      isMultiline: isMultiline ?? this.isMultiline,
      isReadOnly: isReadOnly ?? this.isReadOnly,
      isLink: isLink ?? this.isLink,
      isSlider: isSlider ?? this.isSlider,
      isKeyboardKey: isKeyboardKey ?? this.isKeyboardKey,
      isExpanded: isExpanded ?? this.isExpanded,
      isRequired: isRequired ?? this.isRequired,
      isAccessibilityFocusBlocked: isAccessibilityFocusBlocked ?? this.isAccessibilityFocusBlocked,
    );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is SemanticsFlags &&
          runtimeType == other.runtimeType &&
          isChecked == other.isChecked &&
          isSelected == other.isSelected &&
          isEnabled == other.isEnabled &&
          isToggled == other.isToggled &&
          isExpanded == other.isExpanded &&
          isRequired == other.isRequired &&
          isFocused == other.isFocused &&
          isButton == other.isButton &&
          isTextField == other.isTextField &&
          isInMutuallyExclusiveGroup == other.isInMutuallyExclusiveGroup &&
          isHeader == other.isHeader &&
          isObscured == other.isObscured &&
          scopesRoute == other.scopesRoute &&
          namesRoute == other.namesRoute &&
          isHidden == other.isHidden &&
          isImage == other.isImage &&
          isLiveRegion == other.isLiveRegion &&
          hasImplicitScrolling == other.hasImplicitScrolling &&
          isMultiline == other.isMultiline &&
          isReadOnly == other.isReadOnly &&
          isLink == other.isLink &&
          isSlider == other.isSlider &&
          isKeyboardKey == other.isKeyboardKey &&
          isAccessibilityFocusBlocked == other.isAccessibilityFocusBlocked;

  @override
  int get hashCode => Object.hashAll([
    isChecked,
    isSelected,
    isEnabled,
    isToggled,
    isExpanded,
    isRequired,
    isFocused,
    isButton,
    isTextField,
    isInMutuallyExclusiveGroup,
    isHeader,
    isObscured,
    scopesRoute,
    namesRoute,
    isHidden,
    isImage,
    isLiveRegion,
    hasImplicitScrolling,
    isMultiline,
    isReadOnly,
    isLink,
    isSlider,
    isKeyboardKey,
    isAccessibilityFocusBlocked,
  ]);

  List<String> toStrings() {
    return <String>[
      if (isChecked != CheckedState.none) 'hasCheckedState',
      if (isChecked == CheckedState.isTrue) 'isChecked',
      if (isSelected == Tristate.isTrue) 'isSelected',
      if (isButton) 'isButton',
      if (isTextField) 'isTextField',
      if (isFocused == Tristate.isTrue) 'isFocused',
      if (isEnabled != Tristate.none) 'hasEnabledState',
      if (isEnabled == Tristate.isTrue) 'isEnabled',
      if (isInMutuallyExclusiveGroup) 'isInMutuallyExclusiveGroup',
      if (isHeader) 'isHeader',
      if (isObscured) 'isObscured',
      if (scopesRoute) 'scopesRoute',
      if (namesRoute) 'namesRoute',
      if (isHidden) 'isHidden',
      if (isImage) 'isImage',
      if (isLiveRegion) 'isLiveRegion',
      if (isToggled != Tristate.none) 'hasToggledState',
      if (isToggled == Tristate.isTrue) 'isToggled',
      if (hasImplicitScrolling) 'hasImplicitScrolling',
      if (isMultiline) 'isMultiline',
      if (isReadOnly) 'isReadOnly',
      if (isFocused != Tristate.none) 'isFocusable',
      if (isAccessibilityFocusBlocked) 'isAccessibilityFocusBlocked',
      if (isLink) 'isLink',
      if (isSlider) 'isSlider',
      if (isKeyboardKey) 'isKeyboardKey',
      if (isChecked == CheckedState.mixed) 'isCheckStateMixed',
      if (isExpanded != Tristate.none) 'hasExpandedState',
      if (isExpanded == Tristate.isTrue) 'isExpanded',
      if (isSelected != Tristate.none) 'hasSelectedState',
      if (isRequired != Tristate.none) 'hasRequiredState',
      if (isRequired == Tristate.isTrue) 'isRequired',
    ];
  }

  bool hasRepeatedFlags(SemanticsFlags other) {
    return isChecked.hasConflict(other.isChecked) ||
        isSelected.hasConflict(other.isSelected) ||
        isEnabled.hasConflict(other.isEnabled) ||
        isToggled.hasConflict(other.isToggled) ||
        isEnabled.hasConflict(other.isEnabled) ||
        isExpanded.hasConflict(other.isExpanded) ||
        isRequired.hasConflict(other.isRequired) ||
        isFocused.hasConflict(other.isFocused) ||
        (isButton && other.isButton) ||
        (isTextField && other.isTextField) ||
        (isInMutuallyExclusiveGroup && other.isInMutuallyExclusiveGroup) ||
        (isHeader && other.isHeader) ||
        (isObscured && other.isObscured) ||
        (scopesRoute && other.scopesRoute) ||
        (namesRoute && other.namesRoute) ||
        (isHidden && other.isHidden) ||
        (isImage && other.isImage) ||
        (isLiveRegion && other.isLiveRegion) ||
        (hasImplicitScrolling && other.hasImplicitScrolling) ||
        (isMultiline && other.isMultiline) ||
        (isReadOnly && other.isReadOnly) ||
        (isLink && other.isLink) ||
        (isSlider && other.isSlider) ||
        (isKeyboardKey && other.isKeyboardKey);
  }

  bool hasConflictingFlags(SemanticsFlags other) {
    return isChecked.hasConflict(other.isChecked) ||
        isSelected.hasConflict(other.isSelected) ||
        isEnabled.hasConflict(other.isEnabled) ||
        isToggled.hasConflict(other.isToggled) ||
        isEnabled.hasConflict(other.isEnabled) ||
        isExpanded.hasConflict(other.isExpanded) ||
        isRequired.hasConflict(other.isRequired) ||
        isFocused.hasConflict(other.isFocused) ||
        (isButton && other.isButton) ||
        (isTextField && other.isTextField) ||
        (isInMutuallyExclusiveGroup && other.isInMutuallyExclusiveGroup) ||
        (isHeader && other.isHeader) ||
        (isObscured && other.isObscured) ||
        (scopesRoute && other.scopesRoute) ||
        (namesRoute && other.namesRoute) ||
        (isHidden && other.isHidden) ||
        (isImage && other.isImage) ||
        (isLiveRegion && other.isLiveRegion) ||
        (hasImplicitScrolling && other.hasImplicitScrolling) ||
        (isMultiline && other.isMultiline) ||
        (isReadOnly && other.isReadOnly) ||
        (isLink && other.isLink) ||
        (isSlider && other.isSlider) ||
        (isKeyboardKey && other.isKeyboardKey) ||
        (isAccessibilityFocusBlocked != other.isAccessibilityFocusBlocked);
  }
}

// Mirrors engine/src/flutter/lib/ui/semantics.dart
enum SemanticsRole {
  none,
  tab,
  tabBar,
  tabPanel,
  dialog,
  alertDialog,
  table,
  cell,
  row,
  columnHeader,
  dragHandle,
  spinButton,
  comboBox,
  menuBar,
  menu,
  menuItem,
  menuItemCheckbox,
  menuItemRadio,
  list,
  listItem,
  form,
  tooltip,
  loadingSpinner,
  progressBar,
  hotKey,
  radioGroup,
  status,
  alert,
  complementary,
  contentInfo,
  main,
  navigation,
  region,
}

// Mirrors engine/src/flutter/lib/ui/semantics.dart
enum SemanticsInputType { none, text, url, phone, search, email }

// When adding a new StringAttributeType, the classes in these file must be
// updated as well.
//  * engine/src/flutter/lib/ui/semantics.dart
//  * engine/src/flutter/lib/ui/semantics/string_attribute.h
//  * engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java
//  * engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_api_test.dart
//  * engine/src/flutter/testing/dart/semantics_test.dart

abstract class StringAttribute {
  StringAttribute._({required this.range});

  final TextRange range;

  StringAttribute copy({required TextRange range});
}

class SpellOutStringAttribute extends StringAttribute {
  SpellOutStringAttribute({required super.range}) : super._();

  @override
  StringAttribute copy({required TextRange range}) {
    return SpellOutStringAttribute(range: range);
  }

  @override
  String toString() {
    return 'SpellOutStringAttribute($range)';
  }
}

class LocaleStringAttribute extends StringAttribute {
  LocaleStringAttribute({required super.range, required this.locale}) : super._();

  final Locale locale;

  @override
  StringAttribute copy({required TextRange range}) {
    return LocaleStringAttribute(range: range, locale: locale);
  }

  @override
  String toString() {
    return 'LocaleStringAttribute($range, ${locale.toLanguageTag()})';
  }
}

enum SemanticsValidationResult { none, valid, invalid }

enum SemanticsHitTestBehavior { defer, opaque, transparent }

class SemanticsUpdateBuilder {
  SemanticsUpdateBuilder();

  final List<engine.SemanticsNodeUpdate> _nodeUpdates = <engine.SemanticsNodeUpdate>[];
  void updateNode({
    required int id,
    required SemanticsFlags flags,
    required int actions,
    required int maxValueLength,
    required int currentValueLength,
    required int textSelectionBase,
    required int textSelectionExtent,
    required int platformViewId,
    required int scrollChildren,
    required int scrollIndex,
    required int? traversalParent,
    required double scrollPosition,
    required double scrollExtentMax,
    required double scrollExtentMin,
    required Rect rect,
    required String identifier,
    required String label,
    required List<StringAttribute> labelAttributes,
    required String value,
    required List<StringAttribute> valueAttributes,
    required String increasedValue,
    required List<StringAttribute> increasedValueAttributes,
    required String decreasedValue,
    required List<StringAttribute> decreasedValueAttributes,
    required String hint,
    required List<StringAttribute> hintAttributes,
    String? tooltip,
    TextDirection? textDirection,
    required Float64List transform,
    required Float64List hitTestTransform,
    required Int32List childrenInTraversalOrder,
    required Int32List childrenInHitTestOrder,
    required Int32List additionalActions,
    int headingLevel = 0,
    String? linkUrl,
    SemanticsRole role = SemanticsRole.none,
    required List<String>? controlsNodes,
    SemanticsValidationResult validationResult = SemanticsValidationResult.none,
    SemanticsHitTestBehavior hitTestBehavior = SemanticsHitTestBehavior.defer,
    required SemanticsInputType inputType,
    required Locale? locale,
    required String minValue,
    required String maxValue,
  }) {
    if (transform.length != 16) {
      throw ArgumentError('transform argument must have 16 entries.');
    }
    _nodeUpdates.add(
      engine.SemanticsNodeUpdate(
        id: id,
        flags: flags,
        actions: actions,
        maxValueLength: maxValueLength,
        currentValueLength: currentValueLength,
        textSelectionBase: textSelectionBase,
        textSelectionExtent: textSelectionExtent,
        scrollChildren: scrollChildren,
        scrollIndex: scrollIndex,
        traversalParent: traversalParent,
        scrollPosition: scrollPosition,
        scrollExtentMax: scrollExtentMax,
        scrollExtentMin: scrollExtentMin,
        rect: rect,
        identifier: identifier,
        label: label,
        labelAttributes: labelAttributes,
        value: value,
        valueAttributes: valueAttributes,
        increasedValue: increasedValue,
        increasedValueAttributes: increasedValueAttributes,
        decreasedValue: decreasedValue,
        decreasedValueAttributes: decreasedValueAttributes,
        hint: hint,
        hintAttributes: hintAttributes,
        tooltip: tooltip,
        textDirection: textDirection,
        transform: engine.toMatrix32(transform),
        hitTestTransform: engine.toMatrix32(hitTestTransform),
        childrenInTraversalOrder: childrenInTraversalOrder,
        childrenInHitTestOrder: childrenInHitTestOrder,
        additionalActions: additionalActions,
        platformViewId: platformViewId,
        headingLevel: headingLevel,
        linkUrl: linkUrl,
        role: role,
        controlsNodes: controlsNodes,
        validationResult: validationResult,
        hitTestBehavior: hitTestBehavior,
        inputType: inputType,
        locale: locale,
        minValue: minValue,
        maxValue: maxValue,
      ),
    );
  }

  void updateCustomAction({required int id, String? label, String? hint, int overrideId = -1}) {
    // TODO(yjbanov): implement.
  }
  SemanticsUpdate build() {
    return SemanticsUpdate._(nodeUpdates: _nodeUpdates);
  }
}

abstract class SemanticsUpdate {
  factory SemanticsUpdate._({List<engine.SemanticsNodeUpdate>? nodeUpdates}) =
      engine.SemanticsUpdate;
  void dispose();
}
