import type {
Thenable,
FulfilledThenable,
RejectedThenable,
} from 'shared/ReactTypes';
import type {Lane} from './ReactFiberLane';
import type {Transition} from 'react/src/ReactStartTransition';
import {
requestTransitionLane,
ensureScheduleIsScheduled,
} from './ReactFiberRootScheduler';
import {NoLane} from './ReactFiberLane';
import {
hasScheduledTransitionWork,
clearAsyncTransitionTimer,
} from './ReactProfilerTimer';
import {
enableComponentPerformanceTrack,
enableProfilerTimer,
enableDefaultTransitionIndicator,
} from 'shared/ReactFeatureFlags';
import {clearEntangledAsyncTransitionTypes} from './ReactFiberTransitionTypes';
import noop from 'shared/noop';
import reportGlobalError from 'shared/reportGlobalError';
let currentEntangledListeners: Array<() => mixed> | null = null;
let currentEntangledPendingCount: number = 0;
let currentEntangledLane: Lane = NoLane;
let currentEntangledActionThenable: Thenable<void> | null = null;
let isomorphicDefaultTransitionIndicator:
| void
| null
| (() => void | (() => void)) = undefined;
let pendingIsomorphicIndicator: null | (() => void) = null;
let pendingEntangledRoots: number = 0;
let needsIsomorphicIndicator: boolean = false;
export function entangleAsyncAction<S>(
transition: Transition,
thenable: Thenable<S>,
): Thenable<S> {
if (currentEntangledListeners === null) {
const entangledListeners = (currentEntangledListeners = []);
currentEntangledPendingCount = 0;
currentEntangledLane = requestTransitionLane(transition);
const entangledThenable: Thenable<void> = {
status: 'pending',
value: undefined,
then(resolve: void => mixed) {
entangledListeners.push(resolve);
},
};
currentEntangledActionThenable = entangledThenable;
if (enableDefaultTransitionIndicator) {
needsIsomorphicIndicator = true;
ensureScheduleIsScheduled();
}
}
currentEntangledPendingCount++;
thenable.then(pingEngtangledActionScope, pingEngtangledActionScope);
return thenable;
}
function pingEngtangledActionScope() {
if (--currentEntangledPendingCount === 0) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (!hasScheduledTransitionWork()) {
clearAsyncTransitionTimer();
}
}
clearEntangledAsyncTransitionTypes();
if (pendingEntangledRoots === 0) {
stopIsomorphicDefaultIndicator();
}
if (currentEntangledListeners !== null) {
if (currentEntangledActionThenable !== null) {
const fulfilledThenable: FulfilledThenable<void> =
(currentEntangledActionThenable: any);
fulfilledThenable.status = 'fulfilled';
}
const listeners = currentEntangledListeners;
currentEntangledListeners = null;
currentEntangledLane = NoLane;
currentEntangledActionThenable = null;
needsIsomorphicIndicator = false;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
}
}
}
export function chainThenableValue<T>(
thenable: Thenable<T>,
result: T,
): Thenable<T> {
const listeners = [];
const thenableWithOverride: Thenable<T> = {
status: 'pending',
value: null,
reason: null,
then(resolve: T => mixed) {
listeners.push(resolve);
},
};
thenable.then(
(value: T) => {
const fulfilledThenable: FulfilledThenable<T> =
(thenableWithOverride: any);
fulfilledThenable.status = 'fulfilled';
fulfilledThenable.value = result;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener(result);
}
},
error => {
const rejectedThenable: RejectedThenable<T> = (thenableWithOverride: any);
rejectedThenable.status = 'rejected';
rejectedThenable.reason = error;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener((undefined: any));
}
},
);
return thenableWithOverride;
}
export function peekEntangledActionLane(): Lane {
return currentEntangledLane;
}
export function peekEntangledActionThenable(): Thenable<void> | null {
return currentEntangledActionThenable;
}
export function registerDefaultIndicator(
onDefaultTransitionIndicator: () => void | (() => void),
): void {
if (!enableDefaultTransitionIndicator) {
return;
}
if (isomorphicDefaultTransitionIndicator === undefined) {
isomorphicDefaultTransitionIndicator = onDefaultTransitionIndicator;
} else if (
isomorphicDefaultTransitionIndicator !== onDefaultTransitionIndicator
) {
isomorphicDefaultTransitionIndicator = null;
stopIsomorphicDefaultIndicator();
}
}
export function startIsomorphicDefaultIndicatorIfNeeded() {
if (!enableDefaultTransitionIndicator) {
return;
}
if (!needsIsomorphicIndicator) {
return;
}
if (
isomorphicDefaultTransitionIndicator != null &&
pendingIsomorphicIndicator === null
) {
try {
pendingIsomorphicIndicator =
isomorphicDefaultTransitionIndicator() || noop;
} catch (x) {
pendingIsomorphicIndicator = noop;
reportGlobalError(x);
}
}
}
function stopIsomorphicDefaultIndicator() {
if (!enableDefaultTransitionIndicator) {
return;
}
if (pendingIsomorphicIndicator !== null) {
const cleanup = pendingIsomorphicIndicator;
pendingIsomorphicIndicator = null;
cleanup();
}
}
function releaseIsomorphicIndicator() {
if (--pendingEntangledRoots === 0) {
stopIsomorphicDefaultIndicator();
}
}
export function hasOngoingIsomorphicIndicator(): boolean {
return pendingIsomorphicIndicator !== null;
}
export function retainIsomorphicIndicator(): () => void {
pendingEntangledRoots++;
return releaseIsomorphicIndicator;
}
export function markIsomorphicIndicatorHandled(): void {
needsIsomorphicIndicator = false;
}