chromium/ios/chrome/browser/ui/tab_switcher/tab_strip/ui/tab_strip_item_identifier.swift

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

// The tab strip has two types of items: tabs and groups.
// This should only be used in Objective-C, as Swift code can use enum
// `TabStripItemIdentifier.Item` instead.
@objc enum TabStripItemType: Int {
  case tab
  case group
}

// Represents tab strip items in a diffable data source. TabStripItemIdentifier
// equality is based on the type and the potential item's properties. This means
// that two different TabStripItemIdentifier-s can be equal (via -isEqual:) and
// share the same -hash. Different items though won't be equal and will likely
// have different hashes (the hashing for tabs is based on NSNumber's hashing,
// which prevents consecutive identifiers to have consecutive hash values, while
// the hashing for groups is based on NSValue's hashing of the TabGroup
// pointer).
@objc class TabStripItemIdentifier: NSObject, NSCopying {

  // Item this identifier is referring to.
  // Since a tab strip item can either be a tab (represented by `TabSwitcherItem`)
  // or a group (represented by `TabGroupItem`), a tab strip item is represented
  // by the sum of these two types i.e. Item = TabSwitcherItem + TabGroupItem.
  // In Swift, sum types are enumerations with associated values, which are not
  // visible from Objective-C code, hence the need for `TabStripItemType`.
  enum Item: Hashable {
    case tab(TabSwitcherItem)
    case group(TabGroupItem)
  }

  // Item this identifier is referring to, either a `.tab(_)` or a `.group(_)`.
  public let item: Item
  // Hash of the item.
  private let itemHash: Int

  // MARK: - Initialization

  public init(_ tabSwitcherItem: TabSwitcherItem) {
    self.item = .tab(tabSwitcherItem)
    self.itemHash = Int(GetHashForTabSwitcherItem(tabSwitcherItem))
  }

  public init(_ tabGroupItem: TabGroupItem) {
    self.item = .group(tabGroupItem)
    self.itemHash = Int(GetHashForTabGroupItem(tabGroupItem))
  }

  public convenience init?(_ tabSwitcherItem: TabSwitcherItem?) {
    guard let tabSwitcherItem = tabSwitcherItem else { return nil }
    self.init(tabSwitcherItem)
  }

  public convenience init?(_ tabGroupItem: TabGroupItem?) {
    guard let tabGroupItem = tabGroupItem else { return nil }
    self.init(tabGroupItem)
  }

  // MARK: - NSObject

  // TODO(crbug.com/329073651): Refactor -hash and -isEqual.
  public override func isEqual(_ object: Any?) -> Bool {
    guard let object = object as? TabStripItemIdentifier else {
      return false
    }
    switch (item, object.item) {
    case (.tab(let lhs), .tab(let rhs)):
      return CompareTabSwitcherItems(lhs, rhs)
    case (.group(let lhs), .group(let rhs)):
      return CompareTabGroupItems(lhs, rhs)
    default:
      return false
    }
  }

  // TODO(crbug.com/329073651): Refactor -hash and -isEqual.
  public override var hash: Int {
    return itemHash
  }

  public override var description: String {
    switch item {
    case .tab(let tabSwitcherItem):
      return tabSwitcherItem.description
    case .group(let tabGroupItem):
      return tabGroupItem.description
    }
  }

  // MARK: - NSCopying

  public func copy(with zone: NSZone? = nil) -> Any {
    switch item {
    case .tab(let tabSwitcherItem):
      return TabStripItemIdentifier(tabSwitcherItem)
    case .group(let tabGroupItem):
      return TabStripItemIdentifier(tabGroupItem)
    }
  }

  // MARK: - Objective-C interface

  // Factory method to create an identifier for a `TabSwitcherItem`.
  @objc public static func tabIdentifier(_ tabSwitcherItem: TabSwitcherItem?)
    -> TabStripItemIdentifier?
  {
    return TabStripItemIdentifier(tabSwitcherItem)
  }

  // Factory method to create an identifier for a `TabGroupItem`.
  @objc public static func groupIdentifier(_ tabGroupItem: TabGroupItem?) -> TabStripItemIdentifier?
  {
    return TabStripItemIdentifier(tabGroupItem)
  }

  // Only valid when `itemType`` is `TabStripItemTypeTab`.
  @objc public var tabSwitcherItem: TabSwitcherItem? {
    switch item {
    case .tab(let tabSwitcherItem):
      return tabSwitcherItem
    case .group(_):
      return nil
    }
  }

  // Only valid when `itemType`` is `TabStripItemTypeGroup`.
  @objc public var tabGroupItem: TabGroupItem? {
    switch item {
    case .tab(_):
      return nil
    case .group(let tabGroupItem):
      return tabGroupItem
    }
  }

  // The type of tab strip item this identifier is referring to.
  @objc public var itemType: TabStripItemType {
    switch item {
    case .tab(_):
      return .tab
    case .group(_):
      return .group
    }
  }
}