//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

/// This protocol is only used for compile-time checks that
/// every buffer type implements all required operations.
internal protocol _DictionaryBuffer {
  associatedtype Key
  associatedtype Value
  associatedtype Index

  var startIndex: Index { get }
  var endIndex: Index { get }
  func index(after i: Index) -> Index
  func index(forKey key: Key) -> Index?
  var count: Int { get }

  func contains(_ key: Key) -> Bool
  func lookup(_ key: Key) -> Value?
  func lookup(_ index: Index) -> (key: Key, value: Value)
  func key(at index: Index) -> Key
  func value(at index: Index) -> Value
}

extension Dictionary {
  @usableFromInline
  @frozen
  @safe
  internal struct _Variant {
    @usableFromInline
    internal var object: _BridgeStorage<__RawDictionaryStorage>

    @inlinable
    @inline(__always)
    init(native: __owned _NativeDictionary<Key, Value>) {
      unsafe self.object = _BridgeStorage(native: native._storage)
    }

    @inlinable
    @inline(__always)
    init(dummy: Void) {
#if _pointerBitWidth(_64) && !$Embedded
      unsafe self.object = _BridgeStorage(taggedPayload: 0)
#elseif _pointerBitWidth(_32) || $Embedded
      self.init(native: _NativeDictionary())
#else
#error("Unknown platform")
#endif
    }

#if _runtime(_ObjC)
    @inlinable
    @inline(__always)
    init(cocoa: __owned __CocoaDictionary) {
      unsafe self.object = _BridgeStorage(objC: cocoa.object)
    }
#endif
  }
}

extension Dictionary._Variant {
#if _runtime(_ObjC)
  @usableFromInline @_transparent
  internal var guaranteedNative: Bool {
    return _canBeClass(Key.self) == 0 || _canBeClass(Value.self) == 0
  }
#endif

  @inlinable
  internal mutating func isUniquelyReferenced() -> Bool {
    return unsafe object.isUniquelyReferencedUnflaggedNative()
  }

#if _runtime(_ObjC)
  @usableFromInline @_transparent
  internal var isNative: Bool {
    if guaranteedNative { return true }
    return unsafe object.isUnflaggedNative
  }
#endif

  @usableFromInline @_transparent
  internal var asNative: _NativeDictionary<Key, Value> {
    get {
      return unsafe _NativeDictionary<Key, Value>(object.unflaggedNativeInstance)
    }
    set {
      self = .init(native: newValue)
    }
    _modify {
      var native = unsafe _NativeDictionary<Key, Value>(object.unflaggedNativeInstance)
      self = .init(dummy: ())
      defer { unsafe object = .init(native: native._storage) }
      yield &native
    }
  }

#if _runtime(_ObjC)
  @inlinable
  internal var asCocoa: __CocoaDictionary {
    return unsafe __CocoaDictionary(object.objCInstance)
  }
#endif

  /// Reserves enough space for the specified number of elements to be stored
  /// without reallocating additional storage.
  internal mutating func reserveCapacity(_ capacity: Int) {
#if _runtime(_ObjC)
    guard isNative else {
      let cocoa = asCocoa
      let capacity = Swift.max(cocoa.count, capacity)
      self = .init(native: _NativeDictionary(cocoa, capacity: capacity))
      return
    }
#endif
    let isUnique = isUniquelyReferenced()
    asNative.reserveCapacity(capacity, isUnique: isUnique)
  }

  /// The number of elements that can be stored without expanding the current
  /// storage.
  ///
  /// For bridged storage, this is equal to the current count of the
  /// collection, since any addition will trigger a copy of the elements into
  /// newly allocated storage. For native storage, this is the element count
  /// at which adding any more elements will exceed the load factor.
  @inlinable
  internal var capacity: Int {
#if _runtime(_ObjC)
    guard isNative else {
      return asCocoa.count
    }
#endif
    return asNative.capacity
  }
}

extension Dictionary._Variant: _DictionaryBuffer {
  @usableFromInline
  internal typealias Element = (key: Key, value: Value)
  @usableFromInline
  internal typealias Index = Dictionary<Key, Value>.Index

