import { DisposableStore } from '../lifecycle.js';

/**
 * Represents an observable value.
 *
 * @template T The type of the values the observable can hold.
 * @template TChange The type used to describe value changes
 * (usually `void` and only used in advanced scenarios).
 * While observers can miss temporary values of an observable,
 * they will receive all change values (as long as they are subscribed)!
 */
interface IObservable<T, TChange = unknown> {
    /**
     * Returns the current value.
     *
     * Calls {@link IObserver.handleChange} if the observable notices that the value changed.
     * Must not be called from {@link IObserver.handleChange}!
     */
    get(): T;
    /**
     * Forces the observable to check for changes and report them.
     *
     * Has the same effect as calling {@link IObservable.get}, but does not force the observable
     * to actually construct the value, e.g. if change deltas are used.
     * Calls {@link IObserver.handleChange} if the observable notices that the value changed.
     * Must not be called from {@link IObserver.handleChange}!
     */
    reportChanges(): void;
    /**
     * Adds the observer to the set of subscribed observers.
     * This method is idempotent.
     */
    addObserver(observer: IObserver): void;
    /**
     * Removes the observer from the set of subscribed observers.
     * This method is idempotent.
     */
    removeObserver(observer: IObserver): void;
    /**
     * Reads the current value and subscribes the reader to this observable.
     *
     * Calls {@link IReader.readObservable} if a reader is given, otherwise {@link IObservable.get}
     * (see {@link ConvenientObservable.read} for the implementation).
     */
    read(reader: IReader | undefined): T;
    /**
     * Creates a derived observable that depends on this observable.
     * Use the reader to read other observables
     * (see {@link ConvenientObservable.map} for the implementation).
     */
    map<TNew>(fn: (value: T, reader: IReader) => TNew): IObservable<TNew>;
    map<TNew>(owner: object, fn: (value: T, reader: IReader) => TNew): IObservable<TNew>;
    /**
     * Makes sure this value is computed eagerly.
     */
    recomputeInitiallyAndOnChange(store: DisposableStore, handleValue?: (value: T) => void): IObservable<T>;
    /**
     * Makes sure this value is cached.
     */
    keepObserved(store: DisposableStore): IObservable<T>;
    /**
     * A human-readable name for debugging purposes.
     */
    readonly debugName: string;
    /**
     * This property captures the type of the change object. Do not use it at runtime!
     */
    readonly TChange: TChange;
}
interface IReader {
    /**
     * Reads the value of an observable and subscribes to it.
     */
    readObservable<T>(observable: IObservable<T, any>): T;
}
/**
 * Represents an observer that can be subscribed to an observable.
 *
 * If an observer is subscribed to an observable and that observable didn't signal
 * a change through one of the observer methods, the observer can assume that the
 * observable didn't change.
 * If an observable reported a possible change, {@link IObservable.reportChanges} forces
 * the observable to report an actual change if there was one.
 */
interface IObserver {
    /**
     * Signals that the given observable might have changed and a transaction potentially modifying that observable started.
     * Before the given observable can call this method again, is must call {@link IObserver.endUpdate}.
     *
     * Implementations must not get/read the value of other observables, as they might not have received this event yet!
     * The method {@link IObservable.reportChanges} can be used to force the observable to report the changes.
     */
    beginUpdate<T>(observable: IObservable<T>): void;
    /**
     * Signals that the transaction that potentially modified the given observable ended.
     * This is a good place to react to (potential) changes.
     */
    endUpdate<T>(observable: IObservable<T>): void;
    /**
     * Signals that the given observable might have changed.
     * The method {@link IObservable.reportChanges} can be used to force the observable to report the changes.
     *
     * Implementations must not get/read the value of other observables, as they might not have received this event yet!
     * The change should be processed lazily or in {@link IObserver.endUpdate}.
     */
    handlePossibleChange<T>(observable: IObservable<T>): void;
    /**
     * Signals that the given {@link observable} changed.
     *
     * Implementations must not get/read the value of other observables, as they might not have received this event yet!
     * The change should be processed lazily or in {@link IObserver.endUpdate}.
     *
     * @param change Indicates how or why the value changed.
     */
    handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void;
}
interface ITransaction {
    /**
     * Calls {@link Observer.beginUpdate} immediately
     * and {@link Observer.endUpdate} when the transaction ends.
     */
    updateObserver(observer: IObserver, observable: IObservable<any, any>): void;
}

export type { IObservable, IObserver, IReader, ITransaction };
