/**
 * @license Angular v20.3.15
 * (c) 2010-2025 Google LLC. https://angular.dev/
 * License: MIT
 */

import { Observable, ReplaySubject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { assertInInjectionContext, inject, DestroyRef, RuntimeError, Injector, assertNotInReactiveContext, signal, PendingTasks } from './root_effect_scheduler.mjs';
import { getOutputDestroyRef, effect, untracked, computed, resource, encapsulateResourceError } from './resource.mjs';
import './not_found.mjs';
import './signal.mjs';
import '@angular/core/primitives/signals';
import '@angular/core/primitives/di';
import './effect.mjs';

/**
 * Operator which completes the Observable when the calling context (component, directive, service,
 * etc) is destroyed.
 *
 * @param destroyRef optionally, the `DestroyRef` representing the current context. This can be
 *     passed explicitly to use `takeUntilDestroyed` outside of an [injection
 * context](guide/di/dependency-injection-context). Otherwise, the current `DestroyRef` is injected.
 *
 * @see [Unsubscribing with takeUntilDestroyed](ecosystem/rxjs-interop/take-until-destroyed)
 *
 * @publicApi 19.0
 */
function takeUntilDestroyed(destroyRef) {
    if (!destroyRef) {
        ngDevMode && assertInInjectionContext(takeUntilDestroyed);
        destroyRef = inject(DestroyRef);
    }
    const destroyed$ = new Observable((subscriber) => {
        if (destroyRef.destroyed) {
            subscriber.next();
            return;
        }
        const unregisterFn = destroyRef.onDestroy(subscriber.next.bind(subscriber));
        return unregisterFn;
    });
    return (source) => {
        return source.pipe(takeUntil(destroyed$));
    };
}

/**
 * Implementation of `OutputRef` that emits values from
 * an RxJS observable source.
 *
 * @internal
 */
class OutputFromObservableRef {
    source;
    destroyed = false;
    destroyRef = inject(DestroyRef);
    constructor(source) {
        this.source = source;
        this.destroyRef.onDestroy(() => {
            this.destroyed = true;
        });
    }
    subscribe(callbackFn) {
        if (this.destroyed) {
            throw new RuntimeError(953 /* ɵRuntimeErrorCode.OUTPUT_REF_DESTROYED */, ngDevMode &&
                'Unexpected subscription to destroyed `OutputRef`. ' +
                    'The owning directive/component is destroyed.');
        }
        // Stop yielding more values when the directive/component is already destroyed.
        const subscription = this.source.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
            next: (value) => callbackFn(value),
        });
        return {
            unsubscribe: () => subscription.unsubscribe(),
        };
    }
}
/**
 * Declares an Angular output that is using an RxJS observable as a source
 * for events dispatched to parent subscribers.
 *
 * The behavior for an observable as source is defined as followed:
 *    1. New values are forwarded to the Angular output (next notifications).
 *    2. Errors notifications are not handled by Angular. You need to handle these manually.
 *       For example by using `catchError`.
 *    3. Completion notifications stop the output from emitting new values.
 *
 * @usageNotes
 * Initialize an output in your directive by declaring a
 * class field and initializing it with the `outputFromObservable()` function.
 *
 * ```ts
 * @Directive({..})
 * export class MyDir {
 *   nameChange$ = <some-observable>;
 *   nameChange = outputFromObservable(this.nameChange$);
 * }
 * ```
 * @see [RxJS interop with component and directive outputs](ecosystem/rxjs-interop/output-interop)
 *
 * @publicApi 19.0
 */
function outputFromObservable(observable, opts) {
    ngDevMode && assertInInjectionContext(outputFromObservable);
    return new OutputFromObservableRef(observable);
}

/**
 * Converts an Angular output declared via `output()` or `outputFromObservable()`
 * to an observable.
 * It creates an observable that represents the stream of "events firing" in an output.
 *
 * You can subscribe to the output via `Observable.subscribe` then.
 *
 * @see [RxJS interop with component and directive outputs](ecosystem/rxjs-interop/output-interop)
 *
 * @publicApi 19.0
 */
function outputToObservable(ref) {
    const destroyRef = getOutputDestroyRef(ref);
    return new Observable((observer) => {
        // Complete the observable upon directive/component destroy.
        // Note: May be `undefined` if an `EventEmitter` is declared outside
        // of an injection context.
        const unregisterOnDestroy = destroyRef?.onDestroy(() => observer.complete());
        const subscription = ref.subscribe((v) => observer.next(v));
        return () => {
            subscription.unsubscribe();
            unregisterOnDestroy?.();
        };
    });
}