  @inlinable
  internal var startIndex: Index {
#if _runtime(_ObjC)
    guard isNative else {
      return Index(_cocoa: asCocoa.startIndex)
    }
#endif
    return asNative.startIndex
  }

  @inlinable
  internal var endIndex: Index {
#if _runtime(_ObjC)
    guard isNative else {
      return Index(_cocoa: asCocoa.endIndex)
    }
#endif
    return asNative.endIndex
  }

  @inlinable
  internal func index(after index: Index) -> Index {
#if _runtime(_ObjC)
    guard isNative else {
      return Index(_cocoa: asCocoa.index(after: index._asCocoa))
    }
#endif
    return asNative.index(after: index)
  }

  @inlinable
  internal func formIndex(after index: inout Index) {
#if _runtime(_ObjC)
    guard isNative else {
      let isUnique = index._isUniquelyReferenced()
      asCocoa.formIndex(after: &index._asCocoa, isUnique: isUnique)
      return
    }
#endif
    index = asNative.index(after: index)
  }

  @inlinable
  @inline(__always)
  internal func index(forKey key: Key) -> Index? {
#if _runtime(_ObjC)
    guard isNative else {
      let cocoaKey = _bridgeAnythingToObjectiveC(key)
      guard let index = asCocoa.index(forKey: cocoaKey) else { return nil }
      return Index(_cocoa: index)
    }
#endif
    return asNative.index(forKey: key)
  }

  @inlinable
  internal var count: Int {
    @inline(__always)
    get {
#if _runtime(_ObjC)
      guard isNative else {
        return asCocoa.count
      }
#endif
      return asNative.count
    }
  }

  @inlinable
  @inline(__always)
  func contains(_ key: Key) -> Bool {
#if _runtime(_ObjC)
    guard isNative else {
      let cocoaKey = _bridgeAnythingToObjectiveC(key)
      return asCocoa.contains(cocoaKey)
    }
#endif
    return asNative.contains(key)
  }

  @inlinable
  @inline(__always)
  func lookup(_ key: Key) -> Value? {
#if _runtime(_ObjC)
    guard isNative else {
      let cocoaKey = _bridgeAnythingToObjectiveC(key)
      guard let cocoaValue = asCocoa.lookup(cocoaKey) else { return nil }
      return _forceBridgeFromObjectiveC(cocoaValue, Value.self)
    }
#endif
    return asNative.lookup(key)
  }

  @inlinable
  @inline(__always)
  func lookup(_ index: Index) -> (key: Key, value: Value) {
#if _runtime(_ObjC)
    guard isNative else {
      let (cocoaKey, cocoaValue) = asCocoa.lookup(index._asCocoa)
      let nativeKey = _forceBridgeFromObjectiveC(cocoaKey, Key.self)
      let nativeValue = _forceBridgeFromObjectiveC(cocoaValue, Value.self)
      return (nativeKey, nativeValue)
    }
#endif
    return asNative.lookup(index)
  }

  @inlinable
  @inline(__always)
  func key(at index: Index) -> Key {
#if _runtime(_ObjC)
    guard isNative else {
      let cocoaKey = asCocoa.key(at: index._asCocoa)
      return _forceBridgeFromObjectiveC(cocoaKey, Key.self)
    }
#endif
    return asNative.key(at: index)
  }

  @inlinable
  @inline(__always)
  func value(at index: Index) -> Value {
#if _runtime(_ObjC)
    guard isNative else {
      let cocoaValue = asCocoa.value(at: index._asCocoa)
      return _forceBridgeFromObjectiveC(cocoaValue, Value.self)
    }
#endif
    return asNative.value(at: index)
  }
}

extension Dictionary._Variant {
  @inlinable
  internal subscript(key: Key) -> Value? {
    @inline(__always)
    get {
      return lookup(key)
    }
    @inline(__always)
    _modify {
#if _runtime(_ObjC)
      guard isNative else {
        let cocoa = asCocoa
        var native = _NativeDictionary<Key, Value>(
          cocoa, capacity: cocoa.count + 1)
        self = .init(native: native)
        yield &native[key, isUnique: true]
        return
      }
#endif
      let isUnique = isUniquelyReferenced()
      yield &asNative[key, isUnique: isUnique]
    }
  }
}

