var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import * as React from 'react';
import { z } from 'zod/v4';
const SESSION_STATE_CHANNEL_NAME = 'dara-session-state';
const SESSION_REFRESH_LOCK_NAME = 'dara-auth-refresh-lock';
const SessionStateMessageSchema = z.discriminatedUnion('type', [
    z.object({
        type: z.literal('session_id'),
        value: z.string().nullable(),
    }),
    z.object({
        type: z.literal('session_logged_out'),
    }),
]);
const sessionIdSubscribers = new Set();
let sessionIdentifier = null;
let activeRefreshPromise = null;
const channel = typeof BroadcastChannel !== 'undefined' ? new BroadcastChannel(SESSION_STATE_CHANNEL_NAME) : null;
const inTabLockTails = new Map();
/**
 * Notify local subscribers about an updated session identifier.
 *
 * @param value current session identifier
 */
function notifySessionIdSubscribers(value) {
    sessionIdSubscribers.forEach((cb) => cb(value));
}
/**
 * Parse a cross-tab session state message.
 *
 * @param value unknown message payload
 */
function parseSessionStateMessage(value) {
    const parsed = SessionStateMessageSchema.safeParse(value);
    if (!parsed.success) {
        return null;
    }
    return parsed.data;
}
/**
 * Update the in-memory session identifier and optionally broadcast it to other tabs.
 *
 * @param value current session identifier
 * @param broadcast whether to emit cross-tab events
 */
function setSessionIdentifierInternal(value, broadcast) {
    if (sessionIdentifier === value) {
        return;
    }
    sessionIdentifier = value;
    notifySessionIdSubscribers(value);
    if (broadcast && channel) {
        channel.postMessage({ type: 'session_id', value });
    }
}
if (channel) {
    channel.addEventListener('message', (event) => {
        const message = parseSessionStateMessage(event.data);
        if (!message) {
            return;
        }
        if (message.type === 'session_logged_out') {
            setSessionIdentifierInternal(null, false);
            return;
        }
        setSessionIdentifierInternal(message.value, false);
    });
}
/**
 * Run a callback under an in-tab lock used when Web Locks are unavailable.
 *
 * @param callback callback to execute once this tab acquires the lock
 */
function withInTabRefreshLock(callback) {
    var _a;
    const previousTail = (_a = inTabLockTails.get(SESSION_REFRESH_LOCK_NAME)) !== null && _a !== void 0 ? _a : Promise.resolve();
    let releaseTail = () => undefined;
    const nextTail = new Promise((resolve) => {
        releaseTail = resolve;
    });
    inTabLockTails.set(SESSION_REFRESH_LOCK_NAME, previousTail.then(() => nextTail));
    return previousTail.then(() => __awaiter(this, void 0, void 0, function* () {
        try {
            return yield callback();
        }
        finally {
            releaseTail();
            if (inTabLockTails.get(SESSION_REFRESH_LOCK_NAME) === nextTail) {
                inTabLockTails.delete(SESSION_REFRESH_LOCK_NAME);
            }
        }
    }));
}
/**
 * Acquire the refresh lock using Web Locks when available, otherwise use the in-tab fallback.
 *
 * @param callback callback to execute under the lock
 */
function requestRefreshLock(callback) {
    var _a;
    if (typeof navigator !== 'undefined' && ((_a = navigator.locks) === null || _a === void 0 ? void 0 : _a.request)) {
        return navigator.locks
            .request(SESSION_REFRESH_LOCK_NAME, () => callback())
            .then((result) => Promise.resolve(result));
    }
    return withInTabRefreshLock(callback);
}
/**
 * Notify all tabs that the session has been logged out/invalidated.
 */
export function notifySessionLoggedOut() {
    setSessionIdentifierInternal(null, true);
    if (channel) {
        channel.postMessage({ type: 'session_logged_out' });
    }
}
/**
 * Subscribe to session ID changes.
 *
 * @param cb callback to call when the session ID changes
 */
export function onSessionIdChange(cb) {
    sessionIdSubscribers.add(cb);
    return () => {
        sessionIdSubscribers.delete(cb);
    };
}
/**
 * Retrieve the current Dara session ID.
 */
export function getSessionIdentifier() {
    return sessionIdentifier;
}
/**
 * Set the current Dara session ID.
 *
 * @param sessionId session identifier
 */
export function setSessionIdentifier(sessionId) {
    setSessionIdentifierInternal(sessionId, true);
}
/**
 * Helper hook that synchronizes with the current Dara session ID from store.
 */
export function useSessionIdentifier() {
    return React.useSyncExternalStore(onSessionIdChange, getSessionIdentifier);
}
/**
 * Run a callback under the cross-tab refresh lock.
 *
 * @param callback callback to execute under lock
 */
export function withSessionRefreshLock(callback) {
    return __awaiter(this, void 0, void 0, function* () {
        return requestRefreshLock(callback);
    });
}
/**
 * Wait for any currently active refresh lock to complete.
 */
export function waitForOngoingSessionRefresh() {
    return __awaiter(this, void 0, void 0, function* () {
        yield requestRefreshLock(() => undefined);
    });
}
/**
 * Run the given refresh function once per tab and share the in-flight result.
 *
 * Cross-tab mutual exclusion is guaranteed via Web Locks when available, with
 * an in-tab lock fallback otherwise.
 *
 * @param refreshFn refresh callback
 */
export function runSessionRefresh(refreshFn) {
    if (activeRefreshPromise) {
        return activeRefreshPromise;
    }
    const refreshPromise = withSessionRefreshLock(refreshFn).finally(() => {
        if (activeRefreshPromise === refreshPromise) {
            activeRefreshPromise = null;
        }
    });
    activeRefreshPromise = refreshPromise;
    return refreshPromise;
}
//# sourceMappingURL=session-state.js.map