/**
 * Exposes the value of an Angular `Signal` as an RxJS `Observable`.
 * As it reflects a state, the observable will always emit the latest value upon subscription.
 *
 * The signal's value will be propagated into the `Observable`'s subscribers using an `effect`.
 *
 * `toObservable` must be called in an injection context unless an injector is provided via options.
 *
 * @see [RxJS interop with Angular signals](ecosystem/rxjs-interop)
 * @see [Create an RxJS Observable from a signal with toObservable](ecosystem/rxjs-interop#create-an-rxjs-observable-from-a-signal-with-toobservable)
 *
 * @publicApi 20.0
 */
function toObservable(source, options) {
    if (ngDevMode && !options?.injector) {
        assertInInjectionContext(toObservable);
    }
    const injector = options?.injector ?? inject(Injector);
    const subject = new ReplaySubject(1);
    const watcher = effect(() => {
        let value;
        try {
            value = source();
        }
        catch (err) {
            untracked(() => subject.error(err));
            return;
        }
        untracked(() => subject.next(value));
    }, { injector, manualCleanup: true });
    injector.get(DestroyRef).onDestroy(() => {
        watcher.destroy();
        subject.complete();
    });
    return subject.asObservable();
}

/**
 * Get the current value of an `Observable` as a reactive `Signal`.
 *
 * `toSignal` returns a `Signal` which provides synchronous reactive access to values produced
 * by the given `Observable`, by subscribing to that `Observable`. The returned `Signal` will always
 * have the most recent value emitted by the subscription, and will throw an error if the
 * `Observable` errors.
 *
 * With `requireSync` set to `true`, `toSignal` will assert that the `Observable` produces a value
 * immediately upon subscription. No `initialValue` is needed in this case, and the returned signal
 * does not include an `undefined` type.
 *
 * By default, the subscription will be automatically cleaned up when the current [injection
 * context](guide/di/dependency-injection-context) is destroyed. For example, when `toSignal` is
 * called during the construction of a component, the subscription will be cleaned up when the
 * component is destroyed. If an injection context is not available, an explicit `Injector` can be
 * passed instead.
 *
 * If the subscription should persist until the `Observable` itself completes, the `manualCleanup`
 * option can be specified instead, which disables the automatic subscription teardown. No injection
 * context is needed in this configuration as well.
 *
 * @see [RxJS interop with Angular signals](ecosystem/rxjs-interop)
 */
function toSignal(source, options) {
    typeof ngDevMode !== 'undefined' &&
        ngDevMode &&
        assertNotInReactiveContext(toSignal, 'Invoking `toSignal` causes new subscriptions every time. ' +
            'Consider moving `toSignal` outside of the reactive context and read the signal value where needed.');
    const requiresCleanup = !options?.manualCleanup;
    if (ngDevMode && requiresCleanup && !options?.injector) {
        assertInInjectionContext(toSignal);
    }
    const cleanupRef = requiresCleanup
        ? (options?.injector?.get(DestroyRef) ?? inject(DestroyRef))
        : null;
    const equal = makeToSignalEqual(options?.equal);
    // Note: T is the Observable value type, and U is the initial value type. They don't have to be
    // the same - the returned signal gives values of type `T`.
    let state;
    if (options?.requireSync) {
        // Initially the signal is in a `NoValue` state.
        state = signal({ kind: 0 /* StateKind.NoValue */ }, { equal });
    }
    else {
        // If an initial value was passed, use it. Otherwise, use `undefined` as the initial value.
        state = signal({ kind: 1 /* StateKind.Value */, value: options?.initialValue }, { equal });
    }
    let destroyUnregisterFn;
    // Note: This code cannot run inside a reactive context (see assertion above). If we'd support
    // this, we would subscribe to the observable outside of the current reactive context, avoiding
    // that side-effect signal reads/writes are attribute to the current consumer. The current
    // consumer only needs to be notified when the `state` signal changes through the observable
    // subscription. Additional context (related to async pipe):
    // https://github.com/angular/angular/pull/50522.
    const sub = source.subscribe({
        next: (value) => state.set({ kind: 1 /* StateKind.Value */, value }),
        error: (error) => {
            state.set({ kind: 2 /* StateKind.Error */, error });
            destroyUnregisterFn?.();
        },
        complete: () => {
            destroyUnregisterFn?.();
        },
        // Completion of the Observable is meaningless to the signal. Signals don't have a concept of
        // "complete".
    });
    if (options?.requireSync && state().kind === 0 /* StateKind.NoValue */) {
        throw new RuntimeError(601 /* ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT */, (typeof ngDevMode === 'undefined' || ngDevMode) &&
            '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.');
    }
    // Unsubscribe when the current context is destroyed, if requested.
    destroyUnregisterFn = cleanupRef?.onDestroy(sub.unsubscribe.bind(sub));
    // The actual returned signal is a `computed` of the `State` signal, which maps the various states
    // to either values or errors.
    return computed(() => {
        const current = state();
        switch (current.kind) {
            case 1 /* StateKind.Value */:
                return current.value;
            case 2 /* StateKind.Error */:
                throw current.error;
            case 0 /* StateKind.NoValue */:
                // This shouldn't really happen because the error is thrown on creation.
                throw new RuntimeError(601 /* ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT */, (typeof ngDevMode === 'undefined' || ngDevMode) &&
                    '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.');
        }
    }, { equal: options?.equal });
}
function makeToSignalEqual(userEquality = Object.is) {
    return (a, b) => a.kind === 1 /* StateKind.Value */ && b.kind === 1 /* StateKind.Value */ && userEquality(a.value, b.value);
}