extension Dictionary._Variant {
  /// Same as find(_:), except assume a corresponding key/value pair will be
  /// inserted if it doesn't already exist, and mutated if it does exist. When
  /// this function returns, the storage is guaranteed to be native, uniquely
  /// held, and with enough capacity for a single insertion (if the key isn't
  /// already in the dictionary.)
  @inlinable
  @inline(__always)
  internal mutating func mutatingFind(
    _ key: Key
  ) -> (bucket: _NativeDictionary<Key, Value>.Bucket, found: Bool) {
#if _runtime(_ObjC)
    guard isNative else {
      let cocoa = asCocoa
      var native = _NativeDictionary<Key, Value>(
        cocoa, capacity: cocoa.count + 1)
      let result = native.mutatingFind(key, isUnique: true)
      self = .init(native: native)
      return result
    }
#endif
    let isUnique = isUniquelyReferenced()
    return asNative.mutatingFind(key, isUnique: isUnique)
  }

  @inlinable
  @inline(__always)
  internal mutating func ensureUniqueNative() -> _NativeDictionary<Key, Value> {
#if _runtime(_ObjC)
    guard isNative else {
      let native = _NativeDictionary<Key, Value>(asCocoa)
      self = .init(native: native)
      return native
    }
#endif
    let isUnique = isUniquelyReferenced()
    if !isUnique {
      asNative.copy()
    }
    return asNative
  }

  @inlinable
  internal mutating func updateValue(
    _ value: __owned Value,
    forKey key: Key
  ) -> Value? {
#if _runtime(_ObjC)
    guard isNative else {
      // Make sure we have space for an extra element.
      let cocoa = asCocoa
      var native = _NativeDictionary<Key, Value>(
        cocoa,
        capacity: cocoa.count + 1)
      let result = native.updateValue(value, forKey: key, isUnique: true)
      self = .init(native: native)
      return result
    }
#endif
    let isUnique = self.isUniquelyReferenced()
    return asNative.updateValue(value, forKey: key, isUnique: isUnique)
  }

  @inlinable
  internal mutating func setValue(_ value: __owned Value, forKey key: Key) {
#if _runtime(_ObjC)
    if !isNative {
      // Make sure we have space for an extra element.
      let cocoa = asCocoa
      self = .init(native: _NativeDictionary<Key, Value>(
        cocoa,
        capacity: cocoa.count + 1))
    }
#endif
    let isUnique = self.isUniquelyReferenced()
    asNative.setValue(value, forKey: key, isUnique: isUnique)
  }

  @inlinable
  @_semantics("optimize.sil.specialize.generic.size.never")
  internal mutating func remove(at index: Index) -> Element {
    // FIXME(performance): fuse data migration and element deletion into one
    // operation.
    let native = ensureUniqueNative()
    let bucket = native.validatedBucket(for: index)
    return unsafe asNative.uncheckedRemove(at: bucket, isUnique: true)
  }

  @inlinable
  internal mutating func removeValue(forKey key: Key) -> Value? {
#if _runtime(_ObjC)
    guard isNative else {
      let cocoaKey = _bridgeAnythingToObjectiveC(key)
      let cocoa = asCocoa
      guard cocoa.lookup(cocoaKey) != nil else { return nil }
      var native = _NativeDictionary<Key, Value>(cocoa)
      let (bucket, found) = native.find(key)
      _precondition(found, "Bridging did not preserve equality")
      let old = unsafe native.uncheckedRemove(at: bucket, isUnique: true).value
      self = .init(native: native)
      return old
    }
#endif
    let (bucket, found) = asNative.find(key)
    guard found else { return nil }
    let isUnique = isUniquelyReferenced()
    return unsafe asNative.uncheckedRemove(at: bucket, isUnique: isUnique).value
  }

