//
//  GSKeyValueObservation.h
//  GlyphsCore
//
//  Created by Florian Pircher on 16.05.24.
//  Copyright © 2024 schriftgestaltung.de. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/// A reimplementation of Swift’s `NSKeyValueObservation`, usable from Objective-C and using string key paths.
@interface GSKeyValueObservation : NSObject
/**
Creates a new observation proxy that observes the given object, forwarding observation messages to the given observer.

The returned observation proxy adds itself as an observer of `object` with the given key path, options, and context.
Any observation message is then forwarded to the `observer`, to which the proxy holds a weak reference.
Before forwarding the message, the proxy checks whether the `observer` is `nil` and if so, removes itself as an observer from `object`.

The proxy also removes itself as an observer when it is deallocated.
This allows for a convenient pattern, where the observation is stored in a private property.
Replacing the value if the property stops the current observation, if any, and keeps track of the current observation.
When the containing object is deallocated, so is the observation property and the observation is stopped.

```objc
@interface Controller ()
@property (strong) GSKeyValueObservation *someObservation;
@end

@implementation Controller
- (void)thingDidUpdate:(Thing *)newThing {
    _someObservation = [GSKeyValueObservation observe:newThing ...];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    // `_someObservation` forwards messages to this method
}
@end
```

When `object` is `nil` or `NSNull`, this method returns `nil`.
 */
+ (nullable instancetype)observe:(nullable NSObject *)object forKeyPath:(NSString *)keyPath withObserver:(NSObject *)observer options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
/**
Replaces an existing observation with a new observation.

This is a thin wrapper around ``observe:forKeyPath:withObserver:options:context:`` that ensures that the new observation does not start before the given `observation` ended.
Simply replacing the existing observation with the new observation, as shown in the example from ``observe:forKeyPath:withObserver:options:context:``, works in most cases.
However, this creates the new observation just before the existing observation is replaced, leaving a narrow timeframe where both observations are active.
Using this method eliminates that timeframe.
```objc
- (void)thingDidUpdate:(Thing *)newThing {
    [GSKeyValueObservation replaceObservation:&_someObservation observe:newThing ...];
}
```
- Parameter observation: A pointer to the observation to stop and then replace.
  Can be a pointer to `nil` when no current observation exists.
- Parameter observer: When `nil`, stops the observation.
 */
+ (void)replaceObservation:(GSKeyValueObservation * __strong _Nullable * _Nonnull)observation observe:(nullable NSObject *)object forKeyPath:(NSString *)keyPath withObserver:(NSObject *)observer options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

@end

NS_ASSUME_NONNULL_END