/**
 * Operator which makes the application unstable until the observable emits, completes, errors, or is unsubscribed.
 *
 * Use this operator in observables whose subscriptions are important for rendering and should be included in SSR serialization.
 *
 * @param injector The `Injector` to use during creation. If this is not provided, the current injection context will be used instead (via `inject`).
 *
 * @developerPreview 20.0
 */
function pendingUntilEvent(injector) {
    if (injector === undefined) {
        ngDevMode && assertInInjectionContext(pendingUntilEvent);
        injector = inject(Injector);
    }
    const taskService = injector.get(PendingTasks);
    return (sourceObservable) => {
        return new Observable((originalSubscriber) => {
            // create a new task on subscription
            const removeTask = taskService.add();
            let cleanedUp = false;
            function cleanupTask() {
                if (cleanedUp) {
                    return;
                }
                removeTask();
                cleanedUp = true;
            }
            const innerSubscription = sourceObservable.subscribe({
                next: (v) => {
                    originalSubscriber.next(v);
                    cleanupTask();
                },
                complete: () => {
                    originalSubscriber.complete();
                    cleanupTask();
                },
                error: (e) => {
                    originalSubscriber.error(e);
                    cleanupTask();
                },
            });
            innerSubscription.add(() => {
                originalSubscriber.unsubscribe();
                cleanupTask();
            });
            return innerSubscription;
        });
    };
}

function rxResource(opts) {
    if (ngDevMode && !opts?.injector) {
        assertInInjectionContext(rxResource);
    }
    return resource({
        ...opts,
        loader: undefined,
        stream: (params) => {
            let sub;
            // Track the abort listener so it can be removed if the Observable completes (as a memory
            // optimization).
            const onAbort = () => sub?.unsubscribe();
            params.abortSignal.addEventListener('abort', onAbort);
            // Start off stream as undefined.
            const stream = signal({ value: undefined });
            let resolve;
            const promise = new Promise((r) => (resolve = r));
            function send(value) {
                stream.set(value);
                resolve?.(stream);
                resolve = undefined;
            }
            // TODO(alxhub): remove after g3 updated to rename loader -> stream
            const streamFn = opts.stream ?? opts.loader;
            if (streamFn === undefined) {
                throw new RuntimeError(990 /* ɵRuntimeErrorCode.MUST_PROVIDE_STREAM_OPTION */, ngDevMode && `Must provide \`stream\` option.`);
            }
            sub = streamFn(params).subscribe({
                next: (value) => send({ value }),
                error: (error) => {
                    send({ error: encapsulateResourceError(error) });
                    params.abortSignal.removeEventListener('abort', onAbort);
                },
                complete: () => {
                    if (resolve) {
                        send({
                            error: new RuntimeError(991 /* ɵRuntimeErrorCode.RESOURCE_COMPLETED_BEFORE_PRODUCING_VALUE */, ngDevMode && 'Resource completed before producing a value'),
                        });
                    }
                    params.abortSignal.removeEventListener('abort', onAbort);
                },
            });
            return promise;
        },
    });
}

export { outputFromObservable, outputToObservable, pendingUntilEvent, rxResource, takeUntilDestroyed, toObservable, toSignal };
//# sourceMappingURL=rxjs-interop.mjs.map