  @inlinable
  @_semantics("optimize.sil.specialize.generic.size.never")
  internal mutating func removeAll(keepingCapacity keepCapacity: Bool) {
    if !keepCapacity {
      self = .init(native: _NativeDictionary())
      return
    }
    guard count > 0 else { return }

#if _runtime(_ObjC)
    guard isNative else {
      self = .init(native: _NativeDictionary(capacity: asCocoa.count))
      return
    }
#endif
    let isUnique = isUniquelyReferenced()
    asNative.removeAll(isUnique: isUnique)
  }
}

extension Dictionary._Variant {
  /// Returns an iterator over the `(Key, Value)` pairs.
  ///
  /// - Complexity: O(1).
  @inlinable
  @inline(__always)
  __consuming internal func makeIterator() -> Dictionary<Key, Value>.Iterator {
#if _runtime(_ObjC)
    guard isNative else {
      return Dictionary.Iterator(_cocoa: asCocoa.makeIterator())
    }
#endif
    return Dictionary.Iterator(_native: asNative.makeIterator())
  }
}

extension Dictionary._Variant {
  @export(implementation)
  internal func mapValues<T, E: Error>(
    _ transform: (Value) throws(E) -> T
  ) throws(E) -> _NativeDictionary<Key, T> {
#if _runtime(_ObjC)
    guard isNative else {
      return try asCocoa.mapValues(transform)
    }
#endif
    return try asNative.mapValues(transform)
  }

  @_alwaysEmitIntoClient
  internal func mapKeyedValues<T, E>(
    _ transform: (Key, Value) throws(E) -> T
  ) throws(E) -> _NativeDictionary<Key, T> {
#if _runtime(_ObjC)
    guard isNative else {
      return try asCocoa.mapKeyedValues(transform)
    }
#endif
    return try asNative.mapKeyedValues(transform)
  }

#if !$Embedded
  // ABI-only entrypoint for the rethrows version of mapValues, which has been
  // superseded by the typed-throws version. Expressed as "throws", which is
  // ABI-compatible with "rethrows".
  @_spi(SwiftStdlibLegacyABI) @available(swift, obsoleted: 1)
  @abi(
    func mapValues<T>(
      _ transform: (Value) throws -> T
    ) throws -> _NativeDictionary<Key, T>
  )
  @usableFromInline
  internal func __rethrows_mapValues<T>(
    _ transform: (Value) throws -> T
  ) throws -> _NativeDictionary<Key, T> {
    try mapValues(transform)
  }
#endif

  @export(implementation)
  internal mutating func merge<S: Sequence, E: Error>(
    _ keysAndValues: __owned S,
    uniquingKeysWith combine: (Value, Value) throws(E) -> Value
  ) throws(E) where S.Element == (Key, Value) {
#if _runtime(_ObjC)
    guard isNative else {
      var native = _NativeDictionary<Key, Value>(asCocoa)
      try native.merge(
        keysAndValues,
        isUnique: true,
        uniquingKeysWith: combine)
      self = .init(native: native)
      return
    }
#endif
    let isUnique = isUniquelyReferenced()
    try asNative.merge(
      keysAndValues,
      isUnique: isUnique,
      uniquingKeysWith: combine)
  }

#if !$Embedded
  // ABI-only entrypoint for the rethrows version of merge, which has been
  // superseded by the typed-throws version. Expressed as "throws", which is
  // ABI-compatible with "rethrows".
  @_spi(SwiftStdlibLegacyABI) @available(swift, obsoleted: 1)
  @abi(
    mutating func merge<S: Sequence>(
      _ keysAndValues: __owned S,
      uniquingKeysWith combine: (Value, Value) throws -> Value
    ) throws where S.Element == (Key, Value)
  )
  @usableFromInline
  internal mutating func __rethrows_merge<S: Sequence>(
    _ keysAndValues: __owned S,
    uniquingKeysWith combine: (Value, Value) throws -> Value
  ) throws where S.Element == (Key, Value) {
    try merge(keysAndValues, uniquingKeysWith: combine)
  }
#endif
}